From 287a3d1d6c90fc6a7a088b5ae320f93600d5a167 Mon Sep 17 00:00:00 2001 From: Enrique Jose Avila Asapche Date: Tue, 13 Dec 2022 20:41:51 +0000 Subject: [PATCH] added erigon_GetBlockReceiptsByBlockHash (#6292) I added a way to get canonical receipts through block hash under the erigon name space #6229 Tested: BlockHash: 0xb5d50393734894e02a94a500a5a5aade9c66f388438aeed8d7775b3b344745f1 Screenshot 2022-12-12 at 22 16 18 --- cmd/rpcdaemon/README.md | 1 + cmd/rpcdaemon/commands/erigon_api.go | 2 + cmd/rpcdaemon/commands/erigon_receipts.go | 57 ++++++++++ .../commands/erigon_receipts_test.go | 104 ++++++++++++++++++ 4 files changed, 164 insertions(+) diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index 30ae6187e..84a33dcc9 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -313,6 +313,7 @@ The following table shows the current implementation status of Erigon's RPC daem | db_getHex | No | deprecated | | | | | | erigon_getHeaderByHash | Yes | Erigon only | +| erigon_getBlockReceiptsByBlockHash | Yes | Erigon only | | erigon_getHeaderByNumber | Yes | Erigon only | | erigon_getLogsByHash | Yes | Erigon only | | erigon_forks | Yes | Erigon only | diff --git a/cmd/rpcdaemon/commands/erigon_api.go b/cmd/rpcdaemon/commands/erigon_api.go index d9a177620..fa427a7d2 100644 --- a/cmd/rpcdaemon/commands/erigon_api.go +++ b/cmd/rpcdaemon/commands/erigon_api.go @@ -32,6 +32,8 @@ type ErigonAPI interface { //GetLogsByNumber(ctx context.Context, number rpc.BlockNumber) ([][]*types.Log, error) GetLogs(ctx context.Context, crit ethFilters.FilterCriteria) (types.ErigonLogs, error) GetLatestLogs(ctx context.Context, crit filters.FilterCriteria, logOptions ethFilters.LogFilterOptions) (types.ErigonLogs, error) + // Gets cannonical block receipt through hash. If the block is not cannonical returns error + GetBlockReceiptsByBlockHash(ctx context.Context, cannonicalBlockHash common.Hash) ([]map[string]interface{}, error) // WatchTheBurn / reward related (see ./erigon_issuance.go) WatchTheBurn(ctx context.Context, blockNr rpc.BlockNumber) (Issuance, error) diff --git a/cmd/rpcdaemon/commands/erigon_receipts.go b/cmd/rpcdaemon/commands/erigon_receipts.go index fc51f3f4b..ef8524ca8 100644 --- a/cmd/rpcdaemon/commands/erigon_receipts.go +++ b/cmd/rpcdaemon/commands/erigon_receipts.go @@ -388,6 +388,63 @@ func (api *ErigonImpl) GetLatestLogs(ctx context.Context, crit filters.FilterCri return erigonLogs, nil } +func (api *ErigonImpl) GetBlockReceiptsByBlockHash(ctx context.Context, cannonicalBlockHash common.Hash) ([]map[string]interface{}, error) { + tx, err := api.db.BeginRo(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback() + + isCanonicalHash, err := rawdb.IsCanonicalHash(tx, cannonicalBlockHash) + if err != nil { + return nil, err + } + + if !isCanonicalHash { + return nil, fmt.Errorf("the hash %s is not cannonical", cannonicalBlockHash) + } + + blockNum, _, _, err := rpchelper.GetBlockNumber(rpc.BlockNumberOrHashWithHash(cannonicalBlockHash, true), tx, api.filters) + if err != nil { + return nil, err + } + block, err := api.blockByNumberWithSenders(tx, blockNum) + if err != nil { + return nil, err + } + if block == nil { + return nil, nil + } + chainConfig, err := api.chainConfig(tx) + if err != nil { + return nil, err + } + receipts, err := api.getReceipts(ctx, tx, chainConfig, block, block.Body().SendersFromTxs()) + if err != nil { + return nil, fmt.Errorf("getReceipts error: %w", err) + } + result := make([]map[string]interface{}, 0, len(receipts)) + for _, receipt := range receipts { + txn := block.Transactions()[receipt.TransactionIndex] + result = append(result, marshalReceipt(receipt, txn, chainConfig, block, txn.Hash(), true)) + } + + if chainConfig.Bor != nil { + borTx, _, _, _ := rawdb.ReadBorTransactionForBlock(tx, block) + if borTx != nil { + borReceipt, err := rawdb.ReadBorReceipt(tx, block.Hash(), block.NumberU64(), receipts) + if err != nil { + return nil, err + } + if borReceipt != nil { + result = append(result, marshalReceipt(borReceipt, borTx, chainConfig, block, borReceipt.TxHash, false)) + } + } + } + + return result, nil +} + // GetLogsByNumber implements erigon_getLogsByHash. Returns all the logs that appear in a block given the block's hash. // func (api *ErigonImpl) GetLogsByNumber(ctx context.Context, number rpc.BlockNumber) ([][]*types.Log, error) { // tx, err := api.db.Begin(ctx, false) diff --git a/cmd/rpcdaemon/commands/erigon_receipts_test.go b/cmd/rpcdaemon/commands/erigon_receipts_test.go index 4a2ff07eb..61eee9a43 100644 --- a/cmd/rpcdaemon/commands/erigon_receipts_test.go +++ b/cmd/rpcdaemon/commands/erigon_receipts_test.go @@ -5,14 +5,21 @@ import ( "math/big" "testing" + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/kv/kvcache" "github.com/ledgerwatch/erigon/cmd/rpcdaemon/rpcdaemontest" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core" + "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/eth/filters" + "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rpc" "github.com/ledgerwatch/erigon/rpc/rpccfg" "github.com/ledgerwatch/erigon/turbo/snapshotsync" + "github.com/ledgerwatch/erigon/turbo/stages" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -99,3 +106,100 @@ func TestErigonGetLatestLogsIgnoreTopics(t *testing.T) { require.NotNil(t, actual) assert.EqualValues(expectedErigonLogs, actual) } + +var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +func TestGetBlockReceiptsByBlockHash(t *testing.T) { + // Define three accounts to simulate transactions with + acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) + + signer := types.LatestSignerForChainID(nil) + // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_markets_test) + generator := func(i int, block *core.BlockGen) { + switch i { + case 0: + // In block 1, the test bank sends account #1 some ether. + tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, uint256.NewInt(10000), params.TxGas, nil, nil), *signer, testKey) + block.AddTx(tx) + case 1: + // In block 2, the test bank sends some more ether to account #1. + // acc1Addr passes it on to account #2. + tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, uint256.NewInt(1000), params.TxGas, nil, nil), *signer, testKey) + tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, uint256.NewInt(1000), params.TxGas, nil, nil), *signer, acc1Key) + block.AddTx(tx1) + block.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by account #2. + block.SetCoinbase(acc2Addr) + block.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := block.PrevBlock(1).Header() + b2.Extra = []byte("foo") + block.AddUncle(b2) + b3 := block.PrevBlock(2).Header() + b3.Extra = []byte("foo") + block.AddUncle(b3) + } + } + // Assemble the test environment + m := mockWithGenerator(t, 4, generator) + agg := m.HistoryV3Components() + br := snapshotsync.NewBlockReaderWithSnapshots(m.BlockSnapshots) + stateCache := kvcache.New(kvcache.DefaultCoherentConfig) + api := NewErigonAPI(NewBaseApi(nil, stateCache, br, agg, false, rpccfg.DefaultEvmCallTimeout, m.Engine), m.DB, nil) + + err := m.DB.View(m.Ctx, func(tx kv.Tx) error { + for i := uint64(0); i <= rawdb.ReadCurrentHeader(tx).Number.Uint64(); i++ { + block, err := rawdb.ReadBlockByNumber(tx, i) + if err != nil { + return err + } + + r, err := rawdb.ReadReceiptsByHash(tx, block.Hash()) + if err != nil { + return err + } + + marshaledReceipts := make([]map[string]interface{}, 0, len(r)) + for _, receipt := range r { + txn := block.Transactions()[receipt.TransactionIndex] + marshaledReceipts = append(marshaledReceipts, marshalReceipt(receipt, txn, m.ChainConfig, block, txn.Hash(), true)) + } + + receiptsFromBlock, err := api.GetBlockReceiptsByBlockHash(context.Background(), block.Hash()) + if err != nil { + return err + } + + assert.EqualValues(t, marshaledReceipts, receiptsFromBlock) + } + return nil + }) + + require.NoError(t, err) +} + +// newTestBackend creates a chain with a number of explicitly defined blocks and +// wraps it into a mock backend. +func mockWithGenerator(t *testing.T, blocks int, generator func(int, *core.BlockGen)) *stages.MockSentry { + m := stages.MockWithGenesis(t, &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddr: {Balance: big.NewInt(1000000)}}, + }, testKey, false) + if blocks > 0 { + chain, _ := core.GenerateChain(m.ChainConfig, m.Genesis, m.Engine, m.DB, blocks, generator, true) + err := m.InsertChain(chain) + require.NoError(t, err) + } + return m +}