diff --git a/rpc/backend/blocks.go b/rpc/backend/blocks.go index a91118168a..20d3776c63 100644 --- a/rpc/backend/blocks.go +++ b/rpc/backend/blocks.go @@ -404,7 +404,12 @@ func (b *Backend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Heade ) } - ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee, validatorAccount) return ethHeader, nil } @@ -440,7 +445,12 @@ func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) ) } - ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee, validatorAccount) return ethHeader, nil } @@ -613,7 +623,12 @@ func (b *Backend) EthBlockFromTendermintBlock( ) } - ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee) + validatorAccount, err := GetValidatorAccount(&resBlock.Block.Header, b.queryClient) + if err != nil { + return nil, err + } + + ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee, validatorAccount) msgs, additionals := b.EthMsgsFromTendermintBlock(resBlock, blockRes) txs := []*ethtypes.Transaction{} diff --git a/rpc/backend/blocks_test.go b/rpc/backend/blocks_test.go index 5da81d03c9..0b2be17809 100644 --- a/rpc/backend/blocks_test.go +++ b/rpc/backend/blocks_test.go @@ -1191,6 +1191,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { var expResultBlock *tmrpctypes.ResultBlock _, bz := suite.buildEthereumTx() + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1218,6 +1219,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { height := blockNum.Int64() client := suite.backend.clientCtx.Client.(*mocks.Client) RegisterBlockNotFound(client, height) + }, false, }, @@ -1245,6 +1247,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1260,6 +1263,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1275,6 +1279,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1287,7 +1292,7 @@ func (suite *BackendTestSuite) TestHeaderByNumber() { header, err := suite.backend.HeaderByNumber(tc.blockNumber) if tc.expPass { - expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee, validator) suite.Require().NoError(err) suite.Require().Equal(expHeader, header) } else { @@ -1303,6 +1308,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { _, bz := suite.buildEthereumTx() block := tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil) emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1355,6 +1361,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFeeError(queryClient) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1370,6 +1377,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1385,6 +1393,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, true, }, @@ -1397,7 +1406,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { header, err := suite.backend.HeaderByHash(tc.hash) if tc.expPass { - expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee) + expHeader := ethrpc.EthHeaderFromTendermint(expResultBlock.Block.Header, ethtypes.Bloom{}, tc.baseFee, validator) suite.Require().NoError(err) suite.Require().Equal(expHeader, header) } else { @@ -1410,6 +1419,7 @@ func (suite *BackendTestSuite) TestHeaderByHash() { func (suite *BackendTestSuite) TestEthBlockByNumber() { msgEthereumTx, bz := suite.buildEthereumTx() emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1453,12 +1463,14 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) baseFee := sdk.NewInt(1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{}, nil, @@ -1479,12 +1491,14 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) baseFee := sdk.NewInt(1) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, nil, @@ -1520,6 +1534,7 @@ func (suite *BackendTestSuite) TestEthBlockByNumber() { func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { msgEthereumTx, bz := suite.buildEthereumTx() emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) testCases := []struct { name string @@ -1543,12 +1558,14 @@ func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { func(baseFee sdkmath.Int, blockNum int64) { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{}, nil, @@ -1578,12 +1595,14 @@ func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() { func(baseFee sdkmath.Int, blockNum int64) { queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) RegisterBaseFee(queryClient, baseFee) + RegisterValidatorAccount(queryClient, validator) }, ethtypes.NewBlock( ethrpc.EthHeaderFromTendermint( emptyBlock.Header, ethtypes.Bloom{}, sdk.NewInt(1).BigInt(), + validator, ), []*ethtypes.Transaction{msgEthereumTx.AsTransaction()}, nil, @@ -1657,6 +1676,8 @@ func (suite *BackendTestSuite) TestEthAndSyntheticEthBlockByNumber() { msgEthereumTx, _ := suite.buildEthereumTx() realTx := suite.signAndEncodeEthTx(msgEthereumTx) + validator := sdk.AccAddress(tests.GenerateAddress().Bytes()) + suite.backend.indexer = nil client := suite.backend.clientCtx.Client.(*mocks.Client) queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient) @@ -1664,6 +1685,7 @@ func (suite *BackendTestSuite) TestEthAndSyntheticEthBlockByNumber() { RegisterBlock(client, 1, []tmtypes.Tx{realTx, tx}) RegisterBlockResultsWithTxResults(client, 1, []*types.ResponseDeliverTx{{}, &txRes}) RegisterBaseFee(queryClient, sdk.NewInt(1)) + RegisterValidatorAccount(queryClient, validator) // only real should be returned block, err := suite.backend.EthBlockByNumber(1) diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index bada8750c6..d68884c499 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -36,6 +36,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + tmtypes "github.com/cometbft/cometbft/types" "github.com/zeta-chain/node/rpc/types" ) @@ -318,3 +319,16 @@ func GetHexProofs(proof *crypto.ProofOps) []string { } return proofs } + +func GetValidatorAccount(header *tmtypes.Header, qc *types.QueryClient) (sdk.AccAddress, error) { + res, err := qc.ValidatorAccount( + types.ContextWithHeight(header.Height), + &evmtypes.QueryValidatorAccountRequest{ + ConsAddress: sdk.ConsAddress(header.ProposerAddress).String(), + }, + ) + if err != nil { + return nil, fmt.Errorf("failed to get validator account %w", err) + } + return sdk.AccAddressFromBech32(res.AccountAddress) +} diff --git a/rpc/namespaces/ethereum/eth/filters/api.go b/rpc/namespaces/ethereum/eth/filters/api.go index 7b6d3d9ff3..83a262b01f 100644 --- a/rpc/namespaces/ethereum/eth/filters/api.go +++ b/rpc/namespaces/ethereum/eth/filters/api.go @@ -27,11 +27,13 @@ import ( tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" "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/eth/filters" "github.com/ethereum/go-ethereum/rpc" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/node/rpc/backend" "github.com/zeta-chain/node/rpc/types" ) @@ -81,12 +83,13 @@ type filter struct { // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { - logger log.Logger - clientCtx client.Context - backend Backend - events *EventSystem - filtersMu sync.Mutex - filters map[rpc.ID]*filter + logger log.Logger + clientCtx client.Context + backend Backend + events *EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*filter + queryClient *types.QueryClient } // NewPublicAPI returns a new PublicFilterAPI instance. @@ -98,11 +101,12 @@ func NewPublicAPI( ) *PublicFilterAPI { logger = logger.With("api", "filter") api := &PublicFilterAPI{ - logger: logger, - clientCtx: clientCtx, - backend: backend, - filters: make(map[rpc.ID]*filter), - events: NewEventSystem(logger, tmWSClient), + logger: logger, + clientCtx: clientCtx, + backend: backend, + filters: make(map[rpc.ID]*filter), + events: NewEventSystem(logger, tmWSClient), + queryClient: types.NewQueryClient(clientCtx), } go api.timeoutLoop() @@ -368,9 +372,35 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events) + validatorAccount, err := backend.GetValidatorAccount(&data.Header, api.queryClient) + if err != nil { + api.logger.Error("failed to get validator account", "err", err) + continue + } + // TODO: fetch bloom from events - header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee) - err = notifier.Notify(rpcSub.ID, header) + header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee, validatorAccount) + + var enc types.Header + enc.ParentHash = header.ParentHash + enc.UncleHash = header.UncleHash + enc.Coinbase = header.Coinbase.Hex() + enc.Root = header.Root + enc.TxHash = header.TxHash + enc.ReceiptHash = header.ReceiptHash + enc.Bloom = header.Bloom + enc.Difficulty = (*hexutil.Big)(header.Difficulty) + enc.Number = (*hexutil.Big)(header.Number) + enc.GasLimit = hexutil.Uint64(header.GasLimit) + enc.GasUsed = hexutil.Uint64(header.GasUsed) + enc.Time = hexutil.Uint64(header.Time) + enc.Extra = header.Extra + enc.MixDigest = header.MixDigest + enc.Nonce = header.Nonce + enc.BaseFee = (*hexutil.Big)(header.BaseFee) + enc.Hash = common.BytesToHash(data.Header.Hash()) + + err = notifier.Notify(rpcSub.ID, enc) if err != nil { api.logger.Debug("failed to notify", "error", err.Error()) } diff --git a/rpc/types/block.go b/rpc/types/block.go index cb2c873709..ded8a0d607 100644 --- a/rpc/types/block.go +++ b/rpc/types/block.go @@ -27,6 +27,7 @@ import ( grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/spf13/cast" ethermint "github.com/zeta-chain/ethermint/types" "google.golang.org/grpc/metadata" @@ -210,3 +211,27 @@ func (bnh *BlockNumberOrHash) decodeFromString(input string) error { } return nil } + +// https://github.com/ethereum/go-ethereum/blob/release/1.11/core/types/gen_header_json.go#L18 +type Header struct { + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + // update string avoid lost checksumed miner after MarshalText + Coinbase string `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom ethtypes.Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce ethtypes.BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + // overwrite rlpHash + Hash common.Hash `json:"hash"` +} diff --git a/rpc/types/utils.go b/rpc/types/utils.go index addce9521a..cbc25026df 100644 --- a/rpc/types/utils.go +++ b/rpc/types/utils.go @@ -26,6 +26,7 @@ import ( tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmtypes "github.com/cometbft/cometbft/types" "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" errortypes "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -61,7 +62,7 @@ func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEth // EthHeaderFromTendermint is an util function that returns an Ethereum Header // from a tendermint Header. -func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header { +func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int, miner sdk.AccAddress) *ethtypes.Header { txHash := ethtypes.EmptyRootHash if len(header.DataHash) == 0 { txHash = common.BytesToHash(header.DataHash) @@ -70,7 +71,7 @@ func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFe return ðtypes.Header{ ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), UncleHash: ethtypes.EmptyUncleHash, - Coinbase: common.BytesToAddress(header.ProposerAddress), + Coinbase: common.BytesToAddress(miner), Root: common.BytesToHash(header.AppHash), TxHash: txHash, ReceiptHash: ethtypes.EmptyRootHash, diff --git a/rpc/websockets.go b/rpc/websockets.go index 143b1765e2..7d60ae62a3 100644 --- a/rpc/websockets.go +++ b/rpc/websockets.go @@ -35,13 +35,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/node/rpc/backend" "github.com/zeta-chain/node/rpc/ethereum/pubsub" rpcfilters "github.com/zeta-chain/node/rpc/namespaces/ethereum/eth/filters" "github.com/zeta-chain/node/rpc/types" @@ -379,18 +379,21 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro // pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec type pubSubAPI struct { - events *rpcfilters.EventSystem - logger log.Logger - clientCtx client.Context + events *rpcfilters.EventSystem + logger log.Logger + clientCtx client.Context + queryClient *types.QueryClient } // newPubSubAPI creates an instance of the ethereum PubSub API. func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI { logger = logger.With("module", "websocket-client") + types.NewQueryClient(clientCtx) return &pubSubAPI{ - events: rpcfilters.NewEventSystem(logger, tmWSClient), - logger: logger, - clientCtx: clientCtx, + events: rpcfilters.NewEventSystem(logger, tmWSClient), + logger: logger, + clientCtx: clientCtx, + queryClient: types.NewQueryClient(clientCtx), } } @@ -418,39 +421,12 @@ func (api *pubSubAPI) subscribe(wsConn *wsConn, subID rpc.ID, params []interface } } -// https://github.com/ethereum/go-ethereum/blob/release/1.11/core/types/gen_header_json.go#L18 -type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - // update string avoid lost checksumed miner after MarshalText - Coinbase string `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom ethtypes.Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce ethtypes.BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - // overwrite rlpHash - Hash common.Hash `json:"hash"` -} - func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.UnsubscribeFunc, error) { sub, unsubFn, err := api.events.SubscribeNewHeads() if err != nil { return nil, errors.Wrap(err, "error creating block filter") } - // TODO: use events - baseFee := big.NewInt(params.InitialBaseFee) - go func() { headersCh := sub.Event() errCh := sub.Err() @@ -467,9 +443,17 @@ func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.Un continue } - header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee) + validatorAccount, err := backend.GetValidatorAccount(&data.Header, api.queryClient) + if err != nil { + api.logger.Error("failed to get validator account", "err", err) + continue + } + + baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events) + + header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee, validatorAccount) - var enc Header + var enc types.Header enc.ParentHash = header.ParentHash enc.UncleHash = header.UncleHash enc.Coinbase = header.Coinbase.Hex()