diff --git a/bridge/cmd/start.go b/bridge/cmd/start.go index aa1d3390e..0a9093be6 100644 --- a/bridge/cmd/start.go +++ b/bridge/cmd/start.go @@ -40,8 +40,7 @@ func GetStartCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { // create codec - cdc := app.MakeCodec() - + cdc := app.MakeCodec() // queue connector & http client _queueConnector := queue.NewQueueConnector(helper.GetConfig().AmqpURL) _queueConnector.StartWorker() @@ -52,7 +51,7 @@ func GetStartCmd() *cobra.Command { // selected services to start services := []common.Service{} services = append(services, - listener.NewListenerService(cdc, _queueConnector), + listener.NewListenerService(cdc, _queueConnector, _httpClient), processor.NewProcessorService(cdc, _queueConnector, _httpClient, _txBroadcaster), ) diff --git a/bridge/setu/listener/base.go b/bridge/setu/listener/base.go index f49841eda..ab917e376 100644 --- a/bridge/setu/listener/base.go +++ b/bridge/setu/listener/base.go @@ -17,6 +17,8 @@ import ( "github.com/maticnetwork/heimdall/bridge/setu/queue" "github.com/maticnetwork/heimdall/bridge/setu/util" "github.com/maticnetwork/heimdall/helper" + + httpClient "github.com/tendermint/tendermint/rpc/client" ) // Listener defines a block header listerner for Rootchain, Maticchain, Heimdall @@ -64,12 +66,15 @@ type BaseListener struct { // queue connector queueConnector *queue.QueueConnector + // http client to subscribe to + httpClient *httpClient.HTTP + // storage client storageClient *leveldb.DB } // NewBaseListener creates a new BaseListener. -func NewBaseListener(cdc *codec.Codec, queueConnector *queue.QueueConnector, chainClient *ethclient.Client, name string, impl Listener) *BaseListener { +func NewBaseListener(cdc *codec.Codec, queueConnector *queue.QueueConnector, httpClient *httpClient.HTTP, chainClient *ethclient.Client, name string, impl Listener) *BaseListener { logger := util.Logger().With("service", "listener", "module", name) contractCaller, err := helper.NewContractCaller() @@ -92,6 +97,7 @@ func NewBaseListener(cdc *codec.Codec, queueConnector *queue.QueueConnector, cha cliCtx: cliCtx, queueConnector: queueConnector, + httpClient: httpClient, contractConnector: contractCaller, chainClient: chainClient, diff --git a/bridge/setu/listener/heimdall.go b/bridge/setu/listener/heimdall.go index cd7a8054e..a8b24f77c 100644 --- a/bridge/setu/listener/heimdall.go +++ b/bridge/setu/listener/heimdall.go @@ -3,7 +3,6 @@ package listener import ( "context" "encoding/json" - "fmt" "strconv" "time" @@ -64,17 +63,36 @@ func (hl *HeimdallListener) StartPolling(ctx context.Context, pollInterval time. // the ending of the interval ticker := time.NewTicker(interval) - var eventTypes []string - eventTypes = append(eventTypes, "message.action='checkpoint'") - eventTypes = append(eventTypes, "message.action='event-record'") + // var eventTypes []string + // eventTypes = append(eventTypes, "message.action='checkpoint'") + // eventTypes = append(eventTypes, "message.action='event-record'") + // eventTypes = append(eventTypes, "message.action='tick'") + // ADD EVENT TYPE for SLASH-LIMIT // start listening for { select { case <-ticker.C: - fromBlock, toBlock := hl.fetchFromAndToBlock() - if fromBlock < toBlock { - for _, eventType := range eventTypes { + fromBlock, toBlock, err := hl.fetchFromAndToBlock() + if err != nil { + hl.Logger.Error("Error fetching fromBlock and toBlock...skipping events query", "error", err) + } else if fromBlock < toBlock { + + hl.Logger.Info("Fetching new events between", "fromBlock", fromBlock, "toBlock", toBlock) + + // Querying and processing Begin events + for i := fromBlock; i <= toBlock; i++ { + events, err := helper.GetBeginBlockEvents(hl.httpClient, int64(i)) + if err != nil { + hl.Logger.Error("Error fetching begin block events", "error", err) + } + for _, event := range events { + hl.ProcessBlockEvent(sdk.StringifyEvent(event), int64(i)) + } + } + + // Querying and processing tx Events. Below for loop is kept for future purpose to process events from tx + /* for _, eventType := range eventTypes { var query []string query = append(query, eventType) query = append(query, fmt.Sprintf("tx.height>=%v", fromBlock)) @@ -107,7 +125,7 @@ func (hl *HeimdallListener) StartPolling(ctx context.Context, pollInterval time. page = 0 } } - } + } */ // set last block to storage if err := hl.storageClient.Put([]byte(heimdallLastBlockKey), []byte(strconv.FormatUint(toBlock, 10)), nil); err != nil { hl.Logger.Error("hl.storageClient.Put", "Error", err) @@ -122,28 +140,25 @@ func (hl *HeimdallListener) StartPolling(ctx context.Context, pollInterval time. } } -func (hl *HeimdallListener) fetchFromAndToBlock() (fromBlock uint64, toBlock uint64) { +func (hl *HeimdallListener) fetchFromAndToBlock() (uint64, uint64, error) { // toBlock - get latest blockheight from heimdall node + fromBlock := uint64(0) + toBlock := uint64(0) + nodeStatus, err := helper.GetNodeStatus(hl.cliCtx) if err != nil { - hl.Logger.Error("Error while fetching latest block", "error", err) - return + hl.Logger.Error("Error while fetching heimdall node status", "error", err) + return fromBlock, toBlock, err } toBlock = uint64(nodeStatus.SyncInfo.LatestBlockHeight) - // Process them after two blocks since side-tx takes 2 block to process - if toBlock <= 2 { - return - } - toBlock = toBlock - 2 - // fromBlock - get last block from storage hasLastBlock, _ := hl.storageClient.Has([]byte(heimdallLastBlockKey), nil) if hasLastBlock { lastBlockBytes, err := hl.storageClient.Get([]byte(heimdallLastBlockKey), nil) if err != nil { hl.Logger.Info("Error while fetching last block bytes from storage", "error", err) - return + return fromBlock, toBlock, err } if result, err := strconv.ParseUint(string(lastBlockBytes), 10, 64); err == nil { @@ -152,38 +167,33 @@ func (hl *HeimdallListener) fetchFromAndToBlock() (fromBlock uint64, toBlock uin } else { hl.Logger.Info("Error parsing last block bytes from storage", "error", err) toBlock = 0 - return + return fromBlock, toBlock, err } } - return + return fromBlock, toBlock, err } -// ProcessEvent - process event from heimdall. -func (hl *HeimdallListener) ProcessEvent(event sdk.StringEvent, tx sdk.TxResponse) { - hl.Logger.Info("Process received event from Heimdall", "eventType", event.Type) +// ProcessBlockEvent - process Blockevents (BeginBlock, EndBlock events) from heimdall. +func (hl *HeimdallListener) ProcessBlockEvent(event sdk.StringEvent, blockHeight int64) { + hl.Logger.Info("Received block event from Heimdall", "eventType", event.Type) eventBytes, err := json.Marshal(event) if err != nil { - hl.Logger.Error("Error while parsing event", "error", err, "eventType", event.Type) + hl.Logger.Error("Error while parsing block event", "error", err, "eventType", event.Type) return } - // txBytes, err := json.Marshal(tx) - // if err != nil { - // hl.Logger.Error("Error while parsing tx", "error", err, "txHash", tx.TxHash) - // return - // } - switch event.Type { case clerkTypes.EventTypeRecord: - hl.sendTask("sendDepositRecordToMatic", eventBytes, tx.Height, tx.TxHash) + hl.sendBlockTask("sendDepositRecordToMatic", eventBytes, blockHeight) case checkpointTypes.EventTypeCheckpoint: - hl.sendTask("sendCheckpointToRootchain", eventBytes, tx.Height, tx.TxHash) + hl.sendBlockTask("sendCheckpointToRootchain", eventBytes, blockHeight) + default: - hl.Logger.Info("EventType mismatch", "eventType", event.Type) + hl.Logger.Info("BlockEvent Type mismatch", "eventType", event.Type) } } -func (hl *HeimdallListener) sendTask(taskName string, eventBytes []byte, txHeight int64, txHash string) { +func (hl *HeimdallListener) sendBlockTask(taskName string, eventBytes []byte, blockHeight int64) { // create machinery task signature := &tasks.Signature{ Name: taskName, @@ -194,19 +204,15 @@ func (hl *HeimdallListener) sendTask(taskName string, eventBytes []byte, txHeigh }, { Type: "int64", - Value: txHeight, - }, - { - Type: "string", - Value: txHash, + Value: blockHeight, }, }, } signature.RetryCount = 3 - hl.Logger.Info("Sending task", "taskName", taskName, "currentTime", time.Now()) + hl.Logger.Info("Sending block level task", "taskName", taskName, "currentTime", time.Now(), "blockHeight", blockHeight) // send task _, err := hl.queueConnector.Server.SendTask(signature) if err != nil { - hl.Logger.Error("Error sending task", "taskName", taskName, "error", err) + hl.Logger.Error("Error sending block level task", "taskName", taskName, "blockHeight", blockHeight, "error", err) } } diff --git a/bridge/setu/listener/service.go b/bridge/setu/listener/service.go index 8b7e8da72..6ad8103e3 100644 --- a/bridge/setu/listener/service.go +++ b/bridge/setu/listener/service.go @@ -6,6 +6,7 @@ import ( "github.com/maticnetwork/heimdall/bridge/setu/util" "github.com/maticnetwork/heimdall/helper" "github.com/tendermint/tendermint/libs/common" + httpClient "github.com/tendermint/tendermint/rpc/client" ) const ( @@ -26,7 +27,7 @@ type ListenerService struct { } // NewListenerService returns new service object for listneing to events -func NewListenerService(cdc *codec.Codec, queueConnector *queue.QueueConnector) *ListenerService { +func NewListenerService(cdc *codec.Codec, queueConnector *queue.QueueConnector, httpClient *httpClient.HTTP) *ListenerService { // creating listener object listenerService := &ListenerService{} @@ -34,15 +35,15 @@ func NewListenerService(cdc *codec.Codec, queueConnector *queue.QueueConnector) listenerService.BaseService = *common.NewBaseService(logger, ListenerServiceStr, listenerService) rootchainListener := NewRootChainListener() - rootchainListener.BaseListener = *NewBaseListener(cdc, queueConnector, helper.GetMainClient(), RootChainListenerStr, rootchainListener) + rootchainListener.BaseListener = *NewBaseListener(cdc, queueConnector, httpClient, helper.GetMainClient(), RootChainListenerStr, rootchainListener) listenerService.listeners = append(listenerService.listeners, rootchainListener) maticchainListener := &MaticChainListener{} - maticchainListener.BaseListener = *NewBaseListener(cdc, queueConnector, helper.GetMaticClient(), MaticChainListenerStr, maticchainListener) + maticchainListener.BaseListener = *NewBaseListener(cdc, queueConnector, httpClient, helper.GetMaticClient(), MaticChainListenerStr, maticchainListener) listenerService.listeners = append(listenerService.listeners, maticchainListener) heimdallListener := &HeimdallListener{} - heimdallListener.BaseListener = *NewBaseListener(cdc, queueConnector, nil, HeimdallListenerStr, heimdallListener) + heimdallListener.BaseListener = *NewBaseListener(cdc, queueConnector, httpClient, nil, HeimdallListenerStr, heimdallListener) listenerService.listeners = append(listenerService.listeners, heimdallListener) return listenerService diff --git a/bridge/setu/processor/checkpoint.go b/bridge/setu/processor/checkpoint.go index 3171fe2e8..30091d785 100644 --- a/bridge/setu/processor/checkpoint.go +++ b/bridge/setu/processor/checkpoint.go @@ -148,8 +148,9 @@ func (cp *CheckpointProcessor) sendCheckpointToHeimdall(headerBlockStr string) ( // 1. check if i am the current proposer. // 2. check if this checkpoint has to be submitted to rootchain // 3. if so, create and broadcast checkpoint transaction to rootchain -func (cp *CheckpointProcessor) sendCheckpointToRootchain(eventBytes string, txHeight int64, txHash string) error { - cp.Logger.Info("Received sendCheckpointToRootchain request", "eventBytes", eventBytes, "txHeight", txHeight, "txHash", txHash) +func (cp *CheckpointProcessor) sendCheckpointToRootchain(eventBytes string, blockHeight int64) error { + + cp.Logger.Info("Received sendCheckpointToRootchain request", "eventBytes", eventBytes, "blockHeight", blockHeight) var event = sdk.StringEvent{} if err := json.Unmarshal([]byte(eventBytes), &event); err != nil { cp.Logger.Error("Error unmarshalling event from heimdall", "error", err) @@ -171,6 +172,8 @@ func (cp *CheckpointProcessor) sendCheckpointToRootchain(eventBytes string, txHe var startBlock uint64 var endBlock uint64 + var txHash string + for _, attr := range event.Attributes { if attr.Key == checkpointTypes.AttributeKeyStartBlock { startBlock, _ = strconv.ParseUint(attr.Value, 10, 64) @@ -178,6 +181,9 @@ func (cp *CheckpointProcessor) sendCheckpointToRootchain(eventBytes string, txHe if attr.Key == checkpointTypes.AttributeKeyEndBlock { endBlock, _ = strconv.ParseUint(attr.Value, 10, 64) } + if attr.Key == hmTypes.AttributeKeyTxHash { + txHash = attr.Value + } } checkpointContext, err := cp.getCheckpointContext() @@ -196,7 +202,7 @@ func (cp *CheckpointProcessor) sendCheckpointToRootchain(eventBytes string, txHe cp.Logger.Error("Error decoding txHash while sending checkpoint to rootchain", "txHash", txHash, "error", err) return err } - if err := cp.createAndSendCheckpointToRootchain(checkpointContext, startBlock, endBlock, txHeight, txHash); err != nil { + if err := cp.createAndSendCheckpointToRootchain(checkpointContext, startBlock, endBlock, blockHeight, txHash); err != nil { cp.Logger.Error("Error sending checkpoint to rootchain", "error", err) return err } diff --git a/bridge/setu/processor/clerk.go b/bridge/setu/processor/clerk.go index 040b5300b..3d89fb737 100644 --- a/bridge/setu/processor/clerk.go +++ b/bridge/setu/processor/clerk.go @@ -85,6 +85,7 @@ func (cp *ClerkProcessor) sendStateSyncedToHeimdall(eventName string, logBytes s "borChainId", chainParams.BorChainID, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -98,13 +99,17 @@ func (cp *ClerkProcessor) sendStateSyncedToHeimdall(eventName string, logBytes s "borChainId", chainParams.BorChainID, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) msg := clerkTypes.NewMsgEventRecord( hmTypes.BytesToHeimdallAddress(helper.GetAddress()), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, event.Id.Uint64(), + hmTypes.BytesToHeimdallAddress(event.ContractAddress.Bytes()), + event.Data, chainParams.BorChainID, ) @@ -120,7 +125,7 @@ func (cp *ClerkProcessor) sendStateSyncedToHeimdall(eventName string, logBytes s // HandleRecordConfirmation - handles clerk record confirmation event from heimdall. // 1. check if this record has to be broadcasted to maticchain // 2. create and broadcast record transaction to maticchain -func (cp *ClerkProcessor) sendDepositRecordToMatic(eventBytes string, txHeight int64, txHash string) (err error) { +func (cp *ClerkProcessor) sendDepositRecordToMatic(eventBytes string, blockHeight int64) (err error) { var event = sdk.StringEvent{} if err := json.Unmarshal([]byte(eventBytes), &event); err != nil { cp.Logger.Error("Error unmarshalling event from heimdall", "error", err) diff --git a/bridge/setu/processor/fee.go b/bridge/setu/processor/fee.go index 60e7550f8..a3c7de1a5 100644 --- a/bridge/setu/processor/fee.go +++ b/bridge/setu/processor/fee.go @@ -11,6 +11,8 @@ import ( "github.com/maticnetwork/heimdall/helper" topupTypes "github.com/maticnetwork/heimdall/topup/types" hmTypes "github.com/maticnetwork/heimdall/types" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // FeeProcessor - process fee related events @@ -57,9 +59,11 @@ func (fp *FeeProcessor) sendTopUpFeeToHeimdall(eventName string, logBytes string fp.Logger.Info("Ignoring task to send topup to heimdall as already processed", "event", eventName, "validatorId", event.ValidatorId, + "Signer", event.Signer, "Fee", event.Fee, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -67,13 +71,15 @@ func (fp *FeeProcessor) sendTopUpFeeToHeimdall(eventName string, logBytes string fp.Logger.Info("✅ sending topup to heimdall", "event", eventName, "validatorId", event.ValidatorId, + "Signer", event.Signer, "Fee", event.Fee, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // create msg checkpoint ack message - msg := topupTypes.NewMsgTopup(helper.GetFromAddress(fp.cliCtx), event.ValidatorId.Uint64(), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index)) + msg := topupTypes.NewMsgTopup(helper.GetFromAddress(fp.cliCtx), event.ValidatorId.Uint64(), hmTypes.BytesToHeimdallAddress(event.Signer.Bytes()), sdk.NewIntFromBigInt(event.Fee), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), vLog.BlockNumber) // return broadcast to heimdall if err := fp.txBroadcaster.BroadcastToHeimdall(msg); err != nil { diff --git a/bridge/setu/processor/staking.go b/bridge/setu/processor/staking.go index b49ae820c..390513a05 100644 --- a/bridge/setu/processor/staking.go +++ b/bridge/setu/processor/staking.go @@ -5,6 +5,7 @@ import ( "github.com/RichardKnop/machinery/v1/tasks" cliContext "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/maticnetwork/bor/accounts/abi" "github.com/maticnetwork/bor/core/types" "github.com/maticnetwork/heimdall/bridge/setu/util" @@ -76,6 +77,7 @@ func (sp *StakingProcessor) sendValidatorJoinToHeimdall(eventName string, logByt "SignerPubkey", hmTypes.NewPubKey(signerPubKey).String(), "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -100,15 +102,19 @@ func (sp *StakingProcessor) sendValidatorJoinToHeimdall(eventName string, logByt "SignerPubkey", hmTypes.NewPubKey(signerPubKey).String(), "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // msg validator exit msg := stakingTypes.NewMsgValidatorJoin( hmTypes.BytesToHeimdallAddress(helper.GetAddress()), event.ValidatorId.Uint64(), + event.ActivationEpoch.Uint64(), + sdk.NewIntFromBigInt(event.Amount), hmTypes.NewPubKey(signerPubKey), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, ) // return broadcast to heimdall @@ -140,6 +146,7 @@ func (sp *StakingProcessor) sendUnstakeInitToHeimdall(eventName string, logBytes "amount", event.Amount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -153,14 +160,17 @@ func (sp *StakingProcessor) sendUnstakeInitToHeimdall(eventName string, logBytes "amount", event.Amount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // msg validator exit msg := stakingTypes.NewMsgValidatorExit( hmTypes.BytesToHeimdallAddress(helper.GetAddress()), event.ValidatorId.Uint64(), + event.DeactivationEpoch.Uint64(), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, ) // return broadcast to heimdall @@ -190,6 +200,7 @@ func (sp *StakingProcessor) sendStakeUpdateToHeimdall(eventName string, logBytes "newAmount", event.NewAmount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -200,14 +211,17 @@ func (sp *StakingProcessor) sendStakeUpdateToHeimdall(eventName string, logBytes "newAmount", event.NewAmount, "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // msg validator exit msg := stakingTypes.NewMsgStakeUpdate( hmTypes.BytesToHeimdallAddress(helper.GetAddress()), event.ValidatorId.Uint64(), + sdk.NewIntFromBigInt(event.NewAmount), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, ) // return broadcast to heimdall @@ -244,6 +258,7 @@ func (sp *StakingProcessor) sendSignerChangeToHeimdall(eventName string, logByte "newSigner", event.NewSigner.Hex(), "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) return nil } @@ -256,6 +271,7 @@ func (sp *StakingProcessor) sendSignerChangeToHeimdall(eventName string, logByte "newSigner", event.NewSigner.Hex(), "txHash", hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), "logIndex", uint64(vLog.Index), + "blockNumber", vLog.BlockNumber, ) // signer change @@ -265,6 +281,7 @@ func (sp *StakingProcessor) sendSignerChangeToHeimdall(eventName string, logByte hmTypes.NewPubKey(newSignerPubKey), hmTypes.BytesToHeimdallHash(vLog.TxHash.Bytes()), uint64(vLog.Index), + vLog.BlockNumber, ) // return broadcast to heimdall diff --git a/clerk/client/cli/flags.go b/clerk/client/cli/flags.go index 34df16862..ce83da791 100644 --- a/clerk/client/cli/flags.go +++ b/clerk/client/cli/flags.go @@ -2,8 +2,11 @@ package cli const ( FlagProposerAddress = "proposer" + FlagContractAddress = "contract" FlagTxHash = "tx-hash" + FlagBlockNumber = "block-number" FlagLogIndex = "log-index" FlagRecordID = "id" + FlagData = "data" FlagBorChainId = "bor-chain-id" ) diff --git a/clerk/client/cli/tx.go b/clerk/client/cli/tx.go index d5563ffa3..3d5200cee 100644 --- a/clerk/client/cli/tx.go +++ b/clerk/client/cli/tx.go @@ -72,6 +72,12 @@ func CreateNewStateRecord(cdc *codec.Codec) *cobra.Command { return fmt.Errorf("record id cannot be empty") } + // get contract Addr + contractAddr := types.HexToHeimdallAddress(viper.GetString(FlagContractAddress)) + if contractAddr.Empty() { + return fmt.Errorf("contract Address cannot be empty") + } + // log index logIndexStr := viper.GetString(FlagLogIndex) if logIndexStr == "" { @@ -80,7 +86,18 @@ func CreateNewStateRecord(cdc *codec.Codec) *cobra.Command { logIndex, err := strconv.ParseUint(logIndexStr, 10, 64) if err != nil { - return fmt.Errorf("log index cannot be empty") + return fmt.Errorf("log index cannot be parsed") + } + + // log index + dataStr := viper.GetString(FlagData) + if dataStr == "" { + return fmt.Errorf("data cannot be empty") + } + + data := types.HexToHexBytes(dataStr) + if dataStr == "" { + return fmt.Errorf("data should be hex string") } // create new state record @@ -88,7 +105,10 @@ func CreateNewStateRecord(cdc *codec.Codec) *cobra.Command { proposer, types.HexToHeimdallHash(txHashStr), logIndex, + viper.GetUint64(FlagBlockNumber), recordID, + contractAddr, + data, borChainID, ) @@ -99,6 +119,10 @@ func CreateNewStateRecord(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(FlagLogIndex, "", "--log-index=") cmd.Flags().String(FlagRecordID, "", "--id=") cmd.Flags().String(FlagBorChainId, "", "--bor-chain-id=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + cmd.Flags().String(FlagContractAddress, "", "--contract-addr=") + cmd.Flags().String(FlagData, "", "--data=") + if err := cmd.MarkFlagRequired(FlagProposerAddress); err != nil { logger.Error("CreateNewStateRecord | MarkFlagRequired | FlagProposerAddress", "Error", err) } @@ -114,6 +138,15 @@ func CreateNewStateRecord(cdc *codec.Codec) *cobra.Command { if err := cmd.MarkFlagRequired(FlagBorChainId); err != nil { logger.Error("CreateNewStateRecord | MarkFlagRequired | FlagBorChainId", "Error", err) } + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("CreateNewStateRecord | MarkFlagRequired | FlagBlockNumber", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagContractAddress); err != nil { + logger.Error("CreateNewStateRecord | MarkFlagRequired | FlagContractAddress", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagData); err != nil { + logger.Error("CreateNewStateRecord | MarkFlagRequired | FlagData", "Error", err) + } return cmd } diff --git a/clerk/client/rest/tx.go b/clerk/client/rest/tx.go index b2a9b45df..46cecf646 100644 --- a/clerk/client/rest/tx.go +++ b/clerk/client/rest/tx.go @@ -24,10 +24,13 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { type AddRecordReq struct { BaseReq rest.BaseReq `json:"base_req"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - ID uint64 `json:"id"` - BorChainID string `json:"bor_chain_id"` + TxHash types.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` + ID uint64 `json:"id"` + ContractAddress string `json:"contract_address" yaml:"contract_address"` + BorChainID string `json:"bor_chain_id"` + Data string `json:"data"` } func newEventRecordHandler(cliCtx context.CLIContext) http.HandlerFunc { @@ -43,12 +46,18 @@ func newEventRecordHandler(cliCtx context.CLIContext) http.HandlerFunc { return } + // get ContractAddress + contractAddress := types.HexToHeimdallAddress(req.ContractAddress) + // create new msg msg := clerkTypes.NewMsgEventRecord( types.HexToHeimdallAddress(req.BaseReq.From), req.TxHash, req.LogIndex, + req.BlockNumber, req.ID, + contractAddress, + types.HexToHexBytes(req.Data), req.BorChainID, ) diff --git a/clerk/handler.go b/clerk/handler.go index 7d744fee2..f40b36d6f 100644 --- a/clerk/handler.go +++ b/clerk/handler.go @@ -1,8 +1,8 @@ package clerk import ( + "encoding/hex" "math/big" - "strconv" sdk "github.com/cosmos/cosmos-sdk/types" @@ -27,6 +27,16 @@ func NewHandler(k Keeper, contractCaller helper.IContractCaller) sdk.Handler { } func handleMsgEventRecord(ctx sdk.Context, msg types.MsgEventRecord, k Keeper, contractCaller helper.IContractCaller) sdk.Result { + + k.Logger(ctx).Debug("✅ Validating clerk msg", + "id", msg.ID, + "contract", msg.ContractAddress, + "data", hex.EncodeToString(msg.Data), + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + // check if event record exists if exists := k.HasEventRecord(ctx, msg.ID); exists { return types.ErrEventRecordAlreadySynced(k.Codespace()).Result() @@ -42,28 +52,9 @@ func handleMsgEventRecord(ctx sdk.Context, msg types.MsgEventRecord, k Keeper, c return common.ErrInvalidBorChainID(k.Codespace()).Result() } - // get confirmed tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if receipt == nil || err != nil { - return common.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } - - // get event log for topup - eventLog, err := contractCaller.DecodeStateSyncedEvent(chainParams.StateSenderAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return common.ErrInvalidMsg(k.Codespace(), "Unable to fetch log for txHash").Result() - } - - // check if message and event log matches - if eventLog.Id.Uint64() != msg.ID { - k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "stateIdFromTx", eventLog.Id) - return common.ErrInvalidMsg(k.Codespace(), "ID in message doesn't match with id in log. msgId %v stateIdFromTx %v", msg.ID, eventLog.Id).Result() - } - // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx is older @@ -72,37 +63,6 @@ func handleMsgEventRecord(ctx sdk.Context, msg types.MsgEventRecord, k Keeper, c return common.ErrOldTx(k.Codespace()).Result() } - // create event record - record := types.NewEventRecord( - msg.TxHash, - msg.LogIndex, - eventLog.Id.Uint64(), - hmTypes.BytesToHeimdallAddress(eventLog.ContractAddress.Bytes()), - eventLog.Data, - msg.ChainID, - ) - - // save event into state - if err := k.SetEventRecord(ctx, record); err != nil { - k.Logger(ctx).Error("Unable to update event record", "error", err, "id", msg.ID) - return types.ErrEventUpdate(k.Codespace()).Result() - } - - // save record sequence - k.SetRecordSequence(ctx, sequence.String()) - - // add events - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeRecord, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyRecordID, strconv.FormatUint(msg.ID, 10)), - sdk.NewAttribute(types.AttributeKeyRecordContract, eventLog.ContractAddress.String()), - sdk.NewAttribute(types.AttributeKeyRecordTxHash, msg.TxHash.String()), - sdk.NewAttribute(types.AttributeKeyRecordTxLogIndex, strconv.FormatUint(msg.LogIndex, 10)), - ), - }) - return sdk.Result{ Events: ctx.EventManager().Events(), } diff --git a/clerk/module.go b/clerk/module.go index 4c8e0d20c..c10166e92 100644 --- a/clerk/module.go +++ b/clerk/module.go @@ -15,6 +15,7 @@ import ( clerkRest "github.com/maticnetwork/heimdall/clerk/client/rest" "github.com/maticnetwork/heimdall/clerk/types" "github.com/maticnetwork/heimdall/helper" + hmTypes "github.com/maticnetwork/heimdall/types" hmModule "github.com/maticnetwork/heimdall/types/module" ) @@ -145,3 +146,12 @@ func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +func (am AppModule) NewSideTxHandler() hmTypes.SideTxHandler { + return NewSideTxHandler(am.keeper, am.contractCaller) +} + +// NewPostTxHandler side tx handler +func (am AppModule) NewPostTxHandler() hmTypes.PostTxHandler { + return NewPostTxHandler(am.keeper, am.contractCaller) +} diff --git a/clerk/side_handler.go b/clerk/side_handler.go new file mode 100644 index 000000000..c827eab85 --- /dev/null +++ b/clerk/side_handler.go @@ -0,0 +1,161 @@ +package clerk + +import ( + "bytes" + "math/big" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/maticnetwork/heimdall/clerk/types" + "github.com/maticnetwork/heimdall/common" + hmCommon "github.com/maticnetwork/heimdall/common" + "github.com/maticnetwork/heimdall/helper" + hmTypes "github.com/maticnetwork/heimdall/types" + abci "github.com/tendermint/tendermint/abci/types" + tmTypes "github.com/tendermint/tendermint/types" +) + +// NewSideTxHandler returns a side handler for "topup" type messages. +func NewSideTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.SideTxHandler { + return func(ctx sdk.Context, msg sdk.Msg) abci.ResponseDeliverSideTx { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgEventRecord: + return SideHandleMsgEventRecord(ctx, k, msg, contractCaller) + default: + return abci.ResponseDeliverSideTx{ + Code: uint32(sdk.CodeUnknownRequest), + } + } + } +} + +// NewPostTxHandler returns a side handler for "bank" type messages. +func NewPostTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.PostTxHandler { + return func(ctx sdk.Context, msg sdk.Msg, sideTxResult abci.SideTxResultType) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgEventRecord: + return PostHandleMsgEventRecord(ctx, k, msg, sideTxResult) + default: + errMsg := "Unrecognized EventRecord Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func SideHandleMsgEventRecord(ctx sdk.Context, k Keeper, msg types.MsgEventRecord, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + + k.Logger(ctx).Debug("✅ Validating External call for clerk msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get confirmed tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if receipt == nil || err != nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // get event log for topup + eventLog, err := contractCaller.DecodeStateSyncedEvent(chainParams.StateSenderAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64()) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check if message and event log matches + if eventLog.Id.Uint64() != msg.ID { + k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "stateIdFromTx", eventLog.Id) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if !bytes.Equal(eventLog.ContractAddress.Bytes(), msg.ContractAddress.Bytes()) { + k.Logger(ctx).Error( + "ContractAddress from event does not match with Msg ContractAddress", + "EventContractAddress", eventLog.ContractAddress.String(), + "MsgContractAddress", msg.ContractAddress.String(), + ) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if !bytes.Equal(eventLog.Data, msg.Data) { + k.Logger(ctx).Error( + "Data from event does not match with Msg Data", + "EventData", hmTypes.BytesToHexBytes(eventLog.Data), + "MsgData", hmTypes.BytesToHexBytes(msg.Data), + ) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + result.Result = abci.SideTxResultType_Yes + return +} + +func PostHandleMsgEventRecord(ctx sdk.Context, k Keeper, msg types.MsgEventRecord, sideTxResult abci.SideTxResultType) sdk.Result { + + // Skip handler if clerk is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new clerk since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting clerk state", "sideTxResult", sideTxResult) + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // create event record + record := types.NewEventRecord( + msg.TxHash, + msg.LogIndex, + msg.ID, + msg.ContractAddress, + msg.Data, + msg.ChainID, + ) + + // save event into state + if err := k.SetEventRecord(ctx, record); err != nil { + k.Logger(ctx).Error("Unable to update event record", "error", err, "id", msg.ID) + return types.ErrEventUpdate(k.Codespace()).Result() + } + + // save record sequence + k.SetRecordSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + // add events + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeRecord, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(types.AttributeKeyRecordTxLogIndex, strconv.FormatUint(msg.LogIndex, 10)), + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyRecordID, strconv.FormatUint(msg.ID, 10)), + sdk.NewAttribute(types.AttributeKeyRecordContract, msg.ContractAddress.String()), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/clerk/types/msg.go b/clerk/types/msg.go index 381046c14..a34dbe159 100644 --- a/clerk/types/msg.go +++ b/clerk/types/msg.go @@ -8,11 +8,14 @@ import ( // MsgEventRecord - state msg type MsgEventRecord struct { - From types.HeimdallAddress `json:"from"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - ID uint64 `json:"id"` - ChainID string `json:"bor_chain_id"` + From types.HeimdallAddress `json:"from"` + TxHash types.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` + ContractAddress types.HeimdallAddress `json:"contract_address"` + Data types.HexBytes `json:"data"` + ID uint64 `json:"id"` + ChainID string `json:"bor_chain_id"` } var _ sdk.Msg = MsgEventRecord{} @@ -22,15 +25,22 @@ func NewMsgEventRecord( from types.HeimdallAddress, txHash types.HeimdallHash, logIndex uint64, + blockNumber uint64, id uint64, + contractAddress types.HeimdallAddress, + data types.HexBytes, chainID string, + ) MsgEventRecord { return MsgEventRecord{ - From: from, - TxHash: txHash, - LogIndex: logIndex, - ID: id, - ChainID: chainID, + From: from, + TxHash: txHash, + LogIndex: logIndex, + BlockNumber: blockNumber, + ID: id, + ContractAddress: contractAddress, + Data: data, + ChainID: chainID, } } @@ -71,3 +81,8 @@ func (msg MsgEventRecord) GetTxHash() types.HeimdallHash { func (msg MsgEventRecord) GetLogIndex() uint64 { return msg.LogIndex } + +// GetSideSignBytes returns side sign bytes +func (msg MsgEventRecord) GetSideSignBytes() []byte { + return nil +} diff --git a/common/error.go b/common/error.go index a27560050..6d7f83ea8 100644 --- a/common/error.go +++ b/common/error.go @@ -31,18 +31,20 @@ const ( CodeDisCountinuousCheckpoint CodeType = 1510 CodeNoCheckpointBuffer CodeType = 1511 - CodeOldValidator CodeType = 2500 - CodeNoValidator CodeType = 2501 - CodeValSignerMismatch CodeType = 2502 - CodeValidatorExitDeny CodeType = 2503 - CodeValAlreadyUnbonded CodeType = 2504 - CodeSignerSynced CodeType = 2505 - CodeValSave CodeType = 2506 - CodeValAlreadyJoined CodeType = 2507 - CodeSignerUpdateError CodeType = 2508 - CodeNoConn CodeType = 2509 - CodeWaitFrConfirmation CodeType = 2510 - CodeValPubkeyMismatch CodeType = 2511 + CodeOldValidator CodeType = 2500 + CodeNoValidator CodeType = 2501 + CodeValSignerMismatch CodeType = 2502 + CodeValidatorExitDeny CodeType = 2503 + CodeValAlreadyUnbonded CodeType = 2504 + CodeSignerSynced CodeType = 2505 + CodeValSave CodeType = 2506 + CodeValAlreadyJoined CodeType = 2507 + CodeSignerUpdateError CodeType = 2508 + CodeNoConn CodeType = 2509 + CodeWaitFrConfirmation CodeType = 2510 + CodeValPubkeyMismatch CodeType = 2511 + CodeErrDecodeEvent CodeType = 2512 + CodeNoSignerChangeError CodeType = 2513 CodeSpanNotCountinuous CodeType = 3501 CodeUnableToFreezeSet CodeType = 3502 @@ -54,9 +56,13 @@ const ( CodeFetchCheckpointSigners CodeType = 4501 CodeErrComputeGenesisAccountRoot CodeType = 4503 CodeAccountRootMismatch CodeType = 4504 - CodeErrAccountRootHash CodeType = 4505 - CodeErrSetCheckpointBuffer CodeType = 4506 - CodeErrAddCheckpoint CodeType = 4507 + + CodeErrAccountRootHash CodeType = 4505 + CodeErrSetCheckpointBuffer CodeType = 4506 + CodeErrAddCheckpoint CodeType = 4507 + + CodeInvalidReceipt CodeType = 5501 + CodeSideTxValidationFailed CodeType = 5502 ) // -------- Invalid msg @@ -161,6 +167,10 @@ func ErrSignerUpdateError(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeSignerUpdateError, "Signer update error") } +func ErrNoSignerChange(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeNoSignerChangeError, "New signer same as old signer") +} + func ErrOldTx(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeSignerUpdateError, "Old txhash not allowed") } @@ -219,6 +229,10 @@ func ErrorSideTx(codespace sdk.CodespaceType, code CodeType) (res abci.ResponseD return } +func ErrSideTxValidation(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeSideTxValidationFailed, "External call majority validation failed. ") +} + // // Private methods // diff --git a/helper/query.go b/helper/query.go index 9f10c31f4..5aadfd9b5 100644 --- a/helper/query.go +++ b/helper/query.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" + abci "github.com/tendermint/tendermint/abci/types" httpClient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" tmTypes "github.com/tendermint/tendermint/types" @@ -254,6 +255,50 @@ func GetBlockWithClient(client *httpClient.HTTP, height int64) (*tmTypes.Block, } } +// GetBeginBlockEvents get block through per height +func GetBeginBlockEvents(client *httpClient.HTTP, height int64) ([]abci.Event, error) { + c, cancel := context.WithTimeout(context.Background(), CommitTimeout) + defer cancel() + + // get block using client + blockResults, err := client.BlockResults(&height) + if err == nil && blockResults != nil { + return blockResults.Results.BeginBlock.GetEvents(), nil + } + + // subscriber + subscriber := fmt.Sprintf("new-block-%v", height) + + // query for event + query := tmTypes.QueryForEvent(tmTypes.EventNewBlock).String() + + // register for the next event of this type + eventCh, err := client.Subscribe(c, subscriber, query) + if err != nil { + return nil, errors.Wrap(err, "failed to subscribe") + } + + // unsubscribe query + defer client.Unsubscribe(c, subscriber, query) + + for { + select { + case event := <-eventCh: + eventData := event.Data.(tmTypes.TMEventData) + switch t := eventData.(type) { + case tmTypes.EventDataNewBlock: + if t.Block.Height == height { + return t.ResultBeginBlock.GetEvents(), nil + } + default: + return nil, errors.New("timed out waiting for event") + } + case <-c.Done(): + return nil, errors.New("timed out waiting for event") + } + } +} + // FetchVotes fetches votes and extracts sigs from it func FetchVotes( client *httpClient.HTTP, @@ -287,7 +332,7 @@ func FetchSideTxSigs( sideTxData []byte, ) ([]byte, error) { // get block client - blockDetails, err := GetBlockWithClient(client, height+2) // side-tx takes 2 blocks for votes + blockDetails, err := GetBlockWithClient(client, height) if err != nil { return nil, err diff --git a/staking/client/cli/flags.go b/staking/client/cli/flags.go index a90341526..59e0e193f 100644 --- a/staking/client/cli/flags.go +++ b/staking/client/cli/flags.go @@ -1,17 +1,20 @@ package cli const ( - FlagProposerAddress = "proposer" - FlagValidatorAddress = "validator" - FlagValidatorID = "id" - FlagSignerAddress = "signer" - FlagSignerPubkey = "signer-pubkey" - FlagNewSignerPubkey = "new-pubkey" - FlagAmount = "staked-amount" - FlagAcceptDelegation = "accept-delegation" - FlagTxHash = "tx-hash" - FlagLogIndex = "log-index" - FlagFeeAmount = "fee-amount" + FlagProposerAddress = "proposer" + FlagValidatorAddress = "validator" + FlagValidatorID = "id" + FlagSignerAddress = "signer" + FlagSignerPubkey = "signer-pubkey" + FlagNewSignerPubkey = "new-pubkey" + FlagAmount = "staked-amount" + FlagAcceptDelegation = "accept-delegation" + FlagTxHash = "tx-hash" + FlagLogIndex = "log-index" + FlagActivationEpoch = "activation-epoch" + FlagDeactivationEpoch = "deactivation-epoch" + FlagFeeAmount = "fee-amount" + FlagBlockNumber = "block-number" FlagStartEpoch = "start-epoch" FlagEndEpoch = "end-epoch" diff --git a/staking/client/cli/tx.go b/staking/client/cli/tx.go index 15149486a..687c661b9 100644 --- a/staking/client/cli/tx.go +++ b/staking/client/cli/tx.go @@ -76,6 +76,12 @@ func SendValidatorJoinTx(cdc *codec.Codec) *cobra.Command { } pubkey := hmTypes.NewPubKey(pubkeyBytes) + // total stake amount + amount, ok := sdk.NewIntFromString(viper.GetString(FlagAmount)) + if !ok { + return errors.New("Invalid stake amount") + } + contractCallerObj, err := helper.NewContractCaller() if err != nil { return err @@ -95,7 +101,7 @@ func SendValidatorJoinTx(cdc *codec.Codec) *cobra.Command { abiObject := &contractCallerObj.StakingInfoABI eventName := "Staked" event := new(stakinginfo.StakinginfoStaked) - var logIndex uint + var logIndex uint64 found := false for _, vLog := range receipt.Logs { topic := vLog.Topics[0].Bytes() @@ -105,7 +111,7 @@ func SendValidatorJoinTx(cdc *codec.Codec) *cobra.Command { return err } - logIndex = vLog.Index + logIndex = uint64(vLog.Index) found = true break } @@ -123,9 +129,12 @@ func SendValidatorJoinTx(cdc *codec.Codec) *cobra.Command { msg := types.NewMsgValidatorJoin( proposer, event.ValidatorId.Uint64(), + viper.GetUint64(FlagActivationEpoch), + amount, pubkey, hmTypes.HexToHeimdallHash(txhash), - uint64(logIndex), + logIndex, + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -136,6 +145,22 @@ func SendValidatorJoinTx(cdc *codec.Codec) *cobra.Command { cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") cmd.Flags().String(FlagSignerPubkey, "", "--signer-pubkey=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + cmd.Flags().String(FlagAmount, "0", "--amount=") + cmd.Flags().Uint64(FlagActivationEpoch, 0, "--activation-epoch=") + + if err := cmd.MarkFlagRequired(FlagProposerAddress); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagProposerAddress", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagActivationEpoch); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagActivationEpoch", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagAmount); err != nil { + logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagAmount", "Error", err) + } if err := cmd.MarkFlagRequired(FlagSignerPubkey); err != nil { logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagSignerPubkey", "Error", err) } @@ -159,7 +184,7 @@ func SendValidatorExitTx(cdc *codec.Codec) *cobra.Command { proposer = helper.GetFromAddress(cliCtx) } - validator := viper.GetInt64(FlagValidatorID) + validator := viper.GetUint64(FlagValidatorID) if validator == 0 { return fmt.Errorf("validator ID cannot be 0") } @@ -172,9 +197,11 @@ func SendValidatorExitTx(cdc *codec.Codec) *cobra.Command { // draf msg msg := types.NewMsgValidatorExit( proposer, - uint64(validator), + validator, + viper.GetUint64(FlagDeactivationEpoch), hmTypes.HexToHeimdallHash(txhash), - uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagLogIndex), + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -183,9 +210,12 @@ func SendValidatorExitTx(cdc *codec.Codec) *cobra.Command { } cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().Int(FlagValidatorID, 0, "--id=") + cmd.Flags().Uint64(FlagValidatorID, 0, "--id=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") + cmd.Flags().Uint64(FlagDeactivationEpoch, 0, "--deactivation-epoch=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + if err := cmd.MarkFlagRequired(FlagValidatorID); err != nil { logger.Error("SendValidatorExitTx | MarkFlagRequired | FlagValidatorID", "Error", err) } @@ -195,6 +225,9 @@ func SendValidatorExitTx(cdc *codec.Codec) *cobra.Command { if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { logger.Error("SendValidatorExitTx | MarkFlagRequired | FlagLogIndex", "Error", err) } + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorExitTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } return cmd } @@ -213,7 +246,7 @@ func SendValidatorUpdateTx(cdc *codec.Codec) *cobra.Command { proposer = helper.GetFromAddress(cliCtx) } - validator := viper.GetInt64(FlagValidatorID) + validator := viper.GetUint64(FlagValidatorID) if validator == 0 { return fmt.Errorf("validator ID cannot be 0") } @@ -236,10 +269,11 @@ func SendValidatorUpdateTx(cdc *codec.Codec) *cobra.Command { msg := types.NewMsgSignerUpdate( proposer, - uint64(validator), + validator, pubkey, hmTypes.HexToHeimdallHash(txhash), - uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagLogIndex), + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -248,10 +282,18 @@ func SendValidatorUpdateTx(cdc *codec.Codec) *cobra.Command { } cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().Int(FlagValidatorID, 0, "--id=") + cmd.Flags().Uint64(FlagValidatorID, 0, "--id=") cmd.Flags().String(FlagNewSignerPubkey, "", "--new-pubkey=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + + if err := cmd.MarkFlagRequired(FlagProposerAddress); err != nil { + logger.Error("SendValidatorUpdateTx | MarkFlagRequired | FlagProposerAddress", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagValidatorID); err != nil { + logger.Error("SendValidatorUpdateTx | MarkFlagRequired | FlagValidatorID", "Error", err) + } if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { logger.Error("SendValidatorUpdateTx | MarkFlagRequired | FlagTxHash", "Error", err) } @@ -261,6 +303,9 @@ func SendValidatorUpdateTx(cdc *codec.Codec) *cobra.Command { if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { logger.Error("SendValidatorUpdateTx | MarkFlagRequired | FlagLogIndex", "Error", err) } + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorUpdateTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } return cmd } @@ -279,7 +324,7 @@ func SendValidatorStakeUpdateTx(cdc *codec.Codec) *cobra.Command { proposer = helper.GetFromAddress(cliCtx) } - validator := viper.GetInt64(FlagValidatorID) + validator := viper.GetUint64(FlagValidatorID) if validator == 0 { return fmt.Errorf("validator ID cannot be 0") } @@ -289,11 +334,19 @@ func SendValidatorStakeUpdateTx(cdc *codec.Codec) *cobra.Command { return fmt.Errorf("transaction hash has to be supplied") } + // total stake amount + amount, ok := sdk.NewIntFromString(viper.GetString(FlagAmount)) + if !ok { + return errors.New("Invalid new stake amount") + } + msg := types.NewMsgStakeUpdate( proposer, - uint64(validator), + validator, + amount, hmTypes.HexToHeimdallHash(txhash), - uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagLogIndex), + viper.GetUint64(FlagBlockNumber), ) // broadcast messages @@ -302,15 +355,28 @@ func SendValidatorStakeUpdateTx(cdc *codec.Codec) *cobra.Command { } cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().Int(FlagValidatorID, 0, "--id=") + cmd.Flags().Uint64(FlagValidatorID, 0, "--id=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().String(FlagAmount, "", "--amount=") + cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { logger.Error("SendValidatorStakeUpdateTx | MarkFlagRequired | FlagTxHash", "Error", err) } if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { logger.Error("SendValidatorStakeUpdateTx | MarkFlagRequired | FlagLogIndex", "Error", err) } + if err := cmd.MarkFlagRequired(FlagValidatorID); err != nil { + logger.Error("SendValidatorStakeUpdateTx | MarkFlagRequired | FlagValidatorID", "Error", err) + } + + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + logger.Error("SendValidatorStakeUpdateTx | MarkFlagRequired | FlagBlockNumber", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagAmount); err != nil { + logger.Error("SendValidatorStakeUpdateTx | MarkFlagRequired | FlagAmount", "Error", err) + } return cmd } diff --git a/staking/client/rest/tx.go b/staking/client/rest/tx.go index b9c640e99..7a3c2ae54 100644 --- a/staking/client/rest/tx.go +++ b/staking/client/rest/tx.go @@ -28,10 +28,13 @@ type ( AddValidatorReq struct { BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - SignerPubKey hmTypes.PubKey `json:"pubKey"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + ID uint64 `json:"ID"` + ActivationEpoch uint64 `json:"activationEpoch"` + Amount string `json:"amount"` + SignerPubKey hmTypes.PubKey `json:"pubKey"` + TxHash string `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } // UpdateSignerReq update validator signer request object @@ -42,24 +45,29 @@ type ( NewSignerPubKey hmTypes.PubKey `json:"pubKey"` TxHash string `json:"tx_hash"` LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } // UpdateValidatorStakeReq update validator stake request object UpdateValidatorStakeReq struct { BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + ID uint64 `json:"ID"` + Amount string `json:"amount"` + TxHash string `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } // RemoveValidatorReq remove validator request object RemoveValidatorReq struct { BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + ID uint64 `json:"ID"` + DeactivationEpoch uint64 `json:"deactivationEpoch"` + TxHash string `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } ) @@ -76,13 +84,21 @@ func newValidatorJoinHandler(cliCtx context.CLIContext) http.HandlerFunc { return } + amount, ok := sdk.NewIntFromString(req.Amount) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid amount") + } + // create new msg msg := types.NewMsgValidatorJoin( hmTypes.HexToHeimdallAddress(req.BaseReq.From), req.ID, + req.ActivationEpoch, + amount, req.SignerPubKey, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) // send response @@ -107,8 +123,10 @@ func newValidatorExitHandler(cliCtx context.CLIContext) http.HandlerFunc { msg := types.NewMsgValidatorExit( hmTypes.HexToHeimdallAddress(req.BaseReq.From), req.ID, + req.DeactivationEpoch, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) // send response @@ -136,6 +154,7 @@ func newValidatorUpdateHandler(cliCtx context.CLIContext) http.HandlerFunc { req.NewSignerPubKey, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) // send response @@ -156,12 +175,19 @@ func newValidatorStakeUpdateHandler(cliCtx context.CLIContext) http.HandlerFunc return } + amount, ok := sdk.NewIntFromString(req.Amount) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid amount") + } + // create msg validator update msg := types.NewMsgStakeUpdate( hmTypes.HexToHeimdallAddress(req.BaseReq.From), req.ID, + amount, hmTypes.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) // send response diff --git a/staking/handler.go b/staking/handler.go index d9180c84a..527c562a1 100644 --- a/staking/handler.go +++ b/staking/handler.go @@ -4,11 +4,9 @@ import ( "bytes" "fmt" "math/big" - "strconv" sdk "github.com/cosmos/cosmos-sdk/types" - authTypes "github.com/maticnetwork/heimdall/auth/types" hmCommon "github.com/maticnetwork/heimdall/common" "github.com/maticnetwork/heimdall/helper" "github.com/maticnetwork/heimdall/staking/types" @@ -30,60 +28,28 @@ func NewHandler(k Keeper, contractCaller helper.IContractCaller) sdk.Handler { case types.MsgStakeUpdate: return HandleMsgStakeUpdate(ctx, msg, k, contractCaller) default: - return sdk.ErrTxDecode("Invalid message in checkpoint module").Result() + return sdk.ErrTxDecode("Invalid message in staking module").Result() } } } // HandleMsgValidatorJoin msg validator join func HandleMsgValidatorJoin(ctx sdk.Context, msg types.MsgValidatorJoin, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - k.Logger(ctx).Info("Handling new validator join", "msg", msg) - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if err != nil || receipt == nil { - return hmCommon.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } - // decode validator join event - eventLog, err := contractCaller.DecodeValidatorJoinEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - return hmCommon.ErrInvalidMsg(k.Codespace(), "Unable to fetch logs for txHash").Result() - } + k.Logger(ctx).Debug("✅ Validating validator join msg", + "validatorId", msg.ID, + "activationEpoch", msg.ActivationEpoch, + "amount", msg.Amount, + "SignerPubkey", msg.SignerPubKey.String(), + "txHash", msg.TxHash, + "logIndex", msg.LogIndex, + "blockNumber", msg.BlockNumber, + ) // Generate PubKey from Pubkey in message and signer pubkey := msg.SignerPubKey signer := pubkey.Address() - // check signer pubkey in message corresponds - if !bytes.Equal(pubkey.Bytes()[1:], eventLog.SignerPubkey) { - k.Logger(ctx).Error( - "Signer Pubkey does not match", - "msgValidator", pubkey.String(), - "mainchainValidator", hmTypes.BytesToHexBytes(eventLog.SignerPubkey), - ) - return hmCommon.ErrValSignerPubKeyMismatch(k.Codespace()).Result() - } - - // check signer corresponding to pubkey matches signer from event - if !bytes.Equal(signer.Bytes(), eventLog.Signer.Bytes()) { - k.Logger(ctx).Error( - "Signer Address from Pubkey does not match", - "Validator", signer.String(), - "mainchainValidator", eventLog.Signer.Hex(), - ) - return hmCommon.ErrValSignerMismatch(k.Codespace()).Result() - } - - // check msg id - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) - return hmCommon.ErrInvalidMsg(k.Codespace(), "ID in message doesn't match with id in log. msgId %v validatorIdFromTx %v", msg.ID, eventLog.ValidatorId).Result() - } - // Check if validator has been validator before if _, ok := k.GetSignerFromValidatorID(ctx, msg.ID); ok { k.Logger(ctx).Error("Validator has been validator before, cannot join with same ID", "validatorId", msg.ID) @@ -96,26 +62,15 @@ func HandleMsgValidatorJoin(ctx sdk.Context, msg types.MsgValidatorJoin, k Keepe return hmCommon.ErrValidatorAlreadyJoined(k.Codespace()).Result() } - // get voting power from amount - votingPower, err := helper.GetPowerFromAmount(eventLog.Amount) + // validate voting power + _, err = helper.GetPowerFromAmount(msg.Amount.BigInt()) if err != nil { - return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid amount %v for validator %v", eventLog.Amount, msg.ID)).Result() - } - - // create new validator - newValidator := hmTypes.Validator{ - ID: msg.ID, - StartEpoch: eventLog.ActivationEpoch.Uint64(), - EndEpoch: 0, - VotingPower: votingPower.Int64(), - PubKey: pubkey, - Signer: hmTypes.BytesToHeimdallAddress(signer.Bytes()), - LastUpdated: "", + return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid amount %v for validator %v", msg.Amount, msg.ID)).Result() } // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx is older @@ -124,29 +79,6 @@ func HandleMsgValidatorJoin(ctx sdk.Context, msg types.MsgValidatorJoin, k Keepe return hmCommon.ErrOldTx(k.Codespace()).Result() } - // update last updated - newValidator.LastUpdated = sequence.String() - - // add validator to store - k.Logger(ctx).Debug("Adding new validator to state", "validator", newValidator.String()) - err = k.AddValidator(ctx, newValidator) - if err != nil { - k.Logger(ctx).Error("Unable to add validator to state", "error", err, "validator", newValidator.String()) - return hmCommon.ErrValidatorSave(k.Codespace()).Result() - } - - // save staking sequence - k.SetStakingSequence(ctx, sequence.String()) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeValidatorJoin, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyValidatorID, strconv.FormatUint(newValidator.ID.Uint64(), 10)), - sdk.NewAttribute(types.AttributeKeySigner, newValidator.Signer.String()), - ), - }) - return sdk.Result{ Events: ctx.EventManager().Events(), } @@ -154,39 +86,25 @@ func HandleMsgValidatorJoin(ctx sdk.Context, msg types.MsgValidatorJoin, k Keepe // HandleMsgStakeUpdate handles stake update message func HandleMsgStakeUpdate(ctx sdk.Context, msg types.MsgStakeUpdate, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - k.Logger(ctx).Debug("Handling stake update", "Validator", msg.ID) - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if err != nil || receipt == nil { - return hmCommon.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } - - eventLog, err := contractCaller.DecodeValidatorStakeUpdateEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrInvalidMsg(k.Codespace(), "Unable to fetch logs for txHash").Result() - } - - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) - return hmCommon.ErrInvalidMsg(k.Codespace(), "ID in message doesn't match with id in log. msgId %v validatorIdFromTx %v", msg.ID, eventLog.ValidatorId).Result() - } + k.Logger(ctx).Debug("✅ Validating stake update msg", + "validatorID", msg.ID, + "newAmount", msg.NewAmount, + "txHash", msg.TxHash, + "logIndex", msg.LogIndex, + "blockNumber", msg.BlockNumber, + ) // pull validator from store - validator, ok := k.GetValidatorFromValID(ctx, msg.ID) + _, ok := k.GetValidatorFromValID(ctx, msg.ID) if !ok { k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) return hmCommon.ErrNoValidator(k.Codespace()).Result() } // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx is older @@ -195,34 +113,11 @@ func HandleMsgStakeUpdate(ctx sdk.Context, msg types.MsgStakeUpdate, k Keeper, c return hmCommon.ErrOldTx(k.Codespace()).Result() } - // update last updated - validator.LastUpdated = sequence.String() - // set validator amount - p, err := helper.GetPowerFromAmount(eventLog.NewAmount) + _, err := helper.GetPowerFromAmount(msg.NewAmount.BigInt()) if err != nil { - return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid amount %v for validator %v", eventLog.NewAmount, msg.ID)).Result() + return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid newamount %v for validator %v", msg.NewAmount, msg.ID)).Result() } - validator.VotingPower = p.Int64() - - // save validator - err = k.AddValidator(ctx, validator) - if err != nil { - k.Logger(ctx).Error("Unable to update signer", "error", err, "ValidatorID", validator.ID) - return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() - } - - // save staking sequence - k.SetStakingSequence(ctx, sequence.String()) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeStakeUpdate, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyValidatorID, strconv.FormatUint(validator.ID.Uint64(), 10)), - sdk.NewAttribute(types.AttributeKeyUpdatedAt, validator.LastUpdated), - ), - }) return sdk.Result{ Events: ctx.EventManager().Events(), @@ -231,52 +126,28 @@ func HandleMsgStakeUpdate(ctx sdk.Context, msg types.MsgStakeUpdate, k Keeper, c // HandleMsgSignerUpdate handles signer update message func HandleMsgSignerUpdate(ctx sdk.Context, msg types.MsgSignerUpdate, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - k.Logger(ctx).Debug("Handling signer update", "Validator", msg.ID, "Signer", msg.NewSignerPubKey.Address()) - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if err != nil || receipt == nil { - return hmCommon.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } + + k.Logger(ctx).Debug("✅ Validating signer update msg", + "validatorID", msg.ID, + "NewSignerPubkey", msg.NewSignerPubKey.String(), + "txHash", msg.TxHash, + "logIndex", msg.LogIndex, + "blockNumber", msg.BlockNumber, + ) newPubKey := msg.NewSignerPubKey newSigner := newPubKey.Address() - eventLog, err := contractCaller.DecodeSignerUpdateEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrInvalidMsg(k.Codespace(), "Unable to fetch signer update log for txHash").Result() - } - - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) - return hmCommon.ErrInvalidMsg(k.Codespace(), "ID in message doesn't match with id in log. msgId %v validatorIdFromTx %v", msg.ID, eventLog.ValidatorId).Result() - } - - if !bytes.Equal(eventLog.SignerPubkey, newPubKey.Bytes()[1:]) { - k.Logger(ctx).Error("Newsigner pubkey in txhash and msg dont match", "msgPubKey", newPubKey.String(), "pubkeyTx", hmTypes.NewPubKey(eventLog.SignerPubkey[:]).String()) - return hmCommon.ErrInvalidMsg(k.Codespace(), "Newsigner pubkey in txhash and msg dont match").Result() - } - - // check signer corresponding to pubkey matches signer from event - if !bytes.Equal(newSigner.Bytes(), eventLog.NewSigner.Bytes()) { - k.Logger(ctx).Error("Signer Address from Pubkey does not match", "Validator", newSigner.String(), "mainchainValidator", eventLog.NewSigner.Hex()) - return hmCommon.ErrValSignerMismatch(k.Codespace()).Result() - } - // pull validator from store validator, ok := k.GetValidatorFromValID(ctx, msg.ID) if !ok { k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) return hmCommon.ErrNoValidator(k.Codespace()).Result() } - oldValidator := validator.Copy() // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx is older @@ -285,69 +156,11 @@ func HandleMsgSignerUpdate(ctx sdk.Context, msg types.MsgSignerUpdate, k Keeper, return hmCommon.ErrOldTx(k.Codespace()).Result() } - // update last udpated - validator.LastUpdated = sequence.String() - - // check if we are actually updating signer - if !bytes.Equal(newSigner.Bytes(), validator.Signer.Bytes()) { - // Update signer in prev Validator - validator.Signer = hmTypes.HeimdallAddress(newSigner) - validator.PubKey = newPubKey - k.Logger(ctx).Debug("Updating new signer", "signer", newSigner.String(), "oldSigner", oldValidator.Signer.String(), "validatorID", msg.ID) - } - - k.Logger(ctx).Debug("Removing old validator", "validator", oldValidator.String()) - - // remove old validator from HM - oldValidator.EndEpoch = k.moduleCommunicator.GetACKCount(ctx) - - // remove old validator from TM - oldValidator.VotingPower = 0 - // updated last - oldValidator.LastUpdated = sequence.String() - - // save old validator - if err := k.AddValidator(ctx, *oldValidator); err != nil { - k.Logger(ctx).Error("Unable to update signer", "error", err, "validatorId", validator.ID) - return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() - } - - // adding new validator - k.Logger(ctx).Debug("Adding new validator", "validator", validator.String()) - - // save validator - err = k.AddValidator(ctx, validator) - if err != nil { - k.Logger(ctx).Error("Unable to update signer", "error", err, "ValidatorID", validator.ID) - return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() - } - - // save staking sequence - k.SetStakingSequence(ctx, sequence.String()) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeSignerUpdate, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyValidatorID, validator.ID.String()), - sdk.NewAttribute(types.AttributeKeyUpdatedAt, validator.LastUpdated), - ), - }) - - // - // Move heimdall fee to new signer - // - - // check if fee is already withdrawn - coins := k.moduleCommunicator.GetCoins(ctx, oldValidator.Signer) - maticBalance := coins.AmountOf(authTypes.FeeToken) - if !maticBalance.IsZero() { - k.Logger(ctx).Info("Transferring fee", "from", oldValidator.Signer.String(), "to", validator.Signer.String(), "balance", maticBalance.String()) - maticCoins := sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: maticBalance}} - if err := k.moduleCommunicator.SendCoins(ctx, oldValidator.Signer, validator.Signer, maticCoins); err != nil { - k.Logger(ctx).Info("Error while transferring fee", "from", oldValidator.Signer.String(), "to", validator.Signer.String(), "balance", maticBalance.String()) - return err.Result() - } + // check if new signer address is same as existing signer + if bytes.Equal(newSigner.Bytes(), validator.Signer.Bytes()) { + // No signer change + k.Logger(ctx).Error("NewSigner same as OldSigner.") + return hmCommon.ErrNoSignerChange(k.Codespace()).Result() } return sdk.Result{ @@ -357,29 +170,14 @@ func HandleMsgSignerUpdate(ctx sdk.Context, msg types.MsgSignerUpdate, k Keeper, // HandleMsgValidatorExit handle msg validator exit func HandleMsgValidatorExit(ctx sdk.Context, msg types.MsgValidatorExit, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - k.Logger(ctx).Info("Handling validator exit", "ValidatorID", msg.ID) - - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if err != nil || receipt == nil { - return hmCommon.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } - - // decode validator exit - eventLog, err := contractCaller.DecodeValidatorExitEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrInvalidMsg(k.Codespace(), "Unable to fetch unstake log for txHash").Result() - } - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) - return hmCommon.ErrInvalidMsg(k.Codespace(), "ID in message doesn't match with id in log. msgId %v validatorIdFromTx %v", msg.ID, eventLog.ValidatorId).Result() - } + k.Logger(ctx).Debug("✅ Validating validator exit msg", + "validatorID", msg.ID, + "deactivatonEpoch", msg.DeactivationEpoch, + "txHash", msg.TxHash, + "logIndex", msg.LogIndex, + "blockNumber", msg.BlockNumber, + ) validator, ok := k.GetValidatorFromValID(ctx, msg.ID) if !ok { @@ -394,11 +192,9 @@ func HandleMsgValidatorExit(ctx sdk.Context, msg types.MsgValidatorExit, k Keepe return hmCommon.ErrValUnbonded(k.Codespace()).Result() } - // set end epoch - validator.EndEpoch = eventLog.DeactivationEpoch.Uint64() - // sequence id - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx is older @@ -407,26 +203,6 @@ func HandleMsgValidatorExit(ctx sdk.Context, msg types.MsgValidatorExit, k Keepe return hmCommon.ErrOldTx(k.Codespace()).Result() } - // update last updated - validator.LastUpdated = sequence.String() - - // Add deactivation time for validator - if err := k.AddValidator(ctx, validator); err != nil { - k.Logger(ctx).Error("Error while setting deactivation epoch to validator", "error", err, "validatorID", validator.ID.String()) - return hmCommon.ErrValidatorNotDeactivated(k.Codespace()).Result() - } - - // save staking sequence - k.SetStakingSequence(ctx, sequence.String()) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeValidatorExit, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyValidatorID, validator.ID.String()), - ), - }) - return sdk.Result{ Events: ctx.EventManager().Events(), } diff --git a/staking/module.go b/staking/module.go index c81051543..6e4117a49 100644 --- a/staking/module.go +++ b/staking/module.go @@ -14,6 +14,7 @@ import ( stakingCli "github.com/maticnetwork/heimdall/staking/client/cli" stakingRest "github.com/maticnetwork/heimdall/staking/client/rest" "github.com/maticnetwork/heimdall/staking/types" + hmTypes "github.com/maticnetwork/heimdall/types" hmModule "github.com/maticnetwork/heimdall/types/module" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -179,3 +180,12 @@ func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +func (am AppModule) NewSideTxHandler() hmTypes.SideTxHandler { + return NewSideTxHandler(am.keeper, am.contractCaller) +} + +// NewPostTxHandler side tx handler +func (am AppModule) NewPostTxHandler() hmTypes.PostTxHandler { + return NewPostTxHandler(am.keeper, am.contractCaller) +} diff --git a/staking/side_handler.go b/staking/side_handler.go new file mode 100644 index 000000000..55f7f2b46 --- /dev/null +++ b/staking/side_handler.go @@ -0,0 +1,585 @@ +package staking + +import ( + "bytes" + "fmt" + "math/big" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + authTypes "github.com/maticnetwork/heimdall/auth/types" + "github.com/maticnetwork/heimdall/common" + hmCommon "github.com/maticnetwork/heimdall/common" + "github.com/maticnetwork/heimdall/helper" + "github.com/maticnetwork/heimdall/staking/types" + hmTypes "github.com/maticnetwork/heimdall/types" + abci "github.com/tendermint/tendermint/abci/types" + tmTypes "github.com/tendermint/tendermint/types" +) + +// NewSideTxHandler returns a side handler for "staking" type messages. +func NewSideTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.SideTxHandler { + return func(ctx sdk.Context, msg sdk.Msg) abci.ResponseDeliverSideTx { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case types.MsgValidatorJoin: + return SideHandleMsgValidatorJoin(ctx, msg, k, contractCaller) + case types.MsgValidatorExit: + return SideHandleMsgValidatorExit(ctx, msg, k, contractCaller) + case types.MsgSignerUpdate: + return SideHandleMsgSignerUpdate(ctx, msg, k, contractCaller) + case types.MsgStakeUpdate: + return SideHandleMsgStakeUpdate(ctx, msg, k, contractCaller) + default: + return abci.ResponseDeliverSideTx{ + Code: uint32(sdk.CodeUnknownRequest), + } + } + } +} + +// NewPostTxHandler returns a side handler for "bank" type messages. +func NewPostTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.PostTxHandler { + return func(ctx sdk.Context, msg sdk.Msg, sideTxResult abci.SideTxResultType) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case types.MsgValidatorJoin: + return PostHandleMsgValidatorJoin(ctx, k, msg, sideTxResult) + case types.MsgValidatorExit: + return PostHandleMsgValidatorExit(ctx, k, msg, sideTxResult) + case types.MsgSignerUpdate: + return PostHandleMsgSignerUpdate(ctx, k, msg, sideTxResult) + case types.MsgStakeUpdate: + return PostHandleMsgStakeUpdate(ctx, k, msg, sideTxResult) + default: + errMsg := "Unrecognized Staking Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// SideHandleMsgValidatorJoin side msg validator join +func SideHandleMsgValidatorJoin(ctx sdk.Context, msg types.MsgValidatorJoin, k Keeper, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + + k.Logger(ctx).Debug("✅ Validating External call for validator join msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // decode validator join event + eventLog, err := contractCaller.DecodeValidatorJoinEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + // Generate PubKey from Pubkey in message and signer + pubkey := msg.SignerPubKey + signer := pubkey.Address() + + // check signer pubkey in message corresponds + if !bytes.Equal(pubkey.Bytes()[1:], eventLog.SignerPubkey) { + k.Logger(ctx).Error( + "Signer Pubkey does not match", + "msgValidator", pubkey.String(), + "mainchainValidator", hmTypes.BytesToHexBytes(eventLog.SignerPubkey), + ) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check signer corresponding to pubkey matches signer from event + if !bytes.Equal(signer.Bytes(), eventLog.Signer.Bytes()) { + k.Logger(ctx).Error( + "Signer Address from Pubkey does not match", + "Validator", signer.String(), + "mainchainValidator", eventLog.Signer.Hex(), + ) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check msg id + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check ActivationEpoch + if eventLog.ActivationEpoch.Uint64() != msg.ActivationEpoch { + k.Logger(ctx).Error("ActivationEpoch in message doesn't match with ActivationEpoch in log", "msgActivationEpoch", msg.ActivationEpoch, "activationEpochFromTx", eventLog.ActivationEpoch.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check Amount + if eventLog.Amount.Cmp(msg.Amount.BigInt()) != 0 { + k.Logger(ctx).Error("Amount in message doesn't match Amount in event logs", "MsgAmount", msg.Amount, "AmountFromEvent", eventLog.Amount) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check Blocknumber + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for validator join msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// SideHandleMsgStakeUpdate handles stake update message +func SideHandleMsgStakeUpdate(ctx sdk.Context, msg types.MsgStakeUpdate, k Keeper, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for stake update msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + eventLog, err := contractCaller.DecodeValidatorStakeUpdateEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check Amount + if eventLog.NewAmount.Cmp(msg.NewAmount.BigInt()) != 0 { + k.Logger(ctx).Error("NewAmount in message doesn't match NewAmount in event logs", "MsgNewAmount", msg.NewAmount, "NewAmountFromEvent", eventLog.NewAmount) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for stake update msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// SideHandleMsgSignerUpdate handles signer update message +func SideHandleMsgSignerUpdate(ctx sdk.Context, msg types.MsgSignerUpdate, k Keeper, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for signer update msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + newPubKey := msg.NewSignerPubKey + newSigner := newPubKey.Address() + + eventLog, err := contractCaller.DecodeSignerUpdateEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if bytes.Compare(eventLog.SignerPubkey, newPubKey.Bytes()[1:]) != 0 { + k.Logger(ctx).Error("Newsigner pubkey in txhash and msg dont match", "msgPubKey", newPubKey.String(), "pubkeyTx", hmTypes.NewPubKey(eventLog.SignerPubkey[:]).String()) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + // check signer corresponding to pubkey matches signer from event + if !bytes.Equal(newSigner.Bytes(), eventLog.NewSigner.Bytes()) { + k.Logger(ctx).Error("Signer Address from Pubkey does not match", "Validator", newSigner.String(), "mainchainValidator", eventLog.NewSigner.Hex()) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for signer update msg") + result.Result = abci.SideTxResultType_Yes + return +} + +// SideHandleMsgValidatorExit handle side msg validator exit +func SideHandleMsgValidatorExit(ctx sdk.Context, msg types.MsgValidatorExit, k Keeper, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + k.Logger(ctx).Debug("✅ Validating External call for validator exit msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // decode validator exit + eventLog, err := contractCaller.DecodeValidatorExitEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match with id in log", "msgId", msg.ID, "validatorIdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.DeactivationEpoch.Uint64() != msg.DeactivationEpoch { + k.Logger(ctx).Error("DeactivationEpoch in message doesn't match with deactivationEpoch in log", "msgDeactivationEpoch", msg.DeactivationEpoch, "deactivationEpochFromTx", eventLog.DeactivationEpoch.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for validator exit msg") + result.Result = abci.SideTxResultType_Yes + return +} + +/* + Post Handlers - update the state of the tx +**/ + +// PostHandleMsgValidatorJoin msg validator join +func PostHandleMsgValidatorJoin(ctx sdk.Context, k Keeper, msg types.MsgValidatorJoin, sideTxResult abci.SideTxResultType) sdk.Result { + + // Skip handler if validator join is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new validator-join since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Adding validator to state", "sideTxResult", sideTxResult) + + // Generate PubKey from Pubkey in message and signer + pubkey := msg.SignerPubKey + signer := pubkey.Address() + + // get voting power from amount + votingPower, err := helper.GetPowerFromAmount(msg.Amount.BigInt()) + if err != nil { + return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid amount %v for validator %v", msg.Amount, msg.ID)).Result() + } + + // create new validator + newValidator := hmTypes.Validator{ + ID: msg.ID, + StartEpoch: msg.ActivationEpoch, + EndEpoch: 0, + VotingPower: votingPower.Int64(), + PubKey: pubkey, + Signer: hmTypes.BytesToHeimdallAddress(signer.Bytes()), + LastUpdated: "", + } + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // update last updated + newValidator.LastUpdated = sequence.String() + + // add validator to store + k.Logger(ctx).Debug("Adding new validator to state", "validator", newValidator.String()) + err = k.AddValidator(ctx, newValidator) + if err != nil { + k.Logger(ctx).Error("Unable to add validator to state", "error", err, "validator", newValidator.String()) + return hmCommon.ErrValidatorSave(k.Codespace()).Result() + } + + // save staking sequence + k.SetStakingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeValidatorJoin, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeyTxLogIndex, strconv.FormatUint(msg.LogIndex, 10)), + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyValidatorID, strconv.FormatUint(newValidator.ID.Uint64(), 10)), + sdk.NewAttribute(types.AttributeKeySigner, newValidator.Signer.String()), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// PostHandleMsgStakeUpdate handles stake update message +func PostHandleMsgStakeUpdate(ctx sdk.Context, k Keeper, msg types.MsgStakeUpdate, sideTxResult abci.SideTxResultType) sdk.Result { + // Skip handler if stakeUpdate is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping stake update since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Updating validator stake", "sideTxResult", sideTxResult) + + // pull validator from store + validator, ok := k.GetValidatorFromValID(ctx, msg.ID) + if !ok { + k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) + return hmCommon.ErrNoValidator(k.Codespace()).Result() + } + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // update last updated + validator.LastUpdated = sequence.String() + + // set validator amount + p, err := helper.GetPowerFromAmount(msg.NewAmount.BigInt()) + if err != nil { + return hmCommon.ErrInvalidMsg(k.Codespace(), fmt.Sprintf("Invalid amount %v for validator %v", msg.NewAmount, msg.ID)).Result() + } + validator.VotingPower = p.Int64() + + // save validator + err = k.AddValidator(ctx, validator) + if err != nil { + k.Logger(ctx).Error("Unable to update signer", "error", err, "ValidatorID", validator.ID) + return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() + } + + // save staking sequence + k.SetStakingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeStakeUpdate, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyValidatorID, strconv.FormatUint(validator.ID.Uint64(), 10)), + sdk.NewAttribute(types.AttributeKeyUpdatedAt, validator.LastUpdated), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// PostHandleMsgSignerUpdate handles signer update message +func PostHandleMsgSignerUpdate(ctx sdk.Context, k Keeper, msg types.MsgSignerUpdate, sideTxResult abci.SideTxResultType) sdk.Result { + // Skip handler if signer update is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping signer update since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting signer update", "sideTxResult", sideTxResult) + + newPubKey := msg.NewSignerPubKey + newSigner := newPubKey.Address() + + // pull validator from store + validator, ok := k.GetValidatorFromValID(ctx, msg.ID) + if !ok { + k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) + return hmCommon.ErrNoValidator(k.Codespace()).Result() + } + oldValidator := validator.Copy() + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // update last udpated + validator.LastUpdated = sequence.String() + + // check if we are actually updating signer + if !bytes.Equal(newSigner.Bytes(), validator.Signer.Bytes()) { + // Update signer in prev Validator + validator.Signer = hmTypes.HeimdallAddress(newSigner) + validator.PubKey = newPubKey + k.Logger(ctx).Debug("Updating new signer", "newSigner", newSigner.String(), "oldSigner", oldValidator.Signer.String(), "validatorID", msg.ID) + } else { + k.Logger(ctx).Error("No signer change", "newSigner", newSigner.String(), "oldSigner", oldValidator.Signer.String(), "validatorID", msg.ID) + return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Removing old validator", "validator", oldValidator.String()) + + // remove old validator from HM + oldValidator.EndEpoch = k.moduleCommunicator.GetACKCount(ctx) + + // remove old validator from TM + oldValidator.VotingPower = 0 + // updated last + oldValidator.LastUpdated = sequence.String() + + // save old validator + if err := k.AddValidator(ctx, *oldValidator); err != nil { + k.Logger(ctx).Error("Unable to update signer", "error", err, "validatorId", validator.ID) + return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() + } + + // adding new validator + k.Logger(ctx).Debug("Adding new validator", "validator", validator.String()) + + // save validator + err := k.AddValidator(ctx, validator) + if err != nil { + k.Logger(ctx).Error("Unable to update signer", "error", err, "ValidatorID", validator.ID) + return hmCommon.ErrSignerUpdateError(k.Codespace()).Result() + } + + // save staking sequence + k.SetStakingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeSignerUpdate, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyValidatorID, validator.ID.String()), + sdk.NewAttribute(types.AttributeKeyUpdatedAt, validator.LastUpdated), + ), + }) + + // + // Move heimdall fee to new signer + // + + // check if fee is already withdrawn + coins := k.moduleCommunicator.GetCoins(ctx, oldValidator.Signer) + maticBalance := coins.AmountOf(authTypes.FeeToken) + if !maticBalance.IsZero() { + k.Logger(ctx).Info("Transferring fee", "from", oldValidator.Signer.String(), "to", validator.Signer.String(), "balance", maticBalance.String()) + maticCoins := sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: maticBalance}} + if err := k.moduleCommunicator.SendCoins(ctx, oldValidator.Signer, validator.Signer, maticCoins); err != nil { + k.Logger(ctx).Info("Error while transferring fee", "from", oldValidator.Signer.String(), "to", validator.Signer.String(), "balance", maticBalance.String()) + return err.Result() + } + } + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} + +// PostHandleMsgValidatorExit handle msg validator exit +func PostHandleMsgValidatorExit(ctx sdk.Context, k Keeper, msg types.MsgValidatorExit, sideTxResult abci.SideTxResultType) sdk.Result { + // Skip handler if validator exit is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping validator exit since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting validator exit", "sideTxResult", sideTxResult) + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + validator, ok := k.GetValidatorFromValID(ctx, msg.ID) + if !ok { + k.Logger(ctx).Error("Fetching of validator from store failed", "validatorID", msg.ID) + return hmCommon.ErrNoValidator(k.Codespace()).Result() + } + + // set end epoch + validator.EndEpoch = msg.DeactivationEpoch + + // update last updated + validator.LastUpdated = sequence.String() + + // Add deactivation time for validator + if err := k.AddValidator(ctx, validator); err != nil { + k.Logger(ctx).Error("Error while setting deactivation epoch to validator", "error", err, "validatorID", validator.ID.String()) + return hmCommon.ErrValidatorNotDeactivated(k.Codespace()).Result() + } + + // save staking sequence + k.SetStakingSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeValidatorExit, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyValidatorID, validator.ID.String()), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/staking/types/msg.go b/staking/types/msg.go index 4b60a421b..9d23ae77e 100644 --- a/staking/types/msg.go +++ b/staking/types/msg.go @@ -21,28 +21,37 @@ var cdc = codec.New() var _ sdk.Msg = &MsgValidatorJoin{} type MsgValidatorJoin struct { - From hmTypes.HeimdallAddress `json:"from"` - ID hmTypes.ValidatorID `json:"id"` - SignerPubKey hmTypes.PubKey `json:"pub_key"` - TxHash hmTypes.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + From hmTypes.HeimdallAddress `json:"from"` + ID hmTypes.ValidatorID `json:"id"` + ActivationEpoch uint64 `json:"activationEpoch"` + Amount sdk.Int `json:"amount"` + SignerPubKey hmTypes.PubKey `json:"pub_key"` + TxHash hmTypes.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } // NewMsgValidatorJoin creates new validator-join func NewMsgValidatorJoin( from hmTypes.HeimdallAddress, id uint64, + activationEpoch uint64, + amount sdk.Int, pubkey hmTypes.PubKey, txhash hmTypes.HeimdallHash, logIndex uint64, + blockNumber uint64, ) MsgValidatorJoin { return MsgValidatorJoin{ - From: from, - ID: hmTypes.NewValidatorID(id), - SignerPubKey: pubkey, - TxHash: txhash, - LogIndex: logIndex, + From: from, + ID: hmTypes.NewValidatorID(id), + ActivationEpoch: activationEpoch, + Amount: amount, + SignerPubKey: pubkey, + TxHash: txhash, + LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -92,6 +101,11 @@ func (msg MsgValidatorJoin) GetLogIndex() uint64 { return msg.LogIndex } +// GetSideSignBytes returns side sign bytes +func (msg MsgValidatorJoin) GetSideSignBytes() []byte { + return nil +} + // // Stake update // @@ -104,19 +118,23 @@ var _ sdk.Msg = &MsgStakeUpdate{} // MsgStakeUpdate represents stake update type MsgStakeUpdate struct { - From hmTypes.HeimdallAddress `json:"from"` - ID hmTypes.ValidatorID `json:"id"` - TxHash hmTypes.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + From hmTypes.HeimdallAddress `json:"from"` + ID hmTypes.ValidatorID `json:"id"` + NewAmount sdk.Int `json:"amount"` + TxHash hmTypes.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } // NewMsgStakeUpdate represents stake update -func NewMsgStakeUpdate(from hmTypes.HeimdallAddress, id uint64, txhash hmTypes.HeimdallHash, logIndex uint64) MsgStakeUpdate { +func NewMsgStakeUpdate(from hmTypes.HeimdallAddress, id uint64, newAmount sdk.Int, txhash hmTypes.HeimdallHash, logIndex uint64, blockNumber uint64) MsgStakeUpdate { return MsgStakeUpdate{ - From: from, - ID: hmTypes.NewValidatorID(id), - TxHash: txhash, - LogIndex: logIndex, + From: from, + ID: hmTypes.NewValidatorID(id), + NewAmount: newAmount, + TxHash: txhash, + LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -162,6 +180,11 @@ func (msg MsgStakeUpdate) GetLogIndex() uint64 { return msg.LogIndex } +// GetSideSignBytes returns side sign bytes +func (msg MsgStakeUpdate) GetSideSignBytes() []byte { + return nil +} + // // validator update // @@ -175,6 +198,7 @@ type MsgSignerUpdate struct { NewSignerPubKey hmTypes.PubKey `json:"pubKey"` TxHash hmTypes.HeimdallHash `json:"tx_hash"` LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } func NewMsgSignerUpdate( @@ -183,6 +207,7 @@ func NewMsgSignerUpdate( pubKey hmTypes.PubKey, txhash hmTypes.HeimdallHash, logIndex uint64, + blockNumber uint64, ) MsgSignerUpdate { return MsgSignerUpdate{ From: from, @@ -190,6 +215,7 @@ func NewMsgSignerUpdate( NewSignerPubKey: pubKey, TxHash: txhash, LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -239,6 +265,11 @@ func (msg MsgSignerUpdate) GetLogIndex() uint64 { return msg.LogIndex } +// GetSideSignBytes returns side sign bytes +func (msg MsgSignerUpdate) GetSideSignBytes() []byte { + return nil +} + // // validator exit // @@ -246,18 +277,22 @@ func (msg MsgSignerUpdate) GetLogIndex() uint64 { var _ sdk.Msg = &MsgValidatorExit{} type MsgValidatorExit struct { - From hmTypes.HeimdallAddress `json:"from"` - ID hmTypes.ValidatorID `json:"id"` - TxHash hmTypes.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` + From hmTypes.HeimdallAddress `json:"from"` + ID hmTypes.ValidatorID `json:"id"` + DeactivationEpoch uint64 `json:"deactivationEpoch"` + TxHash hmTypes.HeimdallHash `json:"tx_hash"` + LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } -func NewMsgValidatorExit(from hmTypes.HeimdallAddress, id uint64, txhash hmTypes.HeimdallHash, logIndex uint64) MsgValidatorExit { +func NewMsgValidatorExit(from hmTypes.HeimdallAddress, id uint64, deactivationEpoch uint64, txhash hmTypes.HeimdallHash, logIndex uint64, blockNumber uint64) MsgValidatorExit { return MsgValidatorExit{ - From: from, - ID: hmTypes.NewValidatorID(id), - TxHash: txhash, - LogIndex: logIndex, + From: from, + ID: hmTypes.NewValidatorID(id), + DeactivationEpoch: deactivationEpoch, + TxHash: txhash, + LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -302,3 +337,8 @@ func (msg MsgValidatorExit) GetTxHash() types.HeimdallHash { func (msg MsgValidatorExit) GetLogIndex() uint64 { return msg.LogIndex } + +// GetSideSignBytes returns side sign bytes +func (msg MsgValidatorExit) GetSideSignBytes() []byte { + return nil +} diff --git a/topup/client/cli/flags.go b/topup/client/cli/flags.go index 2a98cc024..6ea1841a8 100644 --- a/topup/client/cli/flags.go +++ b/topup/client/cli/flags.go @@ -2,9 +2,12 @@ package cli const ( FlagProposerAddress = "proposer" + FlagSignerAddress = "signer" FlagValidatorID = "validator-id" FlagTxHash = "tx-hash" FlagLogIndex = "log-index" + FlagBlockNumber = "block-number" FlagTo = "to" FlagAmount = "amount" + FlagFeeAmount = "fee-amount" ) diff --git a/topup/client/cli/query.go b/topup/client/cli/query.go index a58a4b13a..06fcb6ccc 100644 --- a/topup/client/cli/query.go +++ b/topup/client/cli/query.go @@ -41,7 +41,7 @@ func GetSequence(cdc *codec.Codec) *cobra.Command { Short: "get sequence from txhash and logindex", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - logIndex := uint64(viper.GetInt64(FlagLogIndex)) + logIndex := viper.GetUint64(FlagLogIndex) txHashStr := viper.GetString(FlagTxHash) if txHashStr == "" { return fmt.Errorf("LogIndex and transaction hash required") @@ -70,7 +70,7 @@ func GetSequence(cdc *codec.Codec) *cobra.Command { } cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { cliLogger.Error("GetSequence | MarkFlagRequired | FlagTxHash", "Error", err) } diff --git a/topup/client/cli/tx.go b/topup/client/cli/tx.go index b643f5427..55e61d478 100644 --- a/topup/client/cli/tx.go +++ b/topup/client/cli/tx.go @@ -53,11 +53,23 @@ func TopupTxCmd(cdc *codec.Codec) *cobra.Command { proposer = helper.GetFromAddress(cliCtx) } - validatorID := viper.GetInt64(FlagValidatorID) + validatorID := viper.GetUint64(FlagValidatorID) if validatorID == 0 { return fmt.Errorf("Validator ID cannot be zero") } + // get signer + signer := types.HexToHeimdallAddress(viper.GetString(FlagSignerAddress)) + if signer.Empty() { + return fmt.Errorf("Signer address cannot be zero") + } + + // fee amount + fee, ok := sdk.NewIntFromString(viper.GetString(FlagFeeAmount)) + if !ok { + return errors.New("Invalid fee amount") + } + txhash := viper.GetString(FlagTxHash) if txhash == "" { return fmt.Errorf("transaction hash has to be supplied") @@ -66,9 +78,12 @@ func TopupTxCmd(cdc *codec.Codec) *cobra.Command { // build and sign the transaction, then broadcast to Tendermint msg := topupTypes.NewMsgTopup( proposer, - uint64(validatorID), + validatorID, + signer, + fee, types.HexToHeimdallHash(txhash), - uint64(viper.GetInt64(FlagLogIndex)), + viper.GetUint64(FlagLogIndex), + viper.GetUint64(FlagBlockNumber), ) // broadcast msg with cli @@ -76,9 +91,13 @@ func TopupTxCmd(cdc *codec.Codec) *cobra.Command { }, } - cmd.Flags().Int(FlagValidatorID, 0, "--validator-id=") + cmd.Flags().Uint64(FlagValidatorID, 0, "--validator-id=") cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") + cmd.Flags().String(FlagSignerAddress, "", "--signer=") + cmd.Flags().String(FlagFeeAmount, "", "--topup-amount=") + cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") + cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") + if err := cmd.MarkFlagRequired(FlagValidatorID); err != nil { cliLogger.Error("TopupTxCmd | MarkFlagRequired | FlagValidatorID", "Error", err) } @@ -88,6 +107,16 @@ func TopupTxCmd(cdc *codec.Codec) *cobra.Command { if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { cliLogger.Error("TopupTxCmd | MarkFlagRequired | FlagLogIndex", "Error", err) } + if err := cmd.MarkFlagRequired(FlagSignerAddress); err != nil { + cliLogger.Error("TopupTxCmd | MarkFlagRequired | FlagSignerAddress", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagFeeAmount); err != nil { + cliLogger.Error("TopupTxCmd | MarkFlagRequired | FlagFeeAmount", "Error", err) + } + if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { + cliLogger.Error("TopupTxCmd | MarkFlagRequired | FlagBlockNumber", "Error", err) + } + return cmd } diff --git a/topup/client/rest/tx.go b/topup/client/rest/tx.go index 88c727ffc..0c961b84c 100644 --- a/topup/client/rest/tx.go +++ b/topup/client/rest/tx.go @@ -29,9 +29,12 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { type TopupReq struct { BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - ID uint64 `json:"id" yaml:"id"` - TxHash string `json:"tx_hash" yaml:"tx_hash"` - LogIndex uint64 `json:"log_index" yaml:"log_index"` + ID uint64 `json:"id" yaml:"id"` + TxHash string `json:"tx_hash" yaml:"tx_hash"` + LogIndex uint64 `json:"log_index" yaml:"log_index"` + Signer string `json:"signer" yaml:"signer"` + Fee string `json:"fee" yaml:"fee"` + BlockNumber uint64 `json:"block_number" yaml:"block_number"` } // TopupHandlerFn - http request handler to topup coins to a address. @@ -50,13 +53,25 @@ func TopupHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { // get from address fromAddr := types.HexToHeimdallAddress(req.BaseReq.From) - // get msg + // get signer + signer := types.HexToHeimdallAddress(req.Signer) + + // fee amount + fee, ok := sdk.NewIntFromString(req.Fee) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid amount") + } + msg := topupTypes.NewMsgTopup( fromAddr, req.ID, + signer, + fee, types.HexToHeimdallHash(req.TxHash), req.LogIndex, + req.BlockNumber, ) + restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/topup/handler.go b/topup/handler.go index 41546fd12..389dfe0ee 100644 --- a/topup/handler.go +++ b/topup/handler.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/auth" authTypes "github.com/maticnetwork/heimdall/auth/types" hmCommon "github.com/maticnetwork/heimdall/common" "github.com/maticnetwork/heimdall/helper" @@ -32,46 +31,23 @@ func NewHandler(k Keeper, contractCaller helper.IContractCaller) sdk.Handler { // HandleMsgTopup handles topup event func HandleMsgTopup(ctx sdk.Context, k Keeper, msg types.MsgTopup, contractCaller helper.IContractCaller) sdk.Result { - if !k.bk.GetSendEnabled(ctx) { - return types.ErrSendDisabled(k.Codespace()).Result() - } - - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) - if err != nil || receipt == nil { - return hmCommon.ErrWaitForConfirmation(k.Codespace(), params.TxConfirmationTime).Result() - } - - // get event log for topup - eventLog, err := contractCaller.DecodeValidatorTopupFeesEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrInvalidMsg(k.Codespace(), "Unable to fetch logs for txHash").Result() - } + k.Logger(ctx).Debug("✅ Validating topup msg", + "validatorId", msg.ID, + "Signer", msg.Signer, + "Fee", msg.Fee, + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match id in logs", "MsgID", msg.ID, "IdFromTx", eventLog.ValidatorId) - return hmCommon.ErrInvalidMsg(k.Codespace(), "Invalid txhash and id don't match. Id from tx hash is %v", eventLog.ValidatorId.Uint64()).Result() - } - - // use event log signer - signer := hmTypes.BytesToHeimdallAddress(eventLog.Signer.Bytes()) - // if validator exists use siger from local state - validator, found := k.sk.GetValidatorFromValID(ctx, msg.ID) - if found { - signer = validator.Signer + if !k.bk.GetSendEnabled(ctx) { + return types.ErrSendDisabled(k.Codespace()).Result() } - // create topup amount - topupAmount := sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: sdk.NewIntFromBigInt(eventLog.Fee)}} - // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) // check if incoming tx already exists @@ -80,29 +56,6 @@ func HandleMsgTopup(ctx sdk.Context, k Keeper, msg types.MsgTopup, contractCalle return hmCommon.ErrOldTx(k.Codespace()).Result() } - // increase coins in account - if _, err := k.bk.AddCoins(ctx, signer, topupAmount); err != nil { - return err.Result() - } - - // transfer fees to sender (proposer) - if err := k.bk.SendCoins(ctx, signer, msg.FromAddress, auth.DefaultFeeWantedPerTx); err != nil { - return err.Result() - } - - // save topup - k.SetTopupSequence(ctx, sequence.String()) - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeTopup, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(types.AttributeKeyValidatorID, msg.ID.String()), - sdk.NewAttribute(types.AttributeKeyValidatorSigner, signer.String()), - sdk.NewAttribute(types.AttributeKeyTopupAmount, eventLog.Fee.String()), - ), - }) - return sdk.Result{ Events: ctx.EventManager().Events(), } diff --git a/topup/module.go b/topup/module.go index 07b14f52c..6024b4807 100644 --- a/topup/module.go +++ b/topup/module.go @@ -19,6 +19,7 @@ import ( topupRest "github.com/maticnetwork/heimdall/topup/client/rest" "github.com/maticnetwork/heimdall/topup/types" + hmTypes "github.com/maticnetwork/heimdall/types" hmModule "github.com/maticnetwork/heimdall/types/module" simTypes "github.com/maticnetwork/heimdall/types/simulation" ) @@ -173,3 +174,12 @@ func (AppModule) RegisterStoreDecoder(sdr hmModule.StoreDecoderRegistry) { func (AppModule) WeightedOperations(_ hmModule.SimulationState) []simTypes.WeightedOperation { return nil } + +func (am AppModule) NewSideTxHandler() hmTypes.SideTxHandler { + return NewSideTxHandler(am.keeper, am.contractCaller) +} + +// NewPostTxHandler side tx handler +func (am AppModule) NewPostTxHandler() hmTypes.PostTxHandler { + return NewPostTxHandler(am.keeper, am.contractCaller) +} diff --git a/topup/side_handler.go b/topup/side_handler.go new file mode 100644 index 000000000..41b828a99 --- /dev/null +++ b/topup/side_handler.go @@ -0,0 +1,164 @@ +package topup + +import ( + "bytes" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/maticnetwork/heimdall/auth" + authTypes "github.com/maticnetwork/heimdall/auth/types" + "github.com/maticnetwork/heimdall/common" + hmCommon "github.com/maticnetwork/heimdall/common" + "github.com/maticnetwork/heimdall/helper" + "github.com/maticnetwork/heimdall/topup/types" + hmTypes "github.com/maticnetwork/heimdall/types" + tmTypes "github.com/tendermint/tendermint/types" +) + +// NewSideTxHandler returns a side handler for "topup" type messages. +func NewSideTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.SideTxHandler { + return func(ctx sdk.Context, msg sdk.Msg) abci.ResponseDeliverSideTx { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgTopup: + return SideHandleMsgTopup(ctx, k, msg, contractCaller) + default: + return abci.ResponseDeliverSideTx{ + Code: uint32(sdk.CodeUnknownRequest), + } + } + } +} + +// NewPostTxHandler returns a side handler for "bank" type messages. +func NewPostTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.PostTxHandler { + return func(ctx sdk.Context, msg sdk.Msg, sideTxResult abci.SideTxResultType) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgTopup: + return PostHandleMsgTopup(ctx, k, msg, sideTxResult) + default: + errMsg := "Unrecognized topup Msg type: %s" + msg.Type() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// SideHandleMsgTopup handles MsgTopup message for external call +func SideHandleMsgTopup(ctx sdk.Context, k Keeper, msg types.MsgTopup, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { + + k.Logger(ctx).Debug("✅ Validating External call for topup msg", + "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), + "logIndex", uint64(msg.LogIndex), + "blockNumber", msg.BlockNumber, + ) + + // chainManager params + params := k.chainKeeper.GetParams(ctx) + chainParams := params.ChainParams + + // get main tx receipt + receipt, err := contractCaller.GetConfirmedTxReceipt(ctx.BlockTime(), msg.TxHash.EthHash(), params.TxConfirmationTime) + if err != nil || receipt == nil { + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) + } + + // get event log for topup + eventLog, err := contractCaller.DecodeValidatorTopupFeesEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) + if err != nil || eventLog == nil { + k.Logger(ctx).Error("Error fetching log from txhash") + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) + } + + if receipt.BlockNumber.Uint64() != msg.BlockNumber { + k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { + k.Logger(ctx).Error("ID in message doesn't match id in logs", "MsgID", msg.ID, "IdFromTx", eventLog.ValidatorId) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if !bytes.Equal(eventLog.Signer.Bytes(), msg.Signer.Bytes()) { + k.Logger(ctx).Error( + "Signer Address from event does not match with Msg signer", + "EventSigner", eventLog.Signer.String(), + "MsgSigner", msg.Signer.String(), + ) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + if eventLog.Fee.Cmp(msg.Fee.BigInt()) != 0 { + k.Logger(ctx).Error("Fee in message doesn't match Fee in event logs", "MsgFee", msg.Fee, "FeeFromEvent", eventLog.Fee) + return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) + } + + k.Logger(ctx).Debug("✅ Succesfully validated External call for topup msg") + result.Result = abci.SideTxResultType_Yes + return +} + +func PostHandleMsgTopup(ctx sdk.Context, k Keeper, msg types.MsgTopup, sideTxResult abci.SideTxResultType) sdk.Result { + + // Skip handler if topup is not approved + if sideTxResult != abci.SideTxResultType_Yes { + k.Logger(ctx).Debug("Skipping new topup since side-tx didn't get yes votes") + return common.ErrSideTxValidation(k.Codespace()).Result() + } + + k.Logger(ctx).Debug("Persisting topup state", "sideTxResult", sideTxResult) + + // use event log signer + signer := msg.Signer + // if validator exists use siger from local state + validator, found := k.sk.GetValidatorFromValID(ctx, msg.ID) + if found { + signer = validator.Signer + } + + // create topup amount + topupAmount := sdk.Coins{sdk.Coin{Denom: authTypes.FeeToken, Amount: msg.Fee}} + + // sequence id + blockNumber := new(big.Int).SetUint64(msg.BlockNumber) + sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) + sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) + + // increase coins in account + if _, err := k.bk.AddCoins(ctx, signer, topupAmount); err != nil { + k.Logger(ctx).Error("Error while adding coins to signer", "signer", signer, "topupAmount", topupAmount, "error", err) + return err.Result() + } + + // transfer fees to sender (proposer) + if err := k.bk.SendCoins(ctx, signer, msg.FromAddress, auth.DefaultFeeWantedPerTx); err != nil { + return err.Result() + } + + // save topup + k.SetTopupSequence(ctx, sequence.String()) + + // TX bytes + txBytes := ctx.TxBytes() + hash := tmTypes.Tx(txBytes).Hash() + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTopup, + sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name + sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash + sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result + sdk.NewAttribute(types.AttributeKeyValidatorID, msg.ID.String()), + sdk.NewAttribute(types.AttributeKeyValidatorSigner, signer.String()), + sdk.NewAttribute(types.AttributeKeyTopupAmount, msg.Fee.String()), + ), + }) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } +} diff --git a/topup/types/msg.go b/topup/types/msg.go index 7fdd83c89..6b2bca2a3 100644 --- a/topup/types/msg.go +++ b/topup/types/msg.go @@ -15,8 +15,11 @@ import ( type MsgTopup struct { FromAddress types.HeimdallAddress `json:"from_address"` ID types.ValidatorID `json:"id"` + Signer types.HeimdallAddress `json:"signer"` + Fee sdk.Int `json:"fee"` TxHash types.HeimdallHash `json:"tx_hash"` LogIndex uint64 `json:"log_index"` + BlockNumber uint64 `json:"block_number"` } var _ sdk.Msg = MsgTopup{} @@ -25,14 +28,20 @@ var _ sdk.Msg = MsgTopup{} func NewMsgTopup( fromAddr types.HeimdallAddress, id uint64, + signer types.HeimdallAddress, + fee sdk.Int, txhash types.HeimdallHash, logIndex uint64, + blockNumber uint64, ) MsgTopup { return MsgTopup{ FromAddress: fromAddr, ID: types.NewValidatorID(id), + Signer: signer, + Fee: fee, TxHash: txhash, LogIndex: logIndex, + BlockNumber: blockNumber, } } @@ -43,7 +52,7 @@ func (msg MsgTopup) Route() string { // Type Implements Msg. func (msg MsgTopup) Type() string { - return "deposit" + return "topup" } // ValidateBasic Implements Msg. @@ -83,6 +92,10 @@ func (msg MsgTopup) GetLogIndex() uint64 { return msg.LogIndex } +func (msg MsgTopup) GetSideSignBytes() []byte { + return nil +} + // // Fee token withdrawal // diff --git a/types/events.go b/types/events.go index 73b079a36..d458b7c47 100644 --- a/types/events.go +++ b/types/events.go @@ -3,5 +3,6 @@ package types // staking module event types const ( AttributeKeyTxHash = "txhash" + AttributeKeyTxLogIndex = "tx-log-index" AttributeKeySideTxResult = "side-tx-result" )