Skip to content

Commit

Permalink
RPC endpoints for excluding tracing failures (#1995)
Browse files Browse the repository at this point in the history
* add sei_getTransactionReceipt

* add logic to exclude panic txs from getBlockByNumber

* fixed compilation errs

* test both sei_traceBlock and sei_getTransactionReceipt

* fix block_test

* update go.mod

* fixed tracer test

* tests seem to all work now

* fix genesis code

* remove unnecessary isPanicTx param

* remove prints

* fix complilation

* use a trace block + cache to do isPanic

* catch panic in traceTx

* fix

* add README + change name to *TraceFail

* update geth dep

* update geth dep
  • Loading branch information
jewei1997 authored Dec 22, 2024
1 parent ee91380 commit fce45fd
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 62 deletions.
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1831,7 +1831,7 @@ func (app *App) RegisterTendermintService(clientCtx client.Context) {
return ctx.WithIsEVM(true)
}
if app.evmRPCConfig.HTTPEnabled {
evmHTTPServer, err := evmrpc.NewEVMHTTPServer(app.Logger(), app.evmRPCConfig, clientCtx.Client, &app.EvmKeeper, ctxProvider, app.encodingConfig.TxConfig, DefaultNodeHome)
evmHTTPServer, err := evmrpc.NewEVMHTTPServer(app.Logger(), app.evmRPCConfig, clientCtx.Client, &app.EvmKeeper, ctxProvider, app.encodingConfig.TxConfig, DefaultNodeHome, nil)
if err != nil {
panic(err)
}
Expand Down
24 changes: 24 additions & 0 deletions evmrpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Sei's EVM RPC

Sei supports the standard [Ethereum JSON-RPC API](https://ethereum.org/en/developers/docs/apis/json-rpc/) endpoints. On top of that, Sei supports some additional custom endpoints.

## Sei_ endpoints

### Endpoints for Synthetic txs
The motivation for these endpoints is to expose CW20 and CW721 events on the EVM side through synthetic receipts and logs. This is useful for indexing pointer contracts.
- `sei_getFilterLogs`
- same as `eth_getFilterLogs` but includes synthetic logs
- `sei_getLogs`
- same as `eth_getLogs` but includes synthetic logs
- `sei_getBlockByNumber` and `sei_getBlockByHash`
- same as `eth_getBlockByNumber` and `eth_getBlockByHash` but includes synthetic txs
- NOTE: for synthetic txs, `eth_getTransactionReceipt` can be used to get the receipt data for a synthetic tx hash.

### Endpoints for excluding tracing failures
The motivation for these endpoints is to exclude tracing failures from the EVM side. Due to how our mempool works and our lack of tx simulation, we cannot rely on txs to pass all pre-state checks. Therefore, in the eth_ endpoints, we may see txs that fail tracing with errors like "nonce too low", "nonce too high", "insufficient funds", or other types of panic failures. These transactions are not executed, yet are still included in the block. These endpoints are useful for filtering out these txs.
- `sei_traceBlockByNumberExcludeTraceFail`
- same as `debug_traceBlockByNumber` but excludes panic txs
- `sei_getTransactionReceiptExcludeTraceFail`
- same as `eth_getTransactionReceipt` but excludes panic txs
- `sei_getBlockByNumberExcludeTraceFail` and `sei_getBlockByHashExcludeTraceFail`
- same as `eth_getBlockByNumber` and `eth_getBlockByHash` but excludes panic txs
93 changes: 75 additions & 18 deletions evmrpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,50 @@ type BlockAPI struct {
includeShellReceipts bool
}

func NewBlockAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, txConfig client.TxConfig, connectionType ConnectionType, namespace string) *BlockAPI {
type SeiBlockAPI struct {
*BlockAPI
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)
}

func NewBlockAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, txConfig client.TxConfig, connectionType ConnectionType) *BlockAPI {
return &BlockAPI{
tmClient: tmClient,
keeper: k,
ctxProvider: ctxProvider,
txConfig: txConfig,
connectionType: connectionType,
namespace: namespace,
includeShellReceipts: shouldIncludeSynthetic(namespace),
includeShellReceipts: false,
namespace: "eth",
}
}

func NewSeiBlockAPI(
tmClient rpcclient.Client,
k *keeper.Keeper,
ctxProvider func(int64) sdk.Context,
txConfig client.TxConfig,
connectionType ConnectionType,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
) *SeiBlockAPI {
blockAPI := &BlockAPI{
tmClient: tmClient,
keeper: k,
ctxProvider: ctxProvider,
txConfig: txConfig,
connectionType: connectionType,
includeShellReceipts: true,
namespace: "sei",
}
return &SeiBlockAPI{
BlockAPI: blockAPI,
isPanicTx: isPanicTx,
}
}

func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByNumber(ctx, number, fullTx, a.isPanicTx)
}

