Skip to content

Commit

Permalink
Add utils to replay ETH mainnet txs
Browse files Browse the repository at this point in the history
  • Loading branch information
codchen committed Mar 11, 2024
1 parent 523c19a commit d6e57da
Show file tree
Hide file tree
Showing 20 changed files with 541 additions and 8 deletions.
18 changes: 18 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"sync"
"time"

"github.com/ethereum/go-ethereum/ethclient"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/sei-protocol/sei-chain/app/antedecorators"
"github.com/sei-protocol/sei-chain/evmrpc"
"github.com/sei-protocol/sei-chain/precompiles"
Expand Down Expand Up @@ -120,6 +122,7 @@ import (
"github.com/sei-protocol/sei-chain/x/evm"
evmante "github.com/sei-protocol/sei-chain/x/evm/ante"
evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper"
"github.com/sei-protocol/sei-chain/x/evm/replay"
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/spf13/cast"
abci "github.com/tendermint/tendermint/abci/types"
Expand Down Expand Up @@ -587,6 +590,18 @@ func New(
if err != nil {
panic(fmt.Sprintf("error reading EVM config due to %s", err))
}
ethReplayConfig, err := replay.ReadConfig(appOpts)
if err != nil {
panic(fmt.Sprintf("error reading eth replay config due to %s", err))
}
app.EvmKeeper.EthReplayConfig = ethReplayConfig
if ethReplayConfig.Enabled {
rpcclient, err := ethrpc.Dial(ethReplayConfig.EthRPC)
if err != nil {
panic(fmt.Sprintf("error dialing %s due to %s", ethReplayConfig.EthRPC, err))
}
app.EvmKeeper.EthClient = ethclient.NewClient(rpcclient)
}

customDependencyGenerators := aclmapping.NewCustomDependencyGenerator()
aclOpts = append(aclOpts, aclkeeper.WithResourceTypeToStoreKeyMap(aclutils.ResourceTypeToStoreKeyMap))
Expand Down Expand Up @@ -1548,6 +1563,9 @@ func (app *App) addBadWasmDependenciesToContext(ctx sdk.Context, txResults []*ab
}

func (app *App) getFinalizeBlockResponse(appHash []byte, events []abci.Event, txResults []*abci.ExecTxResult, endBlockResp abci.ResponseEndBlock) abci.ResponseFinalizeBlock {
if app.EvmKeeper.EthReplayConfig.Enabled {
return abci.ResponseFinalizeBlock{}
}
return abci.ResponseFinalizeBlock{
Events: events,
TxResults: txResults,
Expand Down
93 changes: 93 additions & 0 deletions app/eth_replay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package app

import (
"context"
"encoding/binary"
"fmt"
"math/big"
"path/filepath"
"time"

"github.com/cosmos/cosmos-sdk/client"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/sei-protocol/sei-chain/utils"
evmtypes "github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
)

func Replay(a *App) {
gendoc, err := tmtypes.GenesisDocFromFile(filepath.Join(DefaultNodeHome, "config/genesis.json"))
if err != nil {
panic(err)
}
_, err = a.InitChain(context.Background(), &abci.RequestInitChain{
Time: time.Now(),

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
ChainId: "sei-chain",
AppStateBytes: gendoc.AppState,
})
if err != nil {
panic(err)
}
for h := int64(1); h <= int64(a.EvmKeeper.EthReplayConfig.NumBlocksToReplay); h++ {
fmt.Printf("Replaying block height %d\n", h+int64(a.EvmKeeper.EthReplayConfig.EthDataEarliestBlock))
b, err := a.EvmKeeper.EthClient.BlockByNumber(context.Background(), big.NewInt(h+int64(a.EvmKeeper.EthReplayConfig.EthDataEarliestBlock)))
if err != nil {
panic(err)
}
hash := make([]byte, 8)
binary.BigEndian.PutUint64(hash, uint64(h))
_, err = a.FinalizeBlock(context.Background(), &abci.RequestFinalizeBlock{
Txs: utils.Map(b.Txs, func(tx *ethtypes.Transaction) []byte { return encodeTx(tx, a.GetTxConfig()) }),
DecidedLastCommit: abci.CommitInfo{Votes: []abci.VoteInfo{}},
Height: h,
Hash: hash,
Time: time.Now(),

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
})
if err != nil {
panic(err)
}
for i, tx := range b.Txs {
if tx.To() != nil {
fmt.Printf("Verifying balance of tx %d\n", i)
a.EvmKeeper.VerifyBalance(a.GetContextForDeliverTx([]byte{}), *tx.To())
}
}
_, err = a.Commit(context.Background())
if err != nil {
panic(err)
}
}
}

func encodeTx(tx *ethtypes.Transaction, txConfig client.TxConfig) []byte {
var txData ethtx.TxData
var err error
switch tx.Type() {
case ethtypes.LegacyTxType:
txData, err = ethtx.NewLegacyTx(tx)
case ethtypes.DynamicFeeTxType:
txData, err = ethtx.NewDynamicFeeTx(tx)
case ethtypes.AccessListTxType:
txData, err = ethtx.NewAccessListTx(tx)
case ethtypes.BlobTxType:
txData, err = ethtx.NewBlobTx(tx)
}
if err != nil {
panic(err)
}
msg, err := evmtypes.NewMsgEVMTransaction(txData)
if err != nil {
panic(err)
}
txBuilder := txConfig.NewTxBuilder()
if err = txBuilder.SetMsgs(msg); err != nil {
panic(err)
}
txbz, encodeErr := txConfig.TxEncoder()(txBuilder.GetTx())
if encodeErr != nil {
panic(encodeErr)
}
return txbz
}
98 changes: 98 additions & 0 deletions cmd/seid/cmd/ethreplay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cmd

import (
"os"
"path/filepath"

"github.com/CosmWasm/wasmd/x/wasm"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/spf13/cast"
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/store"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper"
"github.com/sei-protocol/sei-chain/app"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"

"net/http"
//nolint:gosec,G108
_ "net/http/pprof"
)

//nolint:gosec
func ReplayCmd(defaultNodeHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "ethreplay",
Short: "replay EVM transactions",
Long: "replay EVM transactions",
RunE: func(cmd *cobra.Command, _ []string) error {

serverCtx := server.GetServerContextFromCmd(cmd)
if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
go func() {
serverCtx.Logger.Info("Listening for profiling at http://localhost:6060/debug/pprof/")
err := http.ListenAndServe(":6060", nil)
if err != nil {
serverCtx.Logger.Error("Error from profiling server", "error", err)
}
}()

Check notice

Code scanning / CodeQL

Spawning a Go routine Note

Spawning a Go routine may be a possible source of non-determinism

home := serverCtx.Viper.GetString(flags.FlagHome)
db, err := openDB(home)
if err != nil {
return err
}

logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
cache := store.NewCommitKVStoreCacheManager()
wasmGasRegisterConfig := wasmkeeper.DefaultGasRegisterConfig()
wasmGasRegisterConfig.GasMultiplier = 21_000_000
a := app.New(
logger,
db,
nil,
true,
map[int64]bool{},
home,
0,
true,
nil,
app.MakeEncodingConfig(),
wasm.EnableAllProposals,
serverCtx.Viper,
[]wasm.Option{
wasmkeeper.WithGasRegister(
wasmkeeper.NewWasmGasRegister(
wasmGasRegisterConfig,
),
),
},
[]aclkeeper.Option{},
baseapp.SetPruning(storetypes.PruneEverything),
baseapp.SetMinGasPrices(cast.ToString(serverCtx.Viper.Get(server.FlagMinGasPrices))),
baseapp.SetMinRetainBlocks(cast.ToUint64(serverCtx.Viper.Get(server.FlagMinRetainBlocks))),
baseapp.SetInterBlockCache(cache),
)
app.Replay(a)
return nil
},
}

cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The database home directory")
cmd.Flags().String(flags.FlagChainID, "sei-chain", "chain ID")

return cmd
}

func openDB(rootDir string) (dbm.DB, error) {
dataDir := filepath.Join(rootDir, "data")
return sdk.NewLevelDB("application", dataDir)
}
14 changes: 13 additions & 1 deletion cmd/seid/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/sei-protocol/sei-chain/app/params"
"github.com/sei-protocol/sei-chain/evmrpc"
"github.com/sei-protocol/sei-chain/tools"
"github.com/sei-protocol/sei-chain/x/evm/replay"
"github.com/spf13/cast"
"github.com/spf13/cobra"
tmcfg "github.com/tendermint/tendermint/config"
Expand Down Expand Up @@ -158,6 +159,7 @@ func initRootCmd(
queryCommand(),
txCommand(),
keys.Commands(app.DefaultNodeHome),
ReplayCmd(app.DefaultNodeHome),
)
}

Expand Down Expand Up @@ -370,6 +372,8 @@ func initAppConfig() (string, interface{}) {
WASM WASMConfig `mapstructure:"wasm"`

EVM evmrpc.Config `mapstructure:"evm"`

ETHReplay replay.Config `mapstructure:"eth_replay"`
}

// Optionally allow the chain developer to overwrite the SDK's default
Expand Down Expand Up @@ -409,7 +413,8 @@ func initAppConfig() (string, interface{}) {
LruSize: 1,
QueryGasLimit: 300000,
},
EVM: evmrpc.DefaultConfig,
EVM: evmrpc.DefaultConfig,
ETHReplay: replay.DefaultConfig,
}

customAppTemplate := serverconfig.DefaultConfigTemplate + `
Expand Down Expand Up @@ -477,6 +482,13 @@ checktx_timeout = "{{ .EVM.CheckTxTimeout }}"
# controls whether to have txns go through one by one
slow = {{ .EVM.Slow }}
[eth_replay]
enabled = {{ .ETHReplay.Enabled }}
eth_rpc = "{{ .ETHReplay.EthRPC }}"
eth_data_dir = "{{ .ETHReplay.EthDataDir }}"
eth_data_earliest_block = {{ .ETHReplay.EthDataEarliestBlock }}
num_blocks_to_replay = {{ .ETHReplay.NumBlocksToReplay }}
`

return customAppTemplate, customAppConfig
Expand Down
3 changes: 3 additions & 0 deletions x/evm/ante/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ func (p *EVMPreprocessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate
derived := msg.Derived
seiAddr := derived.SenderSeiAddr
evmAddr := derived.SenderEVMAddr
if p.evmKeeper.EthReplayConfig.Enabled {
p.evmKeeper.PrepareReplayedAddr(ctx, evmAddr)
}
pubkey := derived.PubKey
isAssociateTx := derived.IsAssociate
_, isAssociated := p.evmKeeper.GetEVMAddress(ctx, seiAddr)
Expand Down
69 changes: 63 additions & 6 deletions x/evm/keeper/genesis.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,80 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triedb/hashdb"
"github.com/ethereum/go-ethereum/trie/triedb/pathdb"

"github.com/sei-protocol/sei-chain/x/evm/types"
)

func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) {
var ethReplayInitialied = false

func (k *Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) {
moduleAcc := authtypes.NewEmptyModuleAccount(types.ModuleName, authtypes.Minter, authtypes.Burner)
k.accountKeeper.SetModuleAccount(ctx, moduleAcc)

k.SetParams(ctx, types.DefaultParams())

seiAddrFc := k.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName) // feeCollector == coinbase
k.SetAddressMapping(ctx, seiAddrFc, GetCoinbaseAddress())

for _, addr := range genState.AddressAssociations {
k.SetAddressMapping(ctx, sdk.MustAccAddressFromBech32(addr.SeiAddress), common.HexToAddress(addr.EthAddress))
}

if k.EthReplayConfig.Enabled && !ethReplayInitialied {
header, db, trie := k.openEthDatabase()
k.Root = header.Root
k.DB = db
k.Trie = trie
params := k.GetParams(ctx)
params.ChainId = sdk.OneInt()
k.SetParams(ctx, params)
if k.EthReplayConfig.EthDataEarliestBlock == 0 {
k.EthReplayConfig.EthDataEarliestBlock = uint64(header.Number.Int64())
}
ethReplayInitialied = true
}
}

func (k *Keeper) openEthDatabase() (*ethtypes.Header, state.Database, state.Trie) {
db, err := rawdb.Open(rawdb.OpenOptions{
Type: "pebble",
Directory: k.EthReplayConfig.EthDataDir,
AncientsDirectory: fmt.Sprintf("%s/ancient", k.EthReplayConfig.EthDataDir),
Namespace: "",
Cache: 256,
Handles: 256,
ReadOnly: true,
})
if err != nil {
panic(err)
}
config := &trie.Config{
Preimages: true,
IsVerkle: false,
}
scheme, err := rawdb.ParseStateScheme(rawdb.ReadStateScheme(db), db)
if err != nil {
panic(err)
}
var triedb *trie.Database
if scheme == rawdb.HashScheme {
config.HashDB = hashdb.Defaults
triedb = trie.NewDatabase(db, config)
} else {
config.PathDB = pathdb.ReadOnly
triedb = trie.NewDatabase(db, config)
}
header := rawdb.ReadHeadHeader(db)
sdb := state.NewDatabaseWithNodeDB(db, triedb)
tr, err := sdb.OpenTrie(header.Root)
if err != nil {
panic(err)
}
return header, sdb, tr
}
Loading

0 comments on commit d6e57da

Please sign in to comment.