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

Synthetic events in separate sei endpoints #1867

Merged
merged 14 commits into from
Sep 24, 2024
Merged
105 changes: 90 additions & 15 deletions evmrpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package evmrpc

import (
"context"
"crypto/sha256"
"errors"
"fmt"
"math/big"
"strings"
"sync"
"time"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -23,20 +26,22 @@ import (
)

type BlockAPI struct {
tmClient rpcclient.Client
keeper *keeper.Keeper
ctxProvider func(int64) sdk.Context
txConfig client.TxConfig
connectionType ConnectionType
tmClient rpcclient.Client
keeper *keeper.Keeper
ctxProvider func(int64) sdk.Context
txConfig client.TxConfig
connectionType ConnectionType
namespace string
includeSyntheticTxs bool
}

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}
func NewBlockAPI(tmClient rpcclient.Client, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, txConfig client.TxConfig, connectionType ConnectionType, namespace string) *BlockAPI {
return &BlockAPI{tmClient: tmClient, keeper: k, ctxProvider: ctxProvider, txConfig: txConfig, connectionType: connectionType, includeSyntheticTxs: shouldIncludeSynthetic(namespace)}
}

func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number rpc.BlockNumber) (result *hexutil.Uint, returnErr error) {
startTime := time.Now()
defer recordMetrics("eth_getBlockTransactionCountByNumber", a.connectionType, startTime, returnErr == nil)
defer recordMetrics(fmt.Sprintf("%s_getBlockTransactionCountByNumber", a.namespace), a.connectionType, startTime, returnErr == nil)
numberPtr, err := getBlockNumber(ctx, a.tmClient, number)
if err != nil {
return nil, err
Expand All @@ -50,7 +55,7 @@ func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number

func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (result *hexutil.Uint, returnErr error) {
startTime := time.Now()
defer recordMetrics("eth_getBlockTransactionCountByHash", a.connectionType, startTime, returnErr == nil)
defer recordMetrics(fmt.Sprintf("%s_getBlockTransactionCountByHash", a.namespace), a.connectionType, startTime, returnErr == nil)
block, err := blockByHashWithRetry(ctx, a.tmClient, blockHash[:], 1)
if err != nil {
return nil, err
Expand All @@ -60,7 +65,11 @@ 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("eth_getBlockByHash", a.connectionType, startTime, returnErr == nil)
defer recordMetrics(fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr == nil)
return a.getBlockByHash(ctx, blockHash, fullTx)
}

func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
block, err := blockByHashWithRetry(ctx, a.tmClient, blockHash[:], 1)
if err != nil {
return nil, err
Expand All @@ -70,12 +79,41 @@ 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(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx)
return EncodeTmBlock(a.ctxProvider(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeSyntheticTxs)
}

func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics("eth_getBlockByNumber", a.connectionType, startTime, returnErr == nil)
defer recordMetrics(fmt.Sprintf("%s_getBlockByNumber", a.namespace), a.connectionType, startTime, returnErr == nil)
if number == 0 {
// for compatibility with the graph, always return genesis block
return map[string]interface{}{
"number": (*hexutil.Big)(big.NewInt(0)),
"hash": common.HexToHash("F9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E"),
"parentHash": common.Hash{},
"nonce": ethtypes.BlockNonce{}, // inapplicable to Sei
"mixHash": common.Hash{}, // inapplicable to Sei
"sha3Uncles": ethtypes.EmptyUncleHash, // inapplicable to Sei
"logsBloom": ethtypes.Bloom{},
"stateRoot": common.Hash{},
"miner": common.Address{},
"difficulty": (*hexutil.Big)(big.NewInt(0)), // inapplicable to Sei
"extraData": hexutil.Bytes{}, // inapplicable to Sei
"gasLimit": hexutil.Uint64(0),
"gasUsed": hexutil.Uint64(0),
"timestamp": hexutil.Uint64(0),
"transactionsRoot": common.Hash{},
"receiptsRoot": common.Hash{},
"size": hexutil.Uint64(0),
"uncles": []common.Hash{}, // inapplicable to Sei
"transactions": []interface{}{},
"baseFeePerGas": (*hexutil.Big)(big.NewInt(0)),
}, nil
}
return a.getBlockByNumber(ctx, number, fullTx)
}

func (a *BlockAPI) getBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
numberPtr, err := getBlockNumber(ctx, a.tmClient, number)
if err != nil {
return nil, err
Expand All @@ -89,12 +127,12 @@ 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(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx)
return EncodeTmBlock(a.ctxProvider(LatestCtxHeight), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeSyntheticTxs)
}

func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics("eth_getBlockReceipts", a.connectionType, startTime, returnErr == nil)
defer recordMetrics(fmt.Sprintf("%s_getBlockReceipts", a.namespace), a.connectionType, startTime, returnErr == nil)
// Get height from params
heightPtr, err := GetBlockNumberByNrOrHash(ctx, a.tmClient, blockNrOrHash)
if err != nil {
Expand Down Expand Up @@ -130,6 +168,12 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block
mtx.Unlock()
}
} else {
if !a.includeSyntheticTxs && len(receipt.Logs) > 0 && receipt.Logs[0].Synthetic {
return
}
if receipt.EffectiveGasPrice == 0 {
return
}
encodedReceipt, err := encodeReceipt(receipt, a.txConfig.TxDecoder(), block, func(h common.Hash) bool {
_, err := a.keeper.GetReceipt(a.ctxProvider(height), h)
return err == nil
Expand All @@ -144,10 +188,16 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block
}(i, hash)
}
wg.Wait()
compactReceipts := make([]map[string]interface{}, 0)
for _, r := range allReceipts {
if len(r) > 0 {
compactReceipts = append(compactReceipts, r)
}
}
if returnErr != nil {
return nil, returnErr
}
return allReceipts, nil
return compactReceipts, nil
}

func EncodeTmBlock(
Expand All @@ -158,6 +208,7 @@ func EncodeTmBlock(
k *keeper.Keeper,
txDecoder sdk.TxDecoder,
fullTx bool,
includeSyntheticTxs bool,
) (map[string]interface{}, error) {
number := big.NewInt(block.Block.Height)
blockhash := common.HexToHash(block.BlockID.Hash.String())
Expand Down Expand Up @@ -196,6 +247,30 @@ func EncodeTmBlock(
newTx := ethapi.NewRPCTransaction(ethtx, blockhash, number.Uint64(), uint64(blockTime.Second()), uint64(receipt.TransactionIndex), baseFeePerGas, chainConfig)
transactions = append(transactions, newTx)
}
case *wasmtypes.MsgExecuteContract:
if !includeSyntheticTxs {
continue
}
th := sha256.Sum256(block.Block.Txs[i])
receipt, err := k.GetReceipt(ctx, th)
if err != nil {
continue
}
if !fullTx {
transactions = append(transactions, th)
} else {
ti := uint64(receipt.TransactionIndex)
to := k.GetEVMAddressOrDefault(ctx, sdk.MustAccAddressFromBech32(m.Contract))
transactions = append(transactions, &ethapi.RPCTransaction{
BlockHash: &blockhash,
BlockNumber: (*hexutil.Big)(number),
From: common.HexToAddress(receipt.From),
To: &to,
Input: m.Msg.Bytes(),
Hash: th,
TransactionIndex: (*hexutil.Uint64)(&ti),
})
}
}
}
}
Expand Down
121 changes: 117 additions & 4 deletions evmrpc/block_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package evmrpc_test

import (
"crypto/sha256"
"math/big"
"testing"
"time"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
types2 "github.com/tendermint/tendermint/proto/tendermint/types"

sdk "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/lib/ethapi"
"github.com/sei-protocol/sei-chain/evmrpc"
testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper"
"github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/rpc/coretypes"
Expand All @@ -22,8 +29,15 @@ func TestGetBlockByHash(t *testing.T) {
verifyBlockResult(t, resObj)
}

func TestGetSeiBlockByHash(t *testing.T) {
resObj := sendSeiRequestGood(t, "getBlockByHash", "0x0000000000000000000000000000000000000000000000000000000000000001", true)
verifyBlockResult(t, resObj)
}

func TestGetBlockByNumber(t *testing.T) {
for _, num := range []string{"0x8", "earliest", "latest", "pending", "finalized", "safe"} {
resObjEarliest := sendSeiRequestGood(t, "getBlockByNumber", "earliest", true)
verifyGenesisBlockResult(t, resObjEarliest)
for _, num := range []string{"0x8", "latest", "pending", "finalized", "safe"} {
resObj := sendRequestGood(t, "getBlockByNumber", num, true)
verifyBlockResult(t, resObj)
}
Expand All @@ -32,6 +46,18 @@ func TestGetBlockByNumber(t *testing.T) {
require.Equal(t, "invalid argument 0: hex string without 0x prefix", resObj["error"].(map[string]interface{})["message"])
}

func TestGetSeiBlockByNumber(t *testing.T) {
resObjEarliest := sendSeiRequestGood(t, "getBlockByNumber", "earliest", true)
verifyGenesisBlockResult(t, resObjEarliest)
for _, num := range []string{"0x8", "latest", "pending", "finalized", "safe"} {
resObj := sendSeiRequestGood(t, "getBlockByNumber", num, true)
verifyBlockResult(t, resObj)
}

resObj := sendSeiRequestBad(t, "getBlockByNumber", "bad_num", true)
require.Equal(t, "invalid argument 0: hex string without 0x prefix", resObj["error"].(map[string]interface{})["message"])
}

func TestGetBlockTransactionCount(t *testing.T) {
// get by block number
for _, num := range []string{"0x8", "earliest", "latest", "pending", "finalized", "safe"} {
Expand Down Expand Up @@ -95,6 +121,30 @@ func TestGetBlockReceipts(t *testing.T) {
require.Equal(t, multiTxBlockTx4.Hash().Hex(), receipt1["transactionHash"])
}

func verifyGenesisBlockResult(t *testing.T, resObj map[string]interface{}) {
resObj = resObj["result"].(map[string]interface{})
require.Equal(t, "0x0", resObj["baseFeePerGas"])
require.Equal(t, "0x0", resObj["difficulty"])
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, "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{}) {
resObj = resObj["result"].(map[string]interface{})
require.Equal(t, "0x0", resObj["difficulty"])
Expand All @@ -103,7 +153,7 @@ func verifyBlockResult(t *testing.T, resObj map[string]interface{}) {
require.Equal(t, "0x5", resObj["gasUsed"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000001", resObj["hash"])
// see setup_tests.go, which have one transaction for block 0x8 (latest)
require.Equal(t, "0x00002000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000200000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000", resObj["logsBloom"])
require.Equal(t, "0x00002000040000000000000000000080000000200000000000000000000000080000000000000000000000000000000000000000000000000800000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000200000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000010200000000000000", resObj["logsBloom"])
require.Equal(t, "0x0000000000000000000000000000000000000005", resObj["miner"])
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resObj["mixHash"])
require.Equal(t, "0x0000000000000000", resObj["nonce"])
Expand Down Expand Up @@ -163,7 +213,7 @@ func TestEncodeTmBlock_EmptyTransactions(t *testing.T) {
}

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

// Assert txHash is equal to ethtypes.EmptyTxsHash
Expand Down Expand Up @@ -209,8 +259,71 @@ func TestEncodeBankMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true)
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, false)
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 0, len(txs))
}

func TestEncodeWasmExecuteMsg(t *testing.T) {
k := &testkeeper.EVMTestApp.EvmKeeper
ctx := testkeeper.EVMTestApp.GetContextForDeliverTx(nil)
fromSeiAddr, fromEvmAddr := testkeeper.MockAddressPair()
toSeiAddr, _ := testkeeper.MockAddressPair()
b := TxConfig.NewTxBuilder()
b.SetMsgs(&wasmtypes.MsgExecuteContract{
Sender: fromSeiAddr.String(),
Contract: toSeiAddr.String(),
Msg: []byte{1, 2, 3},
})
tx := b.GetTx()
bz, _ := Encoder(tx)
k.MockReceipt(ctx, sha256.Sum256(bz), &types.Receipt{
TransactionIndex: 1,
From: fromEvmAddr.Hex(),
})
resBlock := coretypes.ResultBlock{
BlockID: MockBlockID,
Block: &tmtypes.Block{
Header: mockBlockHeader(MockHeight),
Data: tmtypes.Data{
Txs: []tmtypes.Tx{bz},
},
LastCommit: &tmtypes.Commit{
Height: MockHeight - 1,
},
},
}
resBlockRes := coretypes.ResultBlockResults{
TxsResults: []*abci.ExecTxResult{
{
Data: bz,
},
},
ConsensusParamUpdates: &types2.ConsensusParams{
Block: &types2.BlockParams{
MaxBytes: 100000000,
MaxGas: 200000000,
},
},
}
res, err := evmrpc.EncodeTmBlock(ctx, &resBlock, &resBlockRes, ethtypes.Bloom{}, k, Decoder, true, true)
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 1, len(txs))
ti := uint64(1)
bh := common.HexToHash(MockBlockID.Hash.String())
to := common.Address(toSeiAddr)
require.Equal(t, &ethapi.RPCTransaction{
BlockHash: &bh,
BlockNumber: (*hexutil.Big)(big.NewInt(MockHeight)),
From: fromEvmAddr,
To: &to,
Input: []byte{1, 2, 3},
Hash: common.Hash(sha256.Sum256(bz)),
TransactionIndex: (*hexutil.Uint64)(&ti),
V: nil,
R: nil,
S: nil,
}, txs[0].(*ethapi.RPCTransaction))
}
Loading
Loading