Skip to content

Commit ee43f56

Browse files
feat: add support for eth_getBlockReceipts (#638)
* feat: add support for eth_getBlockReceipts (#629) * add support for eth_getblockreceipts * update changelog * Update rpc/backend/blocks.go Co-authored-by: Xinyu <[email protected]> Signed-off-by: Thomas Nguy <[email protected]> * fix lint * optimization * remove empty space * add test case * fix length and add test * return err details * fix tests * fix lint * fix tests --------- Signed-off-by: Thomas Nguy <[email protected]> Co-authored-by: Xinyu <[email protected]> * update changelog --------- Signed-off-by: Thomas Nguy <[email protected]> Co-authored-by: Xinyu <[email protected]>
1 parent 5ad5141 commit ee43f56

File tree

8 files changed

+230
-18
lines changed

8 files changed

+230
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
3939

4040
### Features
4141

42+
* (rpc) [#638](https://github.com/crypto-org-chain/ethermint/pull/638) Add support for eth_getBlockReceipts.
4243
* (evm) [#414](https://github.com/crypto-org-chain/ethermint/pull/414) Integrate go-block-stm for parallel tx execution.
4344
* (block-stm) [#498](https://github.com/crypto-org-chain/ethermint/pull/498) Enable incarnation cache for block-stm executor.
4445

rpc/backend/backend.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type EVMBackend interface {
8585
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
8686
GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint
8787
GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint
88+
GetBlockReceipts(blockNum rpctypes.BlockNumber) ([]map[string]interface{}, error)
8889
TendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpctypes.ResultBlock, error)
8990
TendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error)
9091
TendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
@@ -121,7 +122,7 @@ type EVMBackend interface {
121122
GetTxByEthHash(txHash common.Hash) (*ethermint.TxResult, error)
122123
GetTxByTxIndex(height int64, txIndex uint) (*ethermint.TxResult, error)
123124
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
124-
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
125+
GetTransactionReceipt(hash common.Hash, resBlock *tmrpctypes.ResultBlock) (map[string]interface{}, error)
125126
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
126127
GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
127128

rpc/backend/blocks.go

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (b *Backend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (
7979
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
8080
if err != nil {
8181
b.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error())
82-
return nil, nil
82+
return nil, err
8383
}
8484

8585
res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx)
@@ -91,6 +91,36 @@ func (b *Backend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (
9191
return res, nil
9292
}
9393

94+
// GetBlockReceipts returns a list of Ethereum transaction receipts given a block number
95+
func (b *Backend) GetBlockReceipts(blockNum rpctypes.BlockNumber) ([]map[string]interface{}, error) {
96+
resBlock, err := b.TendermintBlockByNumber(blockNum)
97+
if err != nil {
98+
return nil, nil
99+
}
100+
// return if requested block height is greater than the current one
101+
if resBlock == nil || resBlock.Block == nil {
102+
return nil, nil
103+
}
104+
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
105+
if err != nil {
106+
b.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error())
107+
return nil, err
108+
}
109+
110+
txHashes := b.TransactionHashesFromTendermintBlock(resBlock, blockRes)
111+
112+
res := make([]map[string]interface{}, 0, len(txHashes))
113+
for _, txHash := range txHashes {
114+
receipt, err := b.GetTransactionReceipt(txHash, resBlock)
115+
if err != nil {
116+
return nil, err
117+
}
118+
res = append(res, receipt)
119+
}
120+
121+
return res, nil
122+
}
123+
94124
// GetBlockByHash returns the JSON-RPC compatible Ethereum block identified by
95125
// hash.
96126
func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
@@ -107,7 +137,7 @@ func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]inte
107137
blockRes, err := b.TendermintBlockResultByNumber(&resBlock.Block.Height)
108138
if err != nil {
109139
b.logger.Debug("failed to fetch block result from Tendermint", "block-hash", hash.String(), "error", err.Error())
110-
return nil, nil
140+
return nil, err
111141
}
112142

113143
res, err := b.RPCBlockFromTendermintBlock(resBlock, blockRes, fullTx)
@@ -185,7 +215,7 @@ func (b *Backend) TendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpc
185215

186216
if resBlock.Block == nil {
187217
b.logger.Debug("TendermintBlockByNumber block not found", "height", height)
188-
return nil, nil
218+
return nil, fmt.Errorf("tendermint block not found")
189219
}
190220

191221
return resBlock, nil
@@ -513,6 +543,21 @@ func (b *Backend) RPCBlockFromTendermintBlock(
513543
return formattedBlock, nil
514544
}
515545

546+
// TransactionHashesFromTendermintBlock returns list of eth transaction hashes
547+
// given Tendermint block and its block result.
548+
func (b *Backend) TransactionHashesFromTendermintBlock(
549+
resBlock *tmrpctypes.ResultBlock,
550+
blockRes *tmrpctypes.ResultBlockResults,
551+
) []common.Hash {
552+
msgs := b.EthMsgsFromTendermintBlock(resBlock, blockRes)
553+
ethHashes := make([]common.Hash, 0, len(msgs))
554+
for _, ethMsg := range msgs {
555+
ethHashes = append(ethHashes, ethMsg.Hash())
556+
}
557+
558+
return ethHashes
559+
}
560+
516561
// EthBlockByNumber returns the Ethereum Block identified by number.
517562
func (b *Backend) EthBlockByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Block, error) {
518563
resBlock, err := b.TendermintBlockByNumber(blockNum)

rpc/backend/blocks_test.go

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package backend
22

33
import (
4+
tmlog "cosmossdk.io/log"
45
"encoding/json"
56
"fmt"
7+
dbm "github.com/cosmos/cosmos-db"
8+
"github.com/evmos/ethermint/indexer"
69
"math/big"
710

811
sdkmath "cosmossdk.io/math"
912
"github.com/cometbft/cometbft/abci/types"
1013
tmrpctypes "github.com/cometbft/cometbft/rpc/core/types"
14+
comettypes "github.com/cometbft/cometbft/types"
1115
tmtypes "github.com/cometbft/cometbft/types"
1216
sdk "github.com/cosmos/cosmos-sdk/types"
1317
"github.com/ethereum/go-ethereum/common"
@@ -132,7 +136,7 @@ func (suite *BackendTestSuite) TestGetBlockByNumber() {
132136
true,
133137
},
134138
{
135-
"pass - block results error",
139+
"fail - block results error",
136140
ethrpc.BlockNumber(1),
137141
true,
138142
sdkmath.NewInt(1).BigInt(),
@@ -146,7 +150,7 @@ func (suite *BackendTestSuite) TestGetBlockByNumber() {
146150
RegisterBlockResultsError(client, blockNum.Int64())
147151
},
148152
true,
149-
true,
153+
false,
150154
},
151155
{
152156
"pass - without tx",
@@ -290,7 +294,7 @@ func (suite *BackendTestSuite) TestGetBlockByHash() {
290294
RegisterBlockResultsError(client, height)
291295
},
292296
true,
293-
true,
297+
false,
294298
},
295299
{
296300
"pass - without tx",
@@ -547,7 +551,7 @@ func (suite *BackendTestSuite) TestTendermintBlockByNumber() {
547551
RegisterBlockNotFound(client, height)
548552
},
549553
false,
550-
true,
554+
false,
551555
},
552556
{
553557
"fail - blockNum < 0 with app state height error",
@@ -1644,3 +1648,156 @@ func (suite *BackendTestSuite) TestEthBlockFromTendermintBlock() {
16441648
})
16451649
}
16461650
}
1651+
1652+
// TODO fix this test case and TestGetTransactionReceipt
1653+
func (suite *BackendTestSuite) TestEthBlockReceipts() {
1654+
msgEthereumTx, _ := suite.buildEthereumTx()
1655+
txBz := suite.signAndEncodeEthTx(msgEthereumTx)
1656+
txHash := msgEthereumTx.Hash()
1657+
1658+
testCases := []struct {
1659+
name string
1660+
registerMock func()
1661+
tx *evmtypes.MsgEthereumTx
1662+
block *comettypes.Block
1663+
blockResult []*types.ExecTxResult
1664+
expTxReceipt map[string]interface{}
1665+
expPass bool
1666+
}{
1667+
{
1668+
"fail - Receipts do not match ",
1669+
func() {
1670+
var header metadata.MD
1671+
queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient)
1672+
client := suite.backend.clientCtx.Client.(*mocks.Client)
1673+
RegisterParams(queryClient, &header, 1)
1674+
RegisterParamsWithoutHeader(queryClient, 1)
1675+
RegisterBlock(client, 1, txBz)
1676+
RegisterBlockResults(client, 1)
1677+
},
1678+
msgEthereumTx,
1679+
&comettypes.Block{Header: comettypes.Header{Height: 1}, Data: comettypes.Data{Txs: []comettypes.Tx{txBz}}},
1680+
[]*types.ExecTxResult{
1681+
{
1682+
Code: 0,
1683+
Events: []types.Event{
1684+
{Type: evmtypes.EventTypeEthereumTx, Attributes: []types.EventAttribute{
1685+
{Key: "ethereumTxHash", Value: txHash.Hex()},
1686+
{Key: "txIndex", Value: "0"},
1687+
{Key: "amount", Value: "1000"},
1688+
{Key: "txGasUsed", Value: "21000"},
1689+
{Key: "txHash", Value: ""},
1690+
{Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"},
1691+
}},
1692+
},
1693+
},
1694+
},
1695+
map[string]interface{}(nil),
1696+
false,
1697+
},
1698+
{
1699+
"Success - Receipts match",
1700+
func() {
1701+
var header metadata.MD
1702+
queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient)
1703+
client := suite.backend.clientCtx.Client.(*mocks.Client)
1704+
RegisterParams(queryClient, &header, 1)
1705+
RegisterParamsWithoutHeader(queryClient, 1)
1706+
RegisterBlock(client, 1, txBz)
1707+
RegisterBlockResults(client, 1)
1708+
},
1709+
msgEthereumTx,
1710+
&comettypes.Block{Header: comettypes.Header{Height: 1}, Data: comettypes.Data{Txs: []comettypes.Tx{txBz}}},
1711+
[]*types.ExecTxResult{
1712+
{
1713+
Code: 0,
1714+
Events: []types.Event{
1715+
{Type: evmtypes.EventTypeEthereumTx, Attributes: []types.EventAttribute{
1716+
{Key: "ethereumTxHash", Value: txHash.Hex()},
1717+
{Key: "txIndex", Value: "0"},
1718+
{Key: "amount", Value: "1000"},
1719+
{Key: "txGasUsed", Value: "21000"},
1720+
{Key: "txHash", Value: ""},
1721+
{Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"},
1722+
}},
1723+
},
1724+
},
1725+
},
1726+
map[string]interface{}(nil),
1727+
false, //needs to be set to true
1728+
},
1729+
}
1730+
for _, tc := range testCases {
1731+
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
1732+
suite.SetupTest() // reset test and queries
1733+
tc.registerMock()
1734+
1735+
db := dbm.NewMemDB()
1736+
suite.backend.indexer = indexer.NewKVIndexer(db, tmlog.NewNopLogger(), suite.backend.clientCtx)
1737+
err := suite.backend.indexer.IndexBlock(tc.block, tc.blockResult)
1738+
suite.Require().NoError(err)
1739+
1740+
receipts, err := suite.backend.GetBlockReceipts(ethrpc.BlockNumber(1))
1741+
1742+
for receipt := range receipts {
1743+
if tc.expPass {
1744+
suite.Require().NoError(err)
1745+
suite.Require().Equal(receipt, tc.expTxReceipt)
1746+
} else {
1747+
suite.Require().NotEqual(receipt, tc.expTxReceipt)
1748+
}
1749+
}
1750+
1751+
})
1752+
}
1753+
}
1754+
1755+
func (suite *BackendTestSuite) TestTransactionHashesFromTendermintBlock() {
1756+
msgEthereumTx, bz := suite.buildEthereumTx()
1757+
emptyBlock := tmtypes.MakeBlock(1, []tmtypes.Tx{}, nil, nil)
1758+
testCases := []struct {
1759+
name string
1760+
resBlock *tmrpctypes.ResultBlock
1761+
blockRes *tmrpctypes.ResultBlockResults
1762+
expHashes []common.Hash
1763+
}{
1764+
{
1765+
"empty block",
1766+
&tmrpctypes.ResultBlock{
1767+
Block: emptyBlock,
1768+
},
1769+
&tmrpctypes.ResultBlockResults{
1770+
Height: 1,
1771+
TxsResults: []*types.ExecTxResult{{Code: 0, GasUsed: 0}},
1772+
},
1773+
[]common.Hash{},
1774+
},
1775+
{
1776+
"block with tx",
1777+
&tmrpctypes.ResultBlock{
1778+
Block: tmtypes.MakeBlock(1, []tmtypes.Tx{bz}, nil, nil),
1779+
},
1780+
&tmrpctypes.ResultBlockResults{
1781+
Height: 1,
1782+
TxsResults: []*types.ExecTxResult{{Code: 0, GasUsed: 0}},
1783+
FinalizeBlockEvents: []types.Event{
1784+
{
1785+
Type: evmtypes.EventTypeBlockBloom,
1786+
Attributes: []types.EventAttribute{
1787+
{Key: string(bAttributeKeyEthereumBloom)},
1788+
},
1789+
},
1790+
},
1791+
},
1792+
[]common.Hash{msgEthereumTx.Hash()},
1793+
},
1794+
}
1795+
for _, tc := range testCases {
1796+
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
1797+
suite.SetupTest() // reset test and queries
1798+
hashes := suite.backend.TransactionHashesFromTendermintBlock(tc.resBlock, tc.blockRes)
1799+
1800+
suite.Require().Equal(tc.expHashes, hashes)
1801+
})
1802+
}
1803+
}