func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number rpc.BlockNumber) (result *hexutil.Uint, returnErr error) {
startTime := time.Now()
defer recordMetrics(fmt.Sprintf("%s_getBlockTransactionCountByNumber", a.namespace), a.connectionType, startTime, returnErr == nil)
Expand All @@ -76,12 +108,20 @@ func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash
}

func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics(fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr == nil)
return a.getBlockByHash(ctx, blockHash, fullTx)
return a.getBlockByHash(ctx, blockHash, fullTx, nil)
}

func (a *SeiBlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByHash(ctx, blockHash, fullTx, nil)
}

func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByHash(ctx, blockHash, fullTx, a.isPanicTx)
}

func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics(fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr == nil)
block, err := blockByHashWithRetry(ctx, a.tmClient, blockHash[:], 1)
if err != nil {
return nil, err
Expand All @@ -91,7 +131,7 @@ func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fu
return nil, err
}
blockBloom := a.keeper.GetBlockBloom(a.ctxProvider(block.Block.Height))
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts)
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts, isPanicTx)
}

func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
Expand All @@ -101,7 +141,7 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber,
// for compatibility with the graph, always return genesis block
return map[string]interface{}{
"number": (*hexutil.Big)(big.NewInt(0)),
"hash": common.HexToHash("F9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E"),
"hash": "0xF9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E",
"parentHash": common.Hash{},
"nonce": ethtypes.BlockNonce{}, // inapplicable to Sei
"mixHash": common.Hash{}, // inapplicable to Sei
Expand All @@ -122,10 +162,17 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber,
"baseFeePerGas": (*hexutil.Big)(big.NewInt(0)),
}, nil
}
return a.getBlockByNumber(ctx, number, fullTx)
return a.getBlockByNumber(ctx, number, fullTx, nil)
}

func (a *BlockAPI) getBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
func (a *BlockAPI) getBlockByNumber(
ctx context.Context,
number rpc.BlockNumber,
fullTx bool,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics(fmt.Sprintf("%s_getBlockByNumber", a.namespace), a.connectionType, startTime, returnErr == nil)
numberPtr, err := getBlockNumber(ctx, a.tmClient, number)
if err != nil {
return nil, err
Expand All @@ -139,7 +186,7 @@ func (a *BlockAPI) getBlockByNumber(ctx context.Context, number rpc.BlockNumber,
return nil, err
}
blockBloom := a.keeper.GetBlockBloom(a.ctxProvider(block.Block.Height))
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts)
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts, isPanicTx)
}

