From 6a7ebc7f4beb90191ac37bca267308e52a4e5296 Mon Sep 17 00:00:00 2001 From: codchen Date: Wed, 24 Jan 2024 00:38:31 +0800 Subject: [PATCH] Handle pruned heights in RPC gracefully (#1271) * Handle pruned heights in RPC gracefully * tests --- evmrpc/info.go | 10 ++++++++-- evmrpc/info_test.go | 8 +++++++- evmrpc/setup_test.go | 22 +++++++++++++++++++--- evmrpc/simulate.go | 4 ++++ evmrpc/simulate_test.go | 11 +++++++++++ evmrpc/state.go | 12 ++++++++++++ evmrpc/state_test.go | 6 ++++++ evmrpc/tx.go | 3 +++ evmrpc/tx_test.go | 2 ++ evmrpc/utils.go | 19 +++++++++++++++++++ evmrpc/utils_test.go | 20 ++++++++++++++++++++ 11 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 evmrpc/utils_test.go diff --git a/evmrpc/info.go b/evmrpc/info.go index 82b1d16b76..79446de88a 100644 --- a/evmrpc/info.go +++ b/evmrpc/info.go @@ -127,16 +127,22 @@ func (i *InfoAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecimal64 result.OldestBlock = (*hexutil.Big)(big.NewInt(lastBlockNumber - int64(blockCount) + 1)) } + result.Reward = [][]*hexutil.Big{} // Potentially parallelize the following logic for blockNum := result.OldestBlock.ToInt().Int64(); blockNum <= lastBlockNumber; blockNum++ { - result.GasUsedRatio = append(result.GasUsedRatio, GasUsedRatio) sdkCtx := i.ctxProvider(blockNum) + if CheckVersion(sdkCtx, i.keeper) != nil { + // either height is pruned or before EVM is introduced. Skipping + continue + } + result.GasUsedRatio = append(result.GasUsedRatio, GasUsedRatio) baseFee := i.keeper.GetBaseFeePerGas(sdkCtx).BigInt() result.BaseFee = append(result.BaseFee, (*hexutil.Big)(baseFee)) height := blockNum block, err := i.tmClient.Block(ctx, &height) if err != nil { - return nil, err + // block pruned from tendermint store. Skipping + continue } rewards, err := i.getRewards(block, baseFee, rewardPercentiles) if err != nil { diff --git a/evmrpc/info_test.go b/evmrpc/info_test.go index 3aa9e9452a..33fb2fe3af 100644 --- a/evmrpc/info_test.go +++ b/evmrpc/info_test.go @@ -48,13 +48,17 @@ func TestAccounts(t *testing.T) { } func TestCoinbase(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) resObj := sendRequestGood(t, "coinbase") + Ctx = Ctx.WithBlockHeight(8) result := resObj["result"].(string) require.Equal(t, "0x27f7b8b8b5a4e71e8e9aa671f4e4031e3773303f", result) } func TestGasPrice(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) resObj := sendRequestGood(t, "gasPrice") + Ctx = Ctx.WithBlockHeight(8) result := resObj["result"].(string) require.Equal(t, "0xa", result) } @@ -65,7 +69,8 @@ func TestFeeHistory(t *testing.T) { bodyByEarliest := []interface{}{"0x1", "earliest", []interface{}{0.5}} bodyOld := []interface{}{"0x1", "0x1", []interface{}{0.5}} bodyFuture := []interface{}{"0x1", "0x9", []interface{}{0.5}} - expectedOldest := []string{"0x8", "0x8", "0x1", "0x1", "0x8"} + expectedOldest := []string{"0x1", "0x1", "0x1", "0x1", "0x1"} + Ctx = Ctx.WithBlockHeight(1) for i, body := range [][]interface{}{ bodyByNumber, bodyByLatest, bodyByEarliest, bodyOld, bodyFuture, } { @@ -94,6 +99,7 @@ func TestFeeHistory(t *testing.T) { errMap := resObj["error"].(map[string]interface{}) require.Equal(t, "invalid reward percentiles: must be ascending and between 0 and 100", errMap["message"].(string)) } + Ctx = Ctx.WithBlockHeight(8) } func TestCalculatePercentiles(t *testing.T) { diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index d5571e51af..a896bf3d99 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -23,10 +23,10 @@ import ( "github.com/gorilla/websocket" "github.com/sei-protocol/sei-chain/app" "github.com/sei-protocol/sei-chain/evmrpc" - testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/types" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -320,7 +320,23 @@ var Ctx sdk.Context func init() { types.RegisterInterfaces(EncodingConfig.InterfaceRegistry) - EVMKeeper, Ctx = testkeeper.MockEVMKeeper() + testApp := app.Setup(false, false) + Ctx = testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(8) + EVMKeeper = &testApp.EvmKeeper + EVMKeeper.InitGenesis(Ctx, *evmtypes.DefaultGenesis()) + seiAddr, err := sdk.AccAddressFromHex(common.Bytes2Hex([]byte("seiAddr"))) + if err != nil { + panic(err) + } + err = testApp.BankKeeper.MintCoins(Ctx, "evm", sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10)))) + if err != nil { + panic(err) + } + err = testApp.BankKeeper.SendCoinsFromModuleToAccount(Ctx, "evm", seiAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10)))) + if err != nil { + panic(err) + } + testApp.Commit(context.Background()) goodConfig := evmrpc.DefaultConfig goodConfig.HTTPPort = TestPort goodConfig.WSPort = TestWSPort @@ -413,7 +429,7 @@ func init() { }); err != nil { panic(err) } - seiAddr, err := sdk.AccAddressFromHex(common.Bytes2Hex([]byte("seiAddr"))) + seiAddr, err = sdk.AccAddressFromHex(common.Bytes2Hex([]byte("seiAddr"))) if err != nil { panic(err) } diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 2316a0cab3..99b5c04fff 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -146,6 +146,10 @@ func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHas if err != nil { return nil, nil, err } + sdkCtx := b.ctxProvider(height) + if err := CheckVersion(sdkCtx, b.keeper); err != nil { + return nil, nil, err + } return state.NewDBImpl(b.ctxProvider(height), b.keeper, true), b.getHeader(big.NewInt(height)), nil } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index 14ec4716a6..28b5a06f03 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -17,6 +17,7 @@ import ( ) func TestEstimateGas(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) // transfer _, from := testkeeper.MockAddressPair() _, to := testkeeper.MockAddressPair() @@ -62,9 +63,13 @@ func TestEstimateGas(t *testing.T) { resObj = sendRequestGood(t, "estimateGas", txArgs, nil, map[string]interface{}{}) result = resObj["result"].(string) require.Equal(t, "0x53f3", result) // 21491 + + Ctx = Ctx.WithBlockHeight(8) } func TestCreateAccessList(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) + _, from := testkeeper.MockAddressPair() _, contractAddr := testkeeper.MockAddressPair() code, err := os.ReadFile("../example/contracts/simplestorage/SimpleStorage.bin") @@ -94,9 +99,13 @@ func TestCreateAccessList(t *testing.T) { resObj = sendRequestBad(t, "createAccessList", txArgs, "latest") result = resObj["error"].(map[string]interface{}) require.Equal(t, "error block", result["message"]) + + Ctx = Ctx.WithBlockHeight(8) } func TestCall(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) + _, from := testkeeper.MockAddressPair() _, contractAddr := testkeeper.MockAddressPair() code, err := os.ReadFile("../example/contracts/simplestorage/SimpleStorage.bin") @@ -119,6 +128,8 @@ func TestCall(t *testing.T) { resObj := sendRequestGood(t, "call", txArgs, nil, map[string]interface{}{}, map[string]interface{}{}) result := resObj["result"].(string) require.Equal(t, "0x608060405234801561000f575f80fd5b5060043610610034575f3560e01c806360fe47b1146100385780636d4ce63c14610054575b5f80fd5b610052600480360381019061004d91906100f1565b610072565b005b61005c6100b2565b604051610069919061012b565b60405180910390f35b805f819055507f0de2d86113046b9e8bb6b785e96a6228f6803952bf53a40b68a36dce316218c1816040516100a7919061012b565b60405180910390a150565b5f8054905090565b5f80fd5b5f819050919050565b6100d0816100be565b81146100da575f80fd5b50565b5f813590506100eb816100c7565b92915050565b5f60208284031215610106576101056100ba565b5b5f610113848285016100dd565b91505092915050565b610125816100be565b82525050565b5f60208201905061013e5f83018461011c565b9291505056fea26469706673582212205b2eaa3bd967fbbfe4490610612964348d1d0b2a793d2b0d117fe05ccb02d1e364736f6c63430008150033", result) // 21325 + + Ctx = Ctx.WithBlockHeight(8) } func TestNewRevertError(t *testing.T) { diff --git a/evmrpc/state.go b/evmrpc/state.go index bf2142a065..f79169cf5d 100644 --- a/evmrpc/state.go +++ b/evmrpc/state.go @@ -43,6 +43,9 @@ func (a *StateAPI) GetBalance(ctx context.Context, address common.Address, block sdkCtx := a.ctxProvider(LatestCtxHeight) if block != nil { sdkCtx = a.ctxProvider(*block) + if err := CheckVersion(sdkCtx, a.keeper); err != nil { + return nil, err + } } statedb := state.NewDBImpl(sdkCtx, a.keeper, true) return (*hexutil.Big)(statedb.GetBalance(address)), nil @@ -58,6 +61,9 @@ func (a *StateAPI) GetCode(ctx context.Context, address common.Address, blockNrO sdkCtx := a.ctxProvider(LatestCtxHeight) if block != nil { sdkCtx = a.ctxProvider(*block) + if err := CheckVersion(sdkCtx, a.keeper); err != nil { + return nil, err + } } code := a.keeper.GetCode(sdkCtx, address) return code, nil @@ -73,6 +79,9 @@ func (a *StateAPI) GetStorageAt(ctx context.Context, address common.Address, hex sdkCtx := a.ctxProvider(LatestCtxHeight) if block != nil { sdkCtx = a.ctxProvider(*block) + if err := CheckVersion(sdkCtx, a.keeper); err != nil { + return nil, err + } } key, _, err := decodeHash(hexKey) if err != nil { @@ -110,6 +119,9 @@ func (a *StateAPI) GetProof(ctx context.Context, address common.Address, storage return nil, err } sdkCtx := a.ctxProvider(block.Block.Height) + if err := CheckVersion(sdkCtx, a.keeper); err != nil { + return nil, err + } var iavl *iavlstore.Store s := sdkCtx.MultiStore().GetKVStore((a.keeper.GetStoreKey())) OUTER: diff --git a/evmrpc/state_test.go b/evmrpc/state_test.go index ef920ec961..a6801984f8 100644 --- a/evmrpc/state_test.go +++ b/evmrpc/state_test.go @@ -21,6 +21,7 @@ import ( ) func TestGetBalance(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) tests := []struct { name string addr string @@ -87,9 +88,11 @@ func TestGetBalance(t *testing.T) { } }) } + Ctx = Ctx.WithBlockHeight(8) } func TestGetCode(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) wantKey := "0x" + hex.EncodeToString([]byte("abc")) tests := []struct { name string @@ -141,9 +144,11 @@ func TestGetCode(t *testing.T) { } }) } + Ctx = Ctx.WithBlockHeight(8) } func TestGetStorageAt(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) hexValue := common.BytesToHash([]byte("value")) wantValue := "0x" + hex.EncodeToString(hexValue[:]) tests := []struct { @@ -197,6 +202,7 @@ func TestGetStorageAt(t *testing.T) { } }) } + Ctx = Ctx.WithBlockHeight(8) } func TestGetProof(t *testing.T) { diff --git a/evmrpc/tx.go b/evmrpc/tx.go index 9d9da87b46..7fc0d88602 100644 --- a/evmrpc/tx.go +++ b/evmrpc/tx.go @@ -211,6 +211,9 @@ func (t *TransactionAPI) GetTransactionCount(ctx context.Context, address common if blkNr != nil { sdkCtx = t.ctxProvider(*blkNr) + if err := CheckVersion(sdkCtx, t.keeper); err != nil { + return nil, err + } } nonce := t.keeper.CalculateNextNonce(sdkCtx, address, pending) diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index 0fcb37874b..276b93ac06 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -152,6 +152,7 @@ func TestGetPendingTransactionByHash(t *testing.T) { } func TestGetTransactionCount(t *testing.T) { + Ctx = Ctx.WithBlockHeight(1) // happy path bodyByNumber := "{\"jsonrpc\": \"2.0\",\"method\": \"eth_getTransactionCount\",\"params\":[\"0x1234567890123456789012345678901234567890\",\"0x8\"],\"id\":\"test\"}" bodyByHash := "{\"jsonrpc\": \"2.0\",\"method\": \"eth_getTransactionCount\",\"params\":[\"0x1234567890123456789012345678901234567890\",\"0x3030303030303030303030303030303030303030303030303030303030303031\"],\"id\":\"test\"}" @@ -202,6 +203,7 @@ func TestGetTransactionCount(t *testing.T) { errMsg := errMap["message"].(string) require.Equal(t, errStr, errMsg) } + Ctx = Ctx.WithBlockHeight(8) } func TestGetTransactionError(t *testing.T) { diff --git a/evmrpc/utils.go b/evmrpc/utils.go index b6bd110ede..8dd0f066f6 100644 --- a/evmrpc/utils.go +++ b/evmrpc/utils.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ecdsa" "encoding/hex" + "fmt" "math/big" "time" @@ -283,3 +284,21 @@ func recordMetrics(apiMethod string, startTime time.Time, success bool) { metrics.IncrementRpcRequestCounter(apiMethod, success) metrics.MeasureRpcRequestLatency(apiMethod, startTime) } + +func CheckVersion(ctx sdk.Context, k *keeper.Keeper) error { + if !evmExists(ctx, k) { + return fmt.Errorf("evm module does not exist on height %d", ctx.BlockHeight()) + } + if !bankExists(ctx, k) { + return fmt.Errorf("bank module does not exist on height %d", ctx.BlockHeight()) + } + return nil +} + +func bankExists(ctx sdk.Context, k *keeper.Keeper) bool { + return ctx.KVStore(k.BankKeeper().GetStoreKey()).VersionExists(ctx.BlockHeight()) +} + +func evmExists(ctx sdk.Context, k *keeper.Keeper) bool { + return ctx.KVStore(k.GetStoreKey()).VersionExists(ctx.BlockHeight()) +} diff --git a/evmrpc/utils_test.go b/evmrpc/utils_test.go new file mode 100644 index 0000000000..0fbacf4bdf --- /dev/null +++ b/evmrpc/utils_test.go @@ -0,0 +1,20 @@ +package evmrpc_test + +import ( + "context" + "testing" + + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/evmrpc" + "github.com/stretchr/testify/require" +) + +func TestCheckVersion(t *testing.T) { + testApp := app.Setup(false, false) + k := &testApp.EvmKeeper + ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1) + testApp.Commit(context.Background()) // bump store version to 1 + require.Nil(t, evmrpc.CheckVersion(ctx, k)) + ctx = ctx.WithBlockHeight(2) + require.NotNil(t, evmrpc.CheckVersion(ctx, k)) +}