Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC endpoints for excluding tracing failures #1995

Merged
merged 20 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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,
}

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
}

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) 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 @@
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 @@
// 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 @@
"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()

Check warning

Code scanning / CodeQL

Calling the system time Warning

Calling the system time may be a possible source of non-determinism
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 @@
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 @@
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 @@
}
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
Loading