func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) {
Expand Down Expand Up @@ -215,6 +262,7 @@ func EncodeTmBlock(
txDecoder sdk.TxDecoder,
fullTx bool,
includeSyntheticTxs bool,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
) (map[string]interface{}, error) {
number := big.NewInt(block.Block.Height)
blockhash := common.HexToHash(block.BlockID.Hash.String())
Expand Down Expand Up @@ -243,16 +291,25 @@ func EncodeTmBlock(
}
ethtx, _ := m.AsTransaction()
hash := ethtx.Hash()
if !fullTx {
transactions = append(transactions, hash)
} else {
receipt, err := k.GetReceipt(ctx, hash)
if isPanicTx != nil {
isPanic, err := isPanicTx(ctx.Context(), hash)
if err != nil {
continue
return nil, fmt.Errorf("failed to check if tx is panic tx: %w", err)
}
if !includeSyntheticTxs && receipt.TxType == ShellEVMTxType {
if isPanic {
continue
}
}
receipt, err := k.GetReceipt(ctx, hash)
if err != nil {
continue
}
if !includeSyntheticTxs && receipt.TxType == ShellEVMTxType {
continue
}
if !fullTx {
transactions = append(transactions, hash)
} else {
newTx := ethapi.NewRPCTransaction(ethtx, blockhash, number.Uint64(), uint64(blockTime.Second()), uint64(receipt.TransactionIndex), baseFeePerGas, chainConfig)
transactions = append(transactions, newTx)
}
Expand Down
29 changes: 12 additions & 17 deletions evmrpc/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ func TestGetSeiBlockByHash(t *testing.T) {
verifyBlockResult(t, resObj)
}

func TestGetSeiBlockByNumberExcludeTraceFail(t *testing.T) {
resObj := sendSeiRequestGood(t, "getBlockByNumberExcludeTraceFail", "0x67", true)
// first tx is not a panic tx, second tx is a panic tx
expectedNumTxs := 1
require.Equal(t, expectedNumTxs, len(resObj["result"].(map[string]interface{})["transactions"].([]interface{})))
}

func TestGetBlockByNumber(t *testing.T) {
resObjEarliest := sendSeiRequestGood(t, "getBlockByNumber", "earliest", true)
verifyGenesisBlockResult(t, resObjEarliest)
Expand Down Expand Up @@ -96,7 +103,7 @@ func TestGetBlockReceipts(t *testing.T) {
require.Equal(t, 6, len(result))

// Query by block hash
resObj2 := sendRequestGood(t, "getBlockReceipts", "0x0000000000000000000000000000000000000000000000000000000000000002")
resObj2 := sendRequestGood(t, "getBlockReceipts", MultiTxBlockHash)
result = resObj2["result"].([]interface{})
require.Equal(t, 3, len(result))
receipt1 = result[0].(map[string]interface{})
Expand Down Expand Up @@ -125,21 +132,9 @@ func verifyGenesisBlockResult(t *testing.T, resObj map[string]interface{}) {
require.Equal(t, "0x", resObj["extraData"])
require.Equal(t, "0x0", resObj["gasLimit"])
require.Equal(t, "0x0", resObj["gasUsed"])
require.Equal(t, "0xf9d3845df25b43b1c6926f3ceda6845c17f5624e12212fd8847d0ba01da1ab9e", resObj["hash"])
require.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", resObj["logsBloom"])
require.Equal(t, "0x0000000000000000000000000000000000000000", resObj["miner"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["mixHash"])
require.Equal(t, "0xF9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E", resObj["hash"])
require.Equal(t, "0x0000000000000000", resObj["nonce"])
require.Equal(t, "0x0", resObj["number"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["parentHash"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["receiptsRoot"])
require.Equal(t, "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", resObj["sha3Uncles"])
require.Equal(t, "0x0", resObj["size"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["stateRoot"])
require.Equal(t, "0x0", resObj["timestamp"])
require.Equal(t, []interface{}{}, resObj["transactions"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["transactionsRoot"])
require.Equal(t, []interface{}{}, resObj["uncles"])
}

func verifyBlockResult(t *testing.T, resObj map[string]interface{}) {
Expand Down Expand Up @@ -210,7 +205,7 @@ func TestEncodeTmBlock_EmptyTransactions(t *testing.T) {
}

// Call EncodeTmBlock with empty transactions
result, err := evmrpc.EncodeTmBlock(ctx, block, blockRes, ethtypes.Bloom{}, k, Decoder, true, false)
result, err := evmrpc.EncodeTmBlock(ctx, block, blockRes, ethtypes.Bloom{}, k, Decoder, true, false, nil)
require.Nil(t, err)

// Assert txHash is equal to ethtypes.EmptyTxsHash
Expand Down Expand Up @@ -256,7 +251,7 @@ func TestEncodeBankMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, false)
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, false, nil)
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 0, len(txs))
Expand Down Expand Up @@ -304,7 +299,7 @@ func TestEncodeWasmExecuteMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, true)
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, true, nil)
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 1, len(txs))
Expand Down
2 changes: 0 additions & 2 deletions evmrpc/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package evmrpc_test

import (
"errors"
"fmt"
"math/big"
"testing"

Expand Down Expand Up @@ -113,7 +112,6 @@ func TestFeeHistory(t *testing.T) {
reward, ok := rewards[0].([]interface{})
require.True(t, ok, "Expected reward to be a slice of interfaces")
require.Equal(t, 1, len(reward), "Expected exactly one sub-item in reward")
fmt.Println("resObj", resObj)

require.Equal(t, tc.expectedReward, reward[0].(string), "Reward does not match expected value")

Expand Down
28 changes: 23 additions & 5 deletions evmrpc/server.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package evmrpc

import (
"context"
"strings"

"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
evmCfg "github.com/sei-protocol/sei-chain/x/evm/config"
"github.com/sei-protocol/sei-chain/x/evm/keeper"
Expand All @@ -31,6 +33,7 @@ func NewEVMHTTPServer(
ctxProvider func(int64) sdk.Context,
txConfig client.TxConfig,
homeDir string,
isPanicTxFunc func(ctx context.Context, hash common.Hash) (bool, error), // optional - for testing
) (EVMServer, error) {
httpServer := NewHTTPServer(logger, rpc.HTTPTimeouts{
ReadTimeout: config.ReadTimeout,
Expand All @@ -46,7 +49,14 @@ func NewEVMHTTPServer(
ctx := ctxProvider(LatestCtxHeight)

txAPI := NewTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP)
// filterAPI := NewFilterAPI(tmClient, &LogFetcher{tmClient: tmClient, k: k, ctxProvider: ctxProvider}, &FilterConfig{timeout: config.FilterTimeout, maxLog: config.MaxLogNoBlock, maxBlock: config.MaxBlocksForLog}, ConnectionTypeHTTP)
debugAPI := NewDebugAPI(tmClient, k, ctxProvider, txConfig.TxDecoder(), simulateConfig, ConnectionTypeHTTP)
if isPanicTxFunc == nil {
isPanicTxFunc = func(ctx context.Context, hash common.Hash) (bool, error) {
return debugAPI.isPanicTx(ctx, hash)
}
}
seiTxAPI := NewSeiTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP, isPanicTxFunc)
seiDebugAPI := NewSeiDebugAPI(tmClient, k, ctxProvider, txConfig.TxDecoder(), simulateConfig, ConnectionTypeHTTP)

apis := []rpc.API{
{
Expand All @@ -55,16 +65,20 @@ func NewEVMHTTPServer(
},
{
Namespace: "eth",
Service: NewBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP, "eth"),
Service: NewBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP),
},
{
Namespace: "sei",
Service: NewBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP, "sei"),
Service: NewSeiBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP, isPanicTxFunc),
},
{
Namespace: "eth",
Service: txAPI,
},
{
Namespace: "sei",
Service: seiTxAPI,
},
{
Namespace: "eth",
Service: NewStateAPI(tmClient, k, ctxProvider, ConnectionTypeHTTP),
Expand Down Expand Up @@ -107,7 +121,11 @@ func NewEVMHTTPServer(
},
{
Namespace: "debug",
Service: NewDebugAPI(tmClient, k, ctxProvider, txConfig.TxDecoder(), simulateConfig, ConnectionTypeHTTP),
Service: debugAPI,
},
{
Namespace: "sei",
Service: seiDebugAPI,
},
}
// Test API can only exist on non-live chain IDs. These APIs instrument certain overrides.
Expand Down Expand Up @@ -156,7 +174,7 @@ func NewEVMWebSocketServer(
},
{
Namespace: "eth",
Service: NewBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeWS, "eth"),
Service: NewBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeWS),
},
{
Namespace: "eth",
Expand Down
Loading

0 comments on commit fce45fd

Please sign in to comment.