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: crypto-org-chain#455
- add patch-tx-result command
- test in integration test
  • Loading branch information
yihuang committed May 6, 2022
1 parent b193a0e commit 662d45f
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 14 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 @@

- [#454](https://github.com/crypto-org-chain/cronos/pull/454) Add back the latest testnet upgrade handler.

### 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.

*May 3, 2022*

## v0.7.0
Expand Down
302 changes: 302 additions & 0 deletions cmd/cronosd/cmd/fix-unlucky-tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package cmd

import (
"errors"
"fmt"
"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"
)

// 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)

chainID, err := cmd.Flags().GetString(flags.FlagChainID)
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
}

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

appCreator := func() *app.App {
cms := rootmulti.NewStore(db)
cms.SetLazyLoading(true)
return app.New(
log.NewNopLogger(), db, nil, false, nil,
ctx.Config.RootDir, 0, encCfg, ctx.Viper,
func(baseApp *baseapp.BaseApp) { baseApp.SetCMS(cms) },
)
}

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(appCreator, 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)
continue
}
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if ok {
fmt.Println("patched", ethMsg.Hash, result.Height, result.Index)
}
}
}
return nil
},
}
cmd.Flags().String(flags.FlagChainID, "cronosmainnet_25-1", "network chain ID")

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,
}
}
1 change: 1 addition & 0 deletions cmd/cronosd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ require (
replace (
// TODO: fix keyring upstream
github.com/99designs/keyring => github.com/crypto-org-chain/keyring v1.1.6-fixes
github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.4
github.com/cosmos/cosmos-sdk => github.com/yihuang/cosmos-sdk v0.45.2-0.20220506143148-a7cb97e1a00f

// TODO: remove when middleware will be implemented
github.com/cosmos/ibc-go/v2 => github.com/crypto-org-chain/ibc-go/v2 v2.2.0-hooks2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44=
github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU=
github.com/cosmos/cosmos-sdk v0.45.4 h1:eStDAhJdMY8n5arbBRe+OwpNeBSunxSBHp1g55ulfdA=
github.com/cosmos/cosmos-sdk v0.45.4/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
Expand Down Expand Up @@ -1160,6 +1158,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
github.com/yihuang/cosmos-sdk v0.45.2-0.20220506143148-a7cb97e1a00f h1:BblQhNJgFq8t6m/pa4mpocpElrOiA/CQrEc9RCHZp1c=
github.com/yihuang/cosmos-sdk v0.45.2-0.20220506143148-a7cb97e1a00f/go.mod h1:WOqtDxN3eCCmnYLVla10xG7lEXkFjpTaqm2a2WasgCc=
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
9 changes: 9 additions & 0 deletions integration_tests/cosmoscli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,3 +1050,12 @@ def transfer_tokens(self, from_, to, amount, **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,
).decode()
return [tuple(line.split()[1:]) for line in output.split("\n")]
Loading

0 comments on commit 662d45f

Please sign in to comment.