Skip to content

Commit

Permalink
Problem: The unlucky transactions are not shown in json rpc
Browse files Browse the repository at this point in the history
Closes: #455
- add patch-tx-result command
- test in integration test
  • Loading branch information
yihuang committed May 6, 2022
1 parent ca00b0f commit 7eeae2d
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 25 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
264 changes: 264 additions & 0 deletions cmd/cronosd/cmd/fix-unlucky-tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package cmd

import (
"errors"
"fmt"
"path/filepath"
"strconv"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
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/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"
)

// FixUnluckyTxCmd update the tx execution result of false-failed tx in tendermint db
func FixUnluckyTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "fix-unlucky-tx [start-block] [end-block]",
Short: "Fix tx execution result of false-failed tx",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
startHeight, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return err
}
if startHeight < 1 {
return fmt.Errorf("invalid block start-block: %d", startHeight)
}
endHeight, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return err
}
if endHeight < startHeight {
return fmt.Errorf("invalid end-block %d, smaller than start-block", endHeight)
}

ctx := server.GetServerContextFromCmd(cmd)
clientCtx := client.GetClientContextFromCmd(cmd)

tmDB, err := openTMDB(ctx.Config)
if err != nil {
return err
}

state, err := tmDB.stateStore.Load()
if err != nil {
return err
}

db, err := openAppDB(ctx.Config.RootDir)
if err != nil {
return err
}
defer func() {
if err := db.Close(); err != nil {
ctx.Logger.With("error", err).Error("error closing db")
}
}()

encCfg := app.MakeEncodingConfig()

anApp := app.New(
log.NewNopLogger(), db, nil, false, nil,
ctx.Config.RootDir, 0, encCfg, ctx.Viper,
)

for height := startHeight; height <= endHeight; height++ {
blockResult, err := tmDB.stateStore.LoadABCIResponses(height)
if err != nil {
return err
}

txIndex := findUnluckyTx(blockResult)
if txIndex < 0 {
// no unlucky tx in the block
continue
}

result, err := tmDB.replayTx(anApp, height, txIndex, state.InitialHeight)
if err != nil {
return err
}

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)
}
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if ok {
fmt.Println("patched", ethMsg.Hash, result.Height, result.Index)
}
}
}
return nil
},
}

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) (*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)

store, err := tmnode.DefaultDBProvider(&tmnode.DBContext{ID: "tx_index", Config: cfg})
if err != nil {
return nil, err
}
txIndexer := kv.NewTxIndex(store)

return &tmDB{
blockStore, stateStore, txIndexer,
}, nil
}

func findUnluckyTx(blockResult *tmstate.ABCIResponses) int {
for txIndex, txResult := range blockResult.DeliverTxs {
if rpc.TxExceedsBlockGasLimit(txResult) {
return txIndex
}
}
return -1
}

func (db *tmDB) replayTx(anApp *app.App, height int64, txIndex int, initialHeight int64) (*abci.TxResult, error) {
// replay the tx and patch tx result
// replay and patch block
block := db.blockStore.LoadBlock(height)
if block == nil {
return nil, fmt.Errorf("block %d not found", height)
}
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 tx with infinite block gas meter, before v0.7.0 upgrade those unlucky txs are committed successfully.
anApp.WithBlockGasMeter(sdk.NewInfiniteGasMeter())

// run txs of block
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,
}
}
1 change: 1 addition & 0 deletions cmd/cronosd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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/yihuang/cosmos-sdk v0.44.6-cronos-revert.0.20220506005923-c996096695d0

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
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ 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/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=
Expand Down Expand Up @@ -1024,8 +1024,8 @@ 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/yihuang/cosmos-sdk v0.44.6-cronos-revert.0.20220506005923-c996096695d0 h1:SO302qrfzmErLNyBipyOzolKeCTS1OAGkUa9fFTRzPM=
github.com/yihuang/cosmos-sdk v0.44.6-cronos-revert.0.20220506005923-c996096695d0/go.mod h1:Il2o2AOea3kDxkOp7wcVr9btR2Ns4opBi7NQrgV0jUo=
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=
Expand Down
16 changes: 8 additions & 8 deletions gomod2nix.toml
Original file line number Diff line number Diff line change
Expand Up @@ -950,13 +950,13 @@
sha256 = "0nxbn0m7lr4dg0yrwnvlkfiyg3ndv8vdpssjx7b714nivpc6ar0y"

["github.com/cosmos/cosmos-sdk"]
sumVersion = "v0.44.6-cronos-revert"
vendorPath = "github.com/crypto-org-chain/cosmos-sdk"
sumVersion = "v0.44.6-cronos-revert.0.20220506005923-c996096695d0"
vendorPath = "github.com/yihuang/cosmos-sdk"
["github.com/cosmos/cosmos-sdk".fetch]
type = "git"
url = "https://github.com/crypto-org-chain/cosmos-sdk"
rev = "0bb0c08fb303e20b70d17e85401d02c08f8fdbfd"
sha256 = "0lnhfx2q8zq9b00pp4rgh8zf68j1y07grz6lgky94j80qrmjzzpi"
url = "https://github.com/yihuang/cosmos-sdk"
rev = "c996096695d04bb847994689b0e5b58d637f6b64"
sha256 = "19llii0jpsg8fa035q5wgjbb4lr270v5ywpscrlvnfjqmpk8263n"

["github.com/cosmos/go-bip39"]
sumVersion = "v1.0.0"
Expand Down Expand Up @@ -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"

Expand Down
10 changes: 10 additions & 0 deletions integration_tests/cosmoscli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import hashlib
import json
import tempfile
from collections import namedtuple

import bech32
from dateutil.parser import isoparse
Expand Down Expand Up @@ -1012,3 +1013,12 @@ def update_token_mapping(self, denom, contract, **kwargs):
**kwargs,
)
)

def fix_unlucky_tx(self, begin_block, end_block):
output = self.raw(
"fix-unlucky-tx",
begin_block,
end_block,
home=self.data_dir,
)
return [tuple(line.split()[1:]) for line in output.split("\n")]
Loading

0 comments on commit 7eeae2d

Please sign in to comment.