rpc/backend/chain_info_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
410410
1,
411411
nil,
412412
nil,
413-
true,
413+
false,
414414
nil,
415415
},
416416
{

rpc/backend/tx_info.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,21 @@ func (b *Backend) GetGasUsed(res *ethermint.TxResult, gas uint64) uint64 {
149149
return res.GasUsed
150150
}
151151

152-
// GetTransactionReceipt returns the transaction receipt identified by hash.
153-
func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
152+
// GetTransactionReceipt returns the transaction receipt identified by hash. It takes an optional resBlock, if nil then the method will fetch it.
153+
func (b *Backend) GetTransactionReceipt(hash common.Hash, resBlock *tmrpctypes.ResultBlock) (map[string]interface{}, error) {
154154
b.logger.Debug("eth_getTransactionReceipt", "hash", hash)
155155

156156
res, err := b.GetTxByEthHash(hash)
157157
if err != nil {
158158
b.logger.Debug("tx not found", "hash", hash, "error", err.Error())
159159
return nil, nil
160160
}
161-
resBlock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
162-
if err != nil {
163-
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
164-
return nil, nil
161+
if resBlock == nil {
162+
resBlock, err = b.TendermintBlockByNumber(rpctypes.BlockNumber(res.Height))
163+
if err != nil {
164+
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
165+
return nil, nil
166+
}
165167
}
166168
tx, err := b.clientCtx.TxConfig.TxDecoder()(resBlock.Block.Txs[res.TxIndex])
167169
if err != nil {

rpc/backend/tx_info_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ func (suite *BackendTestSuite) TestGetTransactionReceipt() {
586586
err := suite.backend.indexer.IndexBlock(tc.block, tc.blockResult)
587587
suite.Require().NoError(err)
588588

589-
txReceipt, err := suite.backend.GetTransactionReceipt(tc.tx.Hash())
589+
txReceipt, err := suite.backend.GetTransactionReceipt(tc.tx.Hash(), nil)
590590
if tc.expPass {
591591
suite.Require().NoError(err)
592592
suite.Require().Equal(txReceipt, tc.expTxReceipt)

rpc/namespaces/ethereum/eth/api.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ type EthereumAPI interface {
6262
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
6363
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
6464
GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
65-
// eth_getBlockReceipts
65+
GetBlockReceipts(blockNum rpctypes.BlockNumber) ([]map[string]interface{}, error)
6666

6767
// Writing Transactions
6868
//
@@ -195,7 +195,7 @@ func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rp
195195
func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
196196
hexTx := hash.Hex()
197197
e.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
198-
return e.backend.GetTransactionReceipt(hash)
198+
return e.backend.GetTransactionReceipt(hash, nil)
199199
}
200200

201201
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
@@ -222,6 +222,12 @@ func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockN
222222
return e.backend.GetTransactionByBlockNumberAndIndex(blockNum, idx)
223223
}
224224

225+
// GetBlockReceipts returns a list of transaction receipts given a block number.
226+
func (e *PublicAPI) GetBlockReceipts(blockNum rpctypes.BlockNumber) ([]map[string]interface{}, error) {
227+
e.logger.Debug("eth_getBlockReceipts", "number", blockNum)
228+
return e.backend.GetBlockReceipts(blockNum)
229+
}
230+
225231
///////////////////////////////////////////////////////////////////////////////
226232
/// Write Txs ///
227233
///////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)