From 47c90c67c08600528eebc9e073931d01fdd4be9c Mon Sep 17 00:00:00 2001 From: yihuang Date: Tue, 10 May 2022 12:33:29 +0800 Subject: [PATCH] Problem: The unlucky transactions are not shown in json rpc (#458) * Problem: The unlucky transactions are not shown in json rpc Closes: #455 - add patch-tx-result command - test in integration test * support psql indexer * fix integration test * tag dependency * Update cmd/cronosd/cmd/fix-unlucky-tx.go * Apply suggestions from code review * create a refresh app to replay tx * enable lazy loading * add dry-run flag * fix lint * more convinient flags * Update cmd/cronosd/cmd/fix-unlucky-tx.go Co-authored-by: Thomas Nguy <81727899+thomas-nguy@users.noreply.github.com> * fix integration test * fix lint * Apply suggestions from code review * add a warning in help text Co-authored-by: Thomas Nguy <81727899+thomas-nguy@users.noreply.github.com> --- CHANGELOG.md | 4 + cmd/cronosd/cmd/fix-unlucky-tx.go | 366 +++++++++++++++++++++++++ cmd/cronosd/cmd/root.go | 1 + go.mod | 4 +- go.sum | 8 +- gomod2nix.toml | 12 +- integration_tests/cosmoscli.py | 9 + integration_tests/test_replay_block.py | 45 ++- x/cronos/rpc/api.go | 24 +- 9 files changed, 447 insertions(+), 26 deletions(-) create mode 100644 cmd/cronosd/cmd/fix-unlucky-tx.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 96f98e2dde..f7a9a4f6df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - [#402](https://github.com/crypto-org-chain/cronos/pull/402) Update ethermint to include bug fixes: a) add an configurable upper bound to gasWanted to prevent DoS attack, b) fix eth_sha3 rpc api, c) json-roc server always use local tm rpc client, d) fix tx hash in pending tx filter response. +### Improvements + +- [#458](https://github.com/crypto-org-chain/cronos/pull/458) Add `fix-unlucky-tx` command to patch the false-failed transactions in blocks before the v0.7.0 upgrade. + *March 8, 2022* ## v0.6.9 diff --git a/cmd/cronosd/cmd/fix-unlucky-tx.go b/cmd/cronosd/cmd/fix-unlucky-tx.go new file mode 100644 index 0000000000..f9e267d79e --- /dev/null +++ b/cmd/cronosd/cmd/fix-unlucky-tx.go @@ -0,0 +1,366 @@ +package cmd + +import ( + "bufio" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/store/rootmulti" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + tmcfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/libs/log" + tmnode "github.com/tendermint/tendermint/node" + tmstate "github.com/tendermint/tendermint/proto/tendermint/state" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/state/indexer/sink/psql" + "github.com/tendermint/tendermint/state/txindex" + "github.com/tendermint/tendermint/state/txindex/kv" + tmstore "github.com/tendermint/tendermint/store" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tm-db" + evmtypes "github.com/tharsis/ethermint/x/evm/types" + + "github.com/crypto-org-chain/cronos/app" + "github.com/crypto-org-chain/cronos/x/cronos/rpc" +) + +const ( + FlagPrintBlockNumbers = "print-block-numbers" + FlagBlocksFile = "blocks-file" + FlagStartBlock = "start-block" + FlagEndBlock = "end-block" +) + +// FixUnluckyTxCmd update the tx execution result of false-failed tx in tendermint db +func FixUnluckyTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "fix-unlucky-tx", + Short: "Fix tx execution result of false-failed tx before v0.7.0 upgrade.", + Long: "Fix tx execution result of false-failed tx before v0.7.0 upgrade.\nWARNING: don't use this command to patch blocks generated after v0.7.0 upgrade", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := server.GetServerContextFromCmd(cmd) + clientCtx := client.GetClientContextFromCmd(cmd) + + chainID, err := cmd.Flags().GetString(flags.FlagChainID) + if err != nil { + return err + } + dryRun, err := cmd.Flags().GetBool(flags.FlagDryRun) + if err != nil { + return err + } + printBlockNumbers, err := cmd.Flags().GetBool(FlagPrintBlockNumbers) + if err != nil { + return err + } + + tmDB, err := openTMDB(ctx.Config, chainID) + if err != nil { + return err + } + + state, err := tmDB.stateStore.Load() + if err != nil { + return err + } + + appDB, err := openAppDB(ctx.Config.RootDir) + if err != nil { + return err + } + defer func() { + if err := appDB.Close(); err != nil { + ctx.Logger.With("error", err).Error("error closing db") + } + }() + + encCfg := app.MakeEncodingConfig() + + appCreator := func() *app.App { + cms := rootmulti.NewStore(appDB) + cms.SetLazyLoading(true) + return app.New( + log.NewNopLogger(), appDB, nil, false, nil, + ctx.Config.RootDir, 0, encCfg, ctx.Viper, + func(baseApp *baseapp.BaseApp) { baseApp.SetCMS(cms) }, + ) + } + + // replay and patch a single block + processBlock := func(height int64) error { + blockResult, err := tmDB.stateStore.LoadABCIResponses(height) + if err != nil { + return err + } + + txIndex := findUnluckyTx(blockResult) + if txIndex < 0 { + // no unlucky tx in the block + return nil + } + + if printBlockNumbers { + fmt.Println(height, txIndex) + return nil + } + + result, err := tmDB.replayTx(appCreator, height, txIndex, state.InitialHeight) + if err != nil { + return err + } + + if dryRun { + return clientCtx.PrintProto(result) + } + + if err := tmDB.patchDB(blockResult, result); err != nil { + return err + } + + // decode the tx to get eth tx hashes to log + tx, err := clientCtx.TxConfig.TxDecoder()(result.Tx) + if err != nil { + fmt.Println("can't parse the patched tx", result.Height, result.Index) + return nil + } + for _, msg := range tx.GetMsgs() { + ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) + if ok { + fmt.Println("patched", ethMsg.Hash, result.Height, result.Index) + } + } + return nil + } + + blocksFile, err := cmd.Flags().GetString(FlagBlocksFile) + if err != nil { + return err + } + if len(blocksFile) > 0 { + // read block numbers from file, one number per line + file, err := os.Open(blocksFile) + if err != nil { + return err + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + blockNumber, err := strconv.ParseInt(scanner.Text(), 10, 64) + if err != nil { + return err + } + if err := processBlock(blockNumber); err != nil { + return err + } + } + } else { + startHeight, err := cmd.Flags().GetInt(FlagStartBlock) + if err != nil { + return err + } + endHeight, err := cmd.Flags().GetInt(FlagEndBlock) + if err != nil { + return err + } + if startHeight < 1 { + return fmt.Errorf("invalid start-block: %d", startHeight) + } + if endHeight < startHeight { + return fmt.Errorf("invalid end-block %d, smaller than start-block", endHeight) + } + + for height := startHeight; height <= endHeight; height++ { + if err := processBlock(int64(height)); err != nil { + return err + } + } + } + + return nil + }, + } + cmd.Flags().String(flags.FlagChainID, "cronosmainnet_25-1", "network chain ID, only useful for psql tx indexer backend") + cmd.Flags().Bool(flags.FlagDryRun, false, "Print the execution result of the problematic txs without patch the database") + cmd.Flags().Bool(FlagPrintBlockNumbers, false, "Print the problematic block number and tx index without replay and patch") + cmd.Flags().String(FlagBlocksFile, "", "Read block numbers from a file instead of iterating all the blocks") + cmd.Flags().Int(FlagStartBlock, 1, "The start of the block range to iterate, inclusive") + cmd.Flags().Int(FlagEndBlock, -1, "The end of the block range to iterate, inclusive") + + return cmd +} + +func openAppDB(rootDir string) (dbm.DB, error) { + dataDir := filepath.Join(rootDir, "data") + return sdk.NewLevelDB("application", dataDir) +} + +type tmDB struct { + blockStore *tmstore.BlockStore + stateStore sm.Store + txIndexer txindex.TxIndexer +} + +func openTMDB(cfg *tmcfg.Config, chainID string) (*tmDB, error) { + // open tendermint db + tmdb, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "blockstore", Config: cfg}) + if err != nil { + return nil, err + } + blockStore := tmstore.NewBlockStore(tmdb) + + stateDB, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "state", Config: cfg}) + if err != nil { + return nil, err + } + stateStore := sm.NewStore(stateDB) + + txIndexer, err := newTxIndexer(cfg, chainID) + if err != nil { + return nil, err + } + + return &tmDB{ + blockStore, stateStore, txIndexer, + }, nil +} + +func newTxIndexer(config *tmcfg.Config, chainID string) (txindex.TxIndexer, error) { + switch config.TxIndex.Indexer { + case "kv": + store, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "tx_index", Config: config}) + if err != nil { + return nil, err + } + + return kv.NewTxIndex(store), nil + case "psql": + if config.TxIndex.PsqlConn == "" { + return nil, errors.New(`no psql-conn is set for the "psql" indexer`) + } + es, err := psql.NewEventSink(config.TxIndex.PsqlConn, chainID) + if err != nil { + return nil, fmt.Errorf("creating psql indexer: %w", err) + } + return es.TxIndexer(), nil + default: + return nil, fmt.Errorf("unsupported tx indexer backend %s", config.TxIndex.Indexer) + } +} + +func findUnluckyTx(blockResult *tmstate.ABCIResponses) int { + for txIndex, txResult := range blockResult.DeliverTxs { + if rpc.TxExceedsBlockGasLimit(txResult) { + return txIndex + } + } + return -1 +} + +// replay the tx and return the result +func (db *tmDB) replayTx(appCreator func() *app.App, height int64, txIndex int, initialHeight int64) (*abci.TxResult, error) { + block := db.blockStore.LoadBlock(height) + if block == nil { + return nil, fmt.Errorf("block %d not found", height) + } + anApp := appCreator() + if err := anApp.LoadHeight(block.Header.Height - 1); err != nil { + return nil, err + } + + pbh := block.Header.ToProto() + if pbh == nil { + return nil, errors.New("nil header") + } + + byzVals := make([]abci.Evidence, 0) + for _, evidence := range block.Evidence.Evidence { + byzVals = append(byzVals, evidence.ABCI()...) + } + + commitInfo := getBeginBlockValidatorInfo(block, db.stateStore, initialHeight) + + _ = anApp.BeginBlock(abci.RequestBeginBlock{ + Hash: block.Hash(), + Header: *pbh, + ByzantineValidators: byzVals, + LastCommitInfo: commitInfo, + }) + + // replay with infinite block gas meter, before v0.7.0 upgrade those unlucky txs are committed successfully. + anApp.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + + // run the predecessor txs + for _, tx := range block.Txs[:txIndex] { + anApp.DeliverTx(abci.RequestDeliverTx{Tx: tx}) + } + + rsp := anApp.DeliverTx(abci.RequestDeliverTx{Tx: block.Txs[txIndex]}) + return &abci.TxResult{ + Height: block.Header.Height, + Index: uint32(txIndex), + Tx: block.Txs[txIndex], + Result: rsp, + }, nil +} + +func (db *tmDB) patchDB(blockResult *tmstate.ABCIResponses, result *abci.TxResult) error { + if err := db.txIndexer.Index(result); err != nil { + return err + } + blockResult.DeliverTxs[result.Index] = &result.Result + if err := db.stateStore.SaveABCIResponses(result.Height, blockResult); err != nil { + return err + } + return nil +} + +func getBeginBlockValidatorInfo(block *tmtypes.Block, store sm.Store, + initialHeight int64) abci.LastCommitInfo { + voteInfos := make([]abci.VoteInfo, block.LastCommit.Size()) + // Initial block -> LastCommitInfo.Votes are empty. + // Remember that the first LastCommit is intentionally empty, so it makes + // sense for LastCommitInfo.Votes to also be empty. + if block.Height > initialHeight { + lastValSet, err := store.LoadValidators(block.Height - 1) + if err != nil { + panic(err) + } + + // Sanity check that commit size matches validator set size - only applies + // after first block. + var ( + commitSize = block.LastCommit.Size() + valSetLen = len(lastValSet.Validators) + ) + if commitSize != valSetLen { + panic(fmt.Sprintf( + "commit size (%d) doesn't match valset length (%d) at height %d\n\n%v\n\n%v", + commitSize, valSetLen, block.Height, block.LastCommit.Signatures, lastValSet.Validators, + )) + } + + for i, val := range lastValSet.Validators { + commitSig := block.LastCommit.Signatures[i] + voteInfos[i] = abci.VoteInfo{ + Validator: tmtypes.TM2PB.Validator(val), + SignedLastBlock: !commitSig.Absent(), + } + } + } + + return abci.LastCommitInfo{ + Round: block.LastCommit.Round, + Votes: voteInfos, + } +} diff --git a/cmd/cronosd/cmd/root.go b/cmd/cronosd/cmd/root.go index 47f71fb02c..1683785ae9 100644 --- a/cmd/cronosd/cmd/root.go +++ b/cmd/cronosd/cmd/root.go @@ -136,6 +136,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { // add rosetta rootCmd.AddCommand(server.RosettaCommand(encodingConfig.InterfaceRegistry, encodingConfig.Marshaler)) + rootCmd.AddCommand(FixUnluckyTxCmd()) } func addModuleInitFlags(startCmd *cobra.Command) { diff --git a/go.mod b/go.mod index 8fa597f435..d247442913 100644 --- a/go.mod +++ b/go.mod @@ -162,9 +162,9 @@ replace github.com/cosmos/iavl => github.com/cosmos/iavl v0.17.3 replace github.com/ethereum/go-ethereum => github.com/crypto-org-chain/go-ethereum v1.10.3-patched // TODO: remove when ibc-go and ethermint upgrades cosmos-sdk -replace github.com/cosmos/cosmos-sdk => github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert +replace github.com/cosmos/cosmos-sdk => github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert-2 -replace github.com/tharsis/ethermint => github.com/yihuang/ethermint v0.7.2-cronos-9.0.20220427040028-1d16a8af7dfc +replace github.com/tharsis/ethermint => github.com/crypto-org-chain/ethermint v0.7.2-cronos-15 // Note: gorocksdb bindings for OptimisticTransactionDB are not merged upstream, so we use a fork // See https://github.com/tecbot/gorocksdb/pull/216 diff --git a/go.sum b/go.sum index 9d8cd85152..6bea463655 100644 --- a/go.sum +++ b/go.sum @@ -234,8 +234,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert h1:HpX2ZybTeAzUSr5wh4oTc3sDtdz/9TH5lKB9BqOVcHE= -github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert/go.mod h1:Il2o2AOea3kDxkOp7wcVr9btR2Ns4opBi7NQrgV0jUo= +github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert-2 h1:tuJ/dswS4DP1+R7A8rb96VOtczhT0gj0mIu4jZoQV+g= +github.com/crypto-org-chain/cosmos-sdk v0.44.6-cronos-revert-2/go.mod h1:Il2o2AOea3kDxkOp7wcVr9btR2Ns4opBi7NQrgV0jUo= +github.com/crypto-org-chain/ethermint v0.7.2-cronos-15 h1:O1ZFh8cSa/7lBLqwZOatqSX4EhbW7dzaNhxMdD3gCb0= +github.com/crypto-org-chain/ethermint v0.7.2-cronos-15/go.mod h1:J96LX4KvLyl+5jV6+mt/4l6srtGX/mdDTuqQQuYrdDk= github.com/crypto-org-chain/go-ethereum v1.10.3-patched h1:kr6oQIYOi2VC8SZwkhlUDZE1Omit/YHVysKMgCB2nes= github.com/crypto-org-chain/go-ethereum v1.10.3-patched/go.mod h1:99onQmSd1GRGOziyGldI41YQb7EESX3Q4H41IfJgIQQ= github.com/crypto-org-chain/ibc-go v1.2.1-hooks h1:wuWaQqm/TFKJQwuFgjCPiPumQio+Yik5Z1DObDExrrU= @@ -1024,8 +1026,6 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= -github.com/yihuang/ethermint v0.7.2-cronos-9.0.20220427040028-1d16a8af7dfc h1:GrArNwTT2sxt+Chp2nGydAjWtiFqm/Y7thcm6IM+Mlk= -github.com/yihuang/ethermint v0.7.2-cronos-9.0.20220427040028-1d16a8af7dfc/go.mod h1:J96LX4KvLyl+5jV6+mt/4l6srtGX/mdDTuqQQuYrdDk= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/gomod2nix.toml b/gomod2nix.toml index 83885f29b3..c464b81920 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -950,13 +950,13 @@ sha256 = "0nxbn0m7lr4dg0yrwnvlkfiyg3ndv8vdpssjx7b714nivpc6ar0y" ["github.com/cosmos/cosmos-sdk"] - sumVersion = "v0.44.6-cronos-revert" + sumVersion = "v0.44.6-cronos-revert-2" vendorPath = "github.com/crypto-org-chain/cosmos-sdk" ["github.com/cosmos/cosmos-sdk".fetch] type = "git" url = "https://github.com/crypto-org-chain/cosmos-sdk" - rev = "0bb0c08fb303e20b70d17e85401d02c08f8fdbfd" - sha256 = "0lnhfx2q8zq9b00pp4rgh8zf68j1y07grz6lgky94j80qrmjzzpi" + rev = "c996096695d04bb847994689b0e5b58d637f6b64" + sha256 = "19llii0jpsg8fa035q5wgjbb4lr270v5ywpscrlvnfjqmpk8263n" ["github.com/cosmos/go-bip39"] sumVersion = "v1.0.0" @@ -3705,11 +3705,11 @@ sha256 = "1sgjf2vaq554ybc0cwkzn17cz2ibzph2rq0dgaw21c2hym09437x" ["github.com/tharsis/ethermint"] - sumVersion = "v0.7.2-cronos-9.0.20220427040028-1d16a8af7dfc" - vendorPath = "github.com/yihuang/ethermint" + sumVersion = "v0.7.2-cronos-15" + vendorPath = "github.com/crypto-org-chain/ethermint" ["github.com/tharsis/ethermint".fetch] type = "git" - url = "https://github.com/yihuang/ethermint" + url = "https://github.com/crypto-org-chain/ethermint" rev = "1d16a8af7dfc173d872f3ed439219421151e2d01" sha256 = "0h6nk3brk98pi7snn6dpj02l6kcp77xdf8w4v6xia1wsj065rgsx" diff --git a/integration_tests/cosmoscli.py b/integration_tests/cosmoscli.py index a68b2b53f1..d9ebfbb907 100644 --- a/integration_tests/cosmoscli.py +++ b/integration_tests/cosmoscli.py @@ -1012,3 +1012,12 @@ def update_token_mapping(self, denom, contract, **kwargs): **kwargs, ) ) + + def fix_unlucky_tx(self, start_block, end_block): + output = self.raw( + "fix-unlucky-tx", + start_block=start_block, + end_block=end_block, + home=self.data_dir, + ).decode() + return [tuple(line.split()[1:]) for line in output.split("\n")] diff --git a/integration_tests/test_replay_block.py b/integration_tests/test_replay_block.py index cba0f6587c..070ad86347 100644 --- a/integration_tests/test_replay_block.py +++ b/integration_tests/test_replay_block.py @@ -2,11 +2,19 @@ import pytest import web3 +from pystarport import ports from web3._utils.method_formatters import receipt_formatter from web3.datastructures import AttributeDict from .network import setup_custom_cronos -from .utils import ADDRS, KEYS, deploy_contract, sign_transaction +from .utils import ( + ADDRS, + KEYS, + deploy_contract, + sign_transaction, + supervisorctl, + wait_for_port, +) @pytest.fixture(scope="module") @@ -19,6 +27,8 @@ def custom_cronos(tmp_path_factory): def test_replay_block(custom_cronos): w3: web3.Web3 = custom_cronos.w3 + cli = custom_cronos.cosmos_cli() + begin_height = cli.block_height() contract = deploy_contract( w3, Path(__file__).parent @@ -72,17 +82,24 @@ def test_replay_block(custom_cronos): assert "error" not in rsp, rsp["error"] assert 2 == len(rsp["result"]) + # gas used by the second tx + exp_gas_used2 = 753876 + # check the replay receipts are the same replay_receipts = [AttributeDict(receipt_formatter(item)) for item in rsp["result"]] - # assert replay_receipts[0].gasUsed==replay_receipts[1].gasUsed == receipt1.gasUsed + assert replay_receipts[0].gasUsed == receipt1.gasUsed + assert replay_receipts[1].gasUsed == exp_gas_used2 assert replay_receipts[0].status == replay_receipts[1].status == receipt1.status assert ( replay_receipts[0].logsBloom == replay_receipts[1].logsBloom == receipt1.logsBloom ) - # assert replay_receipts[0].cumulativeGasUsed == receipt1.cumulativeGasUsed - # assert replay_receipts[1].cumulativeGasUsed == receipt1.cumulativeGasUsed * 2 + assert replay_receipts[0].cumulativeGasUsed == receipt1.cumulativeGasUsed + assert ( + replay_receipts[1].cumulativeGasUsed + == receipt1.cumulativeGasUsed + exp_gas_used2 + ) # check the postUpgrade mode rsp = w3.provider.make_request( @@ -93,3 +110,23 @@ def test_replay_block(custom_cronos): replay_receipts = [AttributeDict(receipt_formatter(item)) for item in rsp["result"]] assert replay_receipts[1].status == 0 assert replay_receipts[1].gasUsed == gas_limit + + # patch the unlucky tx with the new cli command + # stop the node0 + end_height = cli.block_height() + supervisorctl(custom_cronos.base_dir / "../tasks.ini", "stop", "cronos_777-1-node0") + results = custom_cronos.cosmos_cli().fix_unlucky_tx(begin_height, end_height) + # the second tx is patched + assert results[0][0] == txhashes[1].hex() + # start the node0 again + supervisorctl( + custom_cronos.base_dir / "../tasks.ini", "start", "cronos_777-1-node0" + ) + # wait for json-rpc port + wait_for_port(ports.evmrpc_port(custom_cronos.base_port(0))) + w3: web3.Web3 = custom_cronos.w3 + receipt = w3.eth.get_transaction_receipt(txhashes[1]) + # check the receipt is the same as a successful one + assert receipt.status == 1 + assert receipt.gasUsed == exp_gas_used2 + assert receipt.logsBloom == receipt1.logsBloom diff --git a/x/cronos/rpc/api.go b/x/cronos/rpc/api.go index 9ed258dd0a..de1af861f5 100644 --- a/x/cronos/rpc/api.go +++ b/x/cronos/rpc/api.go @@ -14,6 +14,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rpc" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" coretypes "github.com/tendermint/tendermint/rpc/core/types" rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" @@ -123,16 +124,14 @@ func (api *CronosAPI) ReplayBlock(blockNrOrHash rpctypes.BlockNumberOrHash, post var msgs []*evmtypes.MsgEthereumTx for i, tx := range resBlock.Block.Txs { txResult := blockRes.TxsResults[i] - if txResult.Code != 0 { - if strings.Contains(txResult.Log, ExceedBlockGasLimitError) { - // the tx with ExceedBlockGasLimitErrorPrefix error should not be ignored because: - // 1) before the 0.7.0 upgrade, the tx is committed successfully. - // 2) after the upgrade, the tx is failed but fee deducted and nonce increased. - // there's at most one such case in each block, and it should be the last tx in the block. - blockGasLimitExceeded = true - } else { - continue - } + if TxExceedsBlockGasLimit(txResult) { + // the tx with ExceedBlockGasLimitError error should not be ignored because: + // 1) before the 0.7.0 upgrade, the tx is committed successfully. + // 2) after the upgrade, the tx is failed but fee deducted and nonce increased. + // there's at most one such case in each block, and it should be the last tx in the block. + blockGasLimitExceeded = true + } else if txResult.Code != 0 { + continue } tx, err := api.clientCtx.TxConfig.TxDecoder()(tx) @@ -268,3 +267,8 @@ func (api *CronosAPI) getBlockNumber(blockNrOrHash rpctypes.BlockNumberOrHash) ( return rpctypes.EthEarliestBlockNumber, nil } } + +// TxExceedsBlockGasLimit returns true if tx's execution exceeds block gas limit +func TxExceedsBlockGasLimit(result *abci.ResponseDeliverTx) bool { + return result.Code == 11 && strings.Contains(result.Log, ExceedBlockGasLimitError) +}