mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-18 22:09:26 +00:00
ethapi: reduce some of the wasted effort in GetTransactionReceipt (#32021)
Towards https://github.com/ethereum/go-ethereum/issues/26974 --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
parent
7c180f851c
commit
f70aaa8399
17 changed files with 487 additions and 106 deletions
|
|
@ -314,7 +314,7 @@ type BlockChain struct {
|
||||||
|
|
||||||
bodyCache *lru.Cache[common.Hash, *types.Body]
|
bodyCache *lru.Cache[common.Hash, *types.Body]
|
||||||
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
|
||||||
receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
|
receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Receipts cache with all fields derived
|
||||||
blockCache *lru.Cache[common.Hash, *types.Block]
|
blockCache *lru.Cache[common.Hash, *types.Block]
|
||||||
|
|
||||||
txLookupLock sync.RWMutex
|
txLookupLock sync.RWMutex
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,12 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
|
@ -213,6 +216,44 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCanonicalReceipt allows fetching a receipt for a transaction that was
|
||||||
|
// already looked up on the index. Notably, only receipt in canonical chain
|
||||||
|
// is visible.
|
||||||
|
func (bc *BlockChain) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) {
|
||||||
|
// The receipt retrieved from the cache contains all previously derived fields
|
||||||
|
if receipts, ok := bc.receiptsCache.Get(blockHash); ok {
|
||||||
|
if int(txIndex) >= len(receipts) {
|
||||||
|
return nil, fmt.Errorf("receipt out of index, length: %d, index: %d", len(receipts), txIndex)
|
||||||
|
}
|
||||||
|
return receipts[int(txIndex)], nil
|
||||||
|
}
|
||||||
|
header := bc.GetHeader(blockHash, blockNumber)
|
||||||
|
if header == nil {
|
||||||
|
return nil, fmt.Errorf("block header is not found, %d, %x", blockNumber, blockHash)
|
||||||
|
}
|
||||||
|
var blobGasPrice *big.Int
|
||||||
|
if header.ExcessBlobGas != nil {
|
||||||
|
blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, header)
|
||||||
|
}
|
||||||
|
receipt, ctx, err := rawdb.ReadCanonicalRawReceipt(bc.db, blockHash, blockNumber, txIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signer := types.MakeSigner(bc.chainConfig, new(big.Int).SetUint64(blockNumber), header.Time)
|
||||||
|
receipt.DeriveFields(signer, types.DeriveReceiptContext{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: blockNumber,
|
||||||
|
BlockTime: header.Time,
|
||||||
|
BaseFee: header.BaseFee,
|
||||||
|
BlobGasPrice: blobGasPrice,
|
||||||
|
GasUsed: ctx.GasUsed,
|
||||||
|
LogIndex: ctx.LogIndex,
|
||||||
|
Tx: tx,
|
||||||
|
TxIndex: uint(txIndex),
|
||||||
|
})
|
||||||
|
return receipt, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
||||||
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
||||||
if receipts, ok := bc.receiptsCache.Get(hash); ok {
|
if receipts, ok := bc.receiptsCache.Get(hash); ok {
|
||||||
|
|
@ -277,13 +318,15 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
|
||||||
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
|
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransactionLookup retrieves the lookup along with the transaction
|
// GetCanonicalTransaction retrieves the lookup along with the transaction
|
||||||
// itself associate with the given transaction hash.
|
// itself associate with the given transaction hash.
|
||||||
//
|
//
|
||||||
// A null will be returned if the transaction is not found. This can be due to
|
// A null will be returned if the transaction is not found. This can be due to
|
||||||
// the transaction indexer not being finished. The caller must explicitly check
|
// the transaction indexer not being finished. The caller must explicitly check
|
||||||
// the indexer progress.
|
// the indexer progress.
|
||||||
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
|
//
|
||||||
|
// Notably, only the transaction in the canonical chain is visible.
|
||||||
|
func (bc *BlockChain) GetCanonicalTransaction(hash common.Hash) (*rawdb.LegacyTxLookupEntry, *types.Transaction) {
|
||||||
bc.txLookupLock.RLock()
|
bc.txLookupLock.RLock()
|
||||||
defer bc.txLookupLock.RUnlock()
|
defer bc.txLookupLock.RUnlock()
|
||||||
|
|
||||||
|
|
@ -291,7 +334,7 @@ func (bc *BlockChain) GetTransactionLookup(hash common.Hash) (*rawdb.LegacyTxLoo
|
||||||
if item, exist := bc.txLookupCache.Get(hash); exist {
|
if item, exist := bc.txLookupCache.Get(hash); exist {
|
||||||
return item.lookup, item.transaction
|
return item.lookup, item.transaction
|
||||||
}
|
}
|
||||||
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
|
tx, blockHash, blockNumber, txIndex := rawdb.ReadCanonicalTransaction(bc.db, hash)
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,12 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||||
|
|
@ -46,6 +48,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// So we can deterministically seed different blockchains
|
// So we can deterministically seed different blockchains
|
||||||
|
|
@ -1004,29 +1007,47 @@ func testChainTxReorgs(t *testing.T, scheme string) {
|
||||||
|
|
||||||
// removed tx
|
// removed tx
|
||||||
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
|
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
|
||||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil {
|
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
|
||||||
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
|
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
|
||||||
}
|
}
|
||||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
|
if rcpt, _, _, _ := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
|
||||||
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
|
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// added tx
|
// added tx
|
||||||
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
|
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
|
||||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
|
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
|
||||||
t.Errorf("add %d: expected tx to be found", i)
|
t.Errorf("add %d: expected tx to be found", i)
|
||||||
}
|
}
|
||||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
|
if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
|
||||||
t.Errorf("add %d: expected receipt to be found", i)
|
t.Errorf("add %d: expected receipt to be found", i)
|
||||||
|
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
|
||||||
|
t.Errorf("add %d: expected raw receipt to be found", i)
|
||||||
|
} else {
|
||||||
|
if rcpt.GasUsed != ctx.GasUsed {
|
||||||
|
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
|
||||||
|
}
|
||||||
|
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
|
||||||
|
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// shared tx
|
// shared tx
|
||||||
for i, tx := range (types.Transactions{postponed, swapped}) {
|
for i, tx := range (types.Transactions{postponed, swapped}) {
|
||||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
|
if txn, _, _, _ := rawdb.ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
|
||||||
t.Errorf("share %d: expected tx to be found", i)
|
t.Errorf("share %d: expected tx to be found", i)
|
||||||
}
|
}
|
||||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
|
if rcpt, _, _, index := rawdb.ReadCanonicalReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
|
||||||
t.Errorf("share %d: expected receipt to be found", i)
|
t.Errorf("share %d: expected receipt to be found", i)
|
||||||
|
} else if rawRcpt, ctx, _ := rawdb.ReadCanonicalRawReceipt(db, rcpt.BlockHash, rcpt.BlockNumber.Uint64(), index); rawRcpt == nil {
|
||||||
|
t.Errorf("add %d: expected raw receipt to be found", i)
|
||||||
|
} else {
|
||||||
|
if rcpt.GasUsed != ctx.GasUsed {
|
||||||
|
t.Errorf("add %d, raw gasUsedSoFar doesn't make sense", i)
|
||||||
|
}
|
||||||
|
if len(rcpt.Logs) > 0 && rcpt.Logs[0].Index != ctx.LogIndex {
|
||||||
|
t.Errorf("add %d, raw startingLogIndex doesn't make sense", i)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4404,6 +4425,93 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
|
||||||
if receipts == nil || len(receipts) != 1 {
|
if receipts == nil || len(receipts) != 1 {
|
||||||
t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64())
|
t.Fatalf("Missed block receipts: %d, cutoff: %d", num, cutoffBlock.NumberU64())
|
||||||
}
|
}
|
||||||
|
for indx, receipt := range receipts {
|
||||||
|
receiptByLookup, err := chain.GetCanonicalReceipt(body.Transactions[indx], receipt.BlockHash,
|
||||||
|
receipt.BlockNumber.Uint64(), uint64(indx))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, receipt, receiptByLookup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCanonicalReceipt(t *testing.T) {
|
||||||
|
const chainLength = 64
|
||||||
|
|
||||||
|
// Configure and generate a sample block chain
|
||||||
|
var (
|
||||||
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
funds = big.NewInt(1000000000000000000)
|
||||||
|
gspec = &Genesis{
|
||||||
|
Config: params.MergedTestChainConfig,
|
||||||
|
Alloc: types.GenesisAlloc{address: {Balance: funds}},
|
||||||
|
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||||
|
}
|
||||||
|
signer = types.LatestSigner(gspec.Config)
|
||||||
|
engine = beacon.New(ethash.NewFaker())
|
||||||
|
codeBin = common.FromHex("0x608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033")
|
||||||
|
)
|
||||||
|
_, blocks, receipts := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, block *BlockGen) {
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// pragma solidity ^0.8.0;
|
||||||
|
//
|
||||||
|
// contract ConstructorLogger {
|
||||||
|
// event ConstructorLog(address sender, uint256 timestamp, string message);
|
||||||
|
//
|
||||||
|
// constructor() {
|
||||||
|
// emit ConstructorLog(msg.sender, block.timestamp, "Constructor was called");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// 608060405234801561000f575f5ffd5b507f8ae1c8c6e5f91159d0bc1c4b9a47ce45301753843012cbe641e4456bfc73538b33426040516100419291906100ff565b60405180910390a1610139565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100778261004e565b9050919050565b6100878161006d565b82525050565b5f819050919050565b61009f8161008d565b82525050565b5f82825260208201905092915050565b7f436f6e7374727563746f72207761732063616c6c6564000000000000000000005f82015250565b5f6100e96016836100a5565b91506100f4826100b5565b602082019050919050565b5f6060820190506101125f83018561007e565b61011f6020830184610096565b8181036040830152610130816100dd565b90509392505050565b603e806101455f395ff3fe60806040525f5ffdfea2646970667358221220e8bc3c31e3ac337eab702e8fdfc1c71894f4df1af4221bcde4a2823360f403fb64736f6c634300081e0033
|
||||||
|
nonce := block.TxNonce(address)
|
||||||
|
tx, err := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
block.AddTx(tx)
|
||||||
|
|
||||||
|
tx2, err := types.SignTx(types.NewContractCreation(nonce+1, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
block.AddTx(tx2)
|
||||||
|
|
||||||
|
tx3, err := types.SignTx(types.NewContractCreation(nonce+2, big.NewInt(0), 100_000, block.header.BaseFee, codeBin), signer, key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
block.AddTx(tx3)
|
||||||
|
})
|
||||||
|
|
||||||
|
db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
|
||||||
|
defer db.Close()
|
||||||
|
options := DefaultConfig().WithStateScheme(rawdb.PathScheme)
|
||||||
|
chain, _ := NewBlockChain(db, gspec, beacon.New(ethash.NewFaker()), options)
|
||||||
|
defer chain.Stop()
|
||||||
|
|
||||||
|
chain.InsertReceiptChain(blocks, types.EncodeBlockReceiptLists(receipts), 0)
|
||||||
|
|
||||||
|
for i := 0; i < chainLength; i++ {
|
||||||
|
block := blocks[i]
|
||||||
|
blockReceipts := chain.GetReceiptsByHash(block.Hash())
|
||||||
|
chain.receiptsCache.Purge() // ugly hack
|
||||||
|
for txIndex, tx := range block.Body().Transactions {
|
||||||
|
receipt, err := chain.GetCanonicalReceipt(tx, block.Hash(), block.NumberU64(), uint64(txIndex))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(receipts[i][txIndex], receipt) {
|
||||||
|
want := spew.Sdump(receipts[i][txIndex])
|
||||||
|
got := spew.Sdump(receipt)
|
||||||
|
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(blockReceipts[txIndex], receipt) {
|
||||||
|
want := spew.Sdump(blockReceipts[txIndex])
|
||||||
|
got := spew.Sdump(receipt)
|
||||||
|
t.Fatalf("Receipt is not matched, want %s, got: %s", want, got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -449,9 +449,10 @@ func ReadBodyRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the canonical
|
// ReadCanonicalBodyRLP retrieves the block body (transactions and uncles) for the
|
||||||
// block at number, in RLP encoding.
|
// canonical block at number, in RLP encoding. Optionally it takes the block hash
|
||||||
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
|
// to avoid looking it up
|
||||||
|
func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
|
||||||
var data []byte
|
var data []byte
|
||||||
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
|
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
|
||||||
data, _ = reader.Ancient(ChainFreezerBodiesTable, number)
|
data, _ = reader.Ancient(ChainFreezerBodiesTable, number)
|
||||||
|
|
@ -459,10 +460,14 @@ func ReadCanonicalBodyRLP(db ethdb.Reader, number uint64) rlp.RawValue {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Block is not in ancients, read from leveldb by hash and number.
|
// Block is not in ancients, read from leveldb by hash and number.
|
||||||
// Note: ReadCanonicalHash cannot be used here because it also
|
if hash != nil {
|
||||||
// calls ReadAncients internally.
|
data, _ = db.Get(blockBodyKey(number, *hash))
|
||||||
hash, _ := db.Get(headerHashKey(number))
|
} else {
|
||||||
data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hash)))
|
// Note: ReadCanonicalHash cannot be used here because it also
|
||||||
|
// calls ReadAncients internally.
|
||||||
|
hashBytes, _ := db.Get(headerHashKey(number))
|
||||||
|
data, _ = db.Get(blockBodyKey(number, common.BytesToHash(hashBytes)))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return data
|
return data
|
||||||
|
|
@ -544,6 +549,29 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadCanonicalReceiptsRLP retrieves the receipts RLP for the canonical block at
|
||||||
|
// number, in RLP encoding. Optionally it takes the block hash to avoid looking it up.
|
||||||
|
func ReadCanonicalReceiptsRLP(db ethdb.Reader, number uint64, hash *common.Hash) rlp.RawValue {
|
||||||
|
var data []byte
|
||||||
|
db.ReadAncients(func(reader ethdb.AncientReaderOp) error {
|
||||||
|
data, _ = reader.Ancient(ChainFreezerReceiptTable, number)
|
||||||
|
if len(data) > 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Block is not in ancients, read from leveldb by hash and number.
|
||||||
|
if hash != nil {
|
||||||
|
data, _ = db.Get(blockReceiptsKey(number, *hash))
|
||||||
|
} else {
|
||||||
|
// Note: ReadCanonicalHash cannot be used here because it also
|
||||||
|
// calls ReadAncients internally.
|
||||||
|
hashBytes, _ := db.Get(headerHashKey(number))
|
||||||
|
data, _ = db.Get(blockReceiptsKey(number, common.BytesToHash(hashBytes)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
|
// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
|
||||||
// The receipt metadata fields and the Bloom are not guaranteed to be populated,
|
// The receipt metadata fields and the Bloom are not guaranteed to be populated,
|
||||||
// so they should not be used. Use ReadReceipts instead if the metadata is needed.
|
// so they should not be used. Use ReadReceipts instead if the metadata is needed.
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,9 @@ func TestAncientStorage(t *testing.T) {
|
||||||
if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 {
|
if blob := ReadReceiptsRLP(db, hash, number); len(blob) > 0 {
|
||||||
t.Fatalf("non existent receipts returned")
|
t.Fatalf("non existent receipts returned")
|
||||||
}
|
}
|
||||||
|
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) > 0 {
|
||||||
|
t.Fatalf("non existent receipts returned")
|
||||||
|
}
|
||||||
|
|
||||||
// Write and verify the header in the database
|
// Write and verify the header in the database
|
||||||
WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
|
WriteAncientBlocks(db, []*types.Block{block}, types.EncodeBlockReceiptLists([]types.Receipts{nil}))
|
||||||
|
|
@ -454,6 +457,9 @@ func TestAncientStorage(t *testing.T) {
|
||||||
if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 {
|
if blob := ReadReceiptsRLP(db, hash, number); len(blob) == 0 {
|
||||||
t.Fatalf("no receipts returned")
|
t.Fatalf("no receipts returned")
|
||||||
}
|
}
|
||||||
|
if blob := ReadCanonicalReceiptsRLP(db, number, &hash); len(blob) == 0 {
|
||||||
|
t.Fatalf("no receipts returned")
|
||||||
|
}
|
||||||
|
|
||||||
// Use a fake hash for data retrieval, nothing should be returned.
|
// Use a fake hash for data retrieval, nothing should be returned.
|
||||||
fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03})
|
fakeHash := common.BytesToHash([]byte{0x01, 0x02, 0x03})
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -169,9 +170,10 @@ func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Trans
|
||||||
return nil, 0, errors.New("transaction not found")
|
return nil, 0, errors.New("transaction not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadTransaction retrieves a specific transaction from the database, along with
|
// ReadCanonicalTransaction retrieves a specific transaction from the database, along
|
||||||
// its added positional metadata.
|
// with its added positional metadata. Notably, only the transaction in the canonical
|
||||||
func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
|
// chain is visible.
|
||||||
|
func ReadCanonicalTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
|
||||||
blockNumber := ReadTxLookupEntry(db, hash)
|
blockNumber := ReadTxLookupEntry(db, hash)
|
||||||
if blockNumber == nil {
|
if blockNumber == nil {
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
|
|
@ -180,7 +182,7 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
|
||||||
if blockHash == (common.Hash{}) {
|
if blockHash == (common.Hash{}) {
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
bodyRLP := ReadBodyRLP(db, blockHash, *blockNumber)
|
bodyRLP := ReadCanonicalBodyRLP(db, *blockNumber, &blockHash)
|
||||||
if bodyRLP == nil {
|
if bodyRLP == nil {
|
||||||
log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash)
|
log.Error("Transaction referenced missing", "number", *blockNumber, "hash", blockHash)
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
|
|
@ -193,9 +195,10 @@ func ReadTransaction(db ethdb.Reader, hash common.Hash) (*types.Transaction, com
|
||||||
return tx, blockHash, *blockNumber, txIndex
|
return tx, blockHash, *blockNumber, txIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadReceipt retrieves a specific transaction receipt from the database, along with
|
// ReadCanonicalReceipt retrieves a specific transaction receipt from the database,
|
||||||
// its added positional metadata.
|
// along with its added positional metadata. Notably, only the receipt in the canonical
|
||||||
func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) {
|
// chain is visible.
|
||||||
|
func ReadCanonicalReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig) (*types.Receipt, common.Hash, uint64, uint64) {
|
||||||
// Retrieve the context of the receipt based on the transaction hash
|
// Retrieve the context of the receipt based on the transaction hash
|
||||||
blockNumber := ReadTxLookupEntry(db, hash)
|
blockNumber := ReadTxLookupEntry(db, hash)
|
||||||
if blockNumber == nil {
|
if blockNumber == nil {
|
||||||
|
|
@ -220,7 +223,91 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
|
||||||
return nil, common.Hash{}, 0, 0
|
return nil, common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFilterMapRow retrieves a filter map row at the given mapRowIndex
|
// extractReceiptFields takes a raw RLP-encoded receipt blob and extracts
|
||||||
|
// specific fields from it.
|
||||||
|
func extractReceiptFields(receiptRLP rlp.RawValue) (uint64, uint, error) {
|
||||||
|
receiptList, _, err := rlp.SplitList(receiptRLP)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
// Decode the field: receipt status
|
||||||
|
// for receipt before the byzantium fork:
|
||||||
|
// - bytes: post state root
|
||||||
|
// for receipt after the byzantium fork:
|
||||||
|
// - bytes: receipt status flag
|
||||||
|
_, _, rest, err := rlp.Split(receiptList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
// Decode the field: cumulative gas used (type: uint64)
|
||||||
|
gasUsed, rest, err := rlp.SplitUint64(rest)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
// Decode the field: logs (type: rlp list)
|
||||||
|
logList, _, err := rlp.SplitList(rest)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
logCount, err := rlp.CountValues(logList)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return gasUsed, uint(logCount), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawReceiptContext carries the contextual information that is needed to derive
|
||||||
|
// a complete receipt from a raw one.
|
||||||
|
type RawReceiptContext struct {
|
||||||
|
GasUsed uint64 // Amount of gas used by the associated transaction
|
||||||
|
LogIndex uint // Starting index of the logs within the block
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadCanonicalRawReceipt reads a raw receipt at the specified position. It also
|
||||||
|
// returns the gas used by the associated transaction and the starting index of
|
||||||
|
// the logs within the block. The main difference with ReadCanonicalReceipt is
|
||||||
|
// that the additional positional fields are not directly included in the receipt.
|
||||||
|
// Notably, only receipts from the canonical chain are visible.
|
||||||
|
func ReadCanonicalRawReceipt(db ethdb.Reader, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, RawReceiptContext, error) {
|
||||||
|
receiptIt, err := rlp.NewListIterator(ReadCanonicalReceiptsRLP(db, blockNumber, &blockHash))
|
||||||
|
if err != nil {
|
||||||
|
return nil, RawReceiptContext{}, err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
cumulativeGasUsed uint64
|
||||||
|
logIndex uint
|
||||||
|
)
|
||||||
|
for i := uint64(0); i <= txIndex; i++ {
|
||||||
|
// Unexpected iteration error
|
||||||
|
if receiptIt.Err() != nil {
|
||||||
|
return nil, RawReceiptContext{}, receiptIt.Err()
|
||||||
|
}
|
||||||
|
// Unexpected end of iteration
|
||||||
|
if !receiptIt.Next() {
|
||||||
|
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
|
||||||
|
}
|
||||||
|
if i == txIndex {
|
||||||
|
var stored types.ReceiptForStorage
|
||||||
|
if err := rlp.DecodeBytes(receiptIt.Value(), &stored); err != nil {
|
||||||
|
return nil, RawReceiptContext{}, err
|
||||||
|
}
|
||||||
|
return (*types.Receipt)(&stored), RawReceiptContext{
|
||||||
|
GasUsed: stored.CumulativeGasUsed - cumulativeGasUsed,
|
||||||
|
LogIndex: logIndex,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
gas, logs, err := extractReceiptFields(receiptIt.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nil, RawReceiptContext{}, err
|
||||||
|
}
|
||||||
|
cumulativeGasUsed = gas
|
||||||
|
logIndex += logs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, RawReceiptContext{}, fmt.Errorf("receipt not found, %d, %x, %d", blockNumber, blockHash, txIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFilterMapExtRow retrieves a filter map row at the given mapRowIndex
|
||||||
// (see filtermaps.mapRowIndex for the storage index encoding).
|
// (see filtermaps.mapRowIndex for the storage index encoding).
|
||||||
// Note that zero length rows are not stored in the database and therefore all
|
// Note that zero length rows are not stored in the database and therefore all
|
||||||
// non-existent entries are interpreted as empty rows and return no error.
|
// non-existent entries are interpreted as empty rows and return no error.
|
||||||
|
|
@ -247,7 +334,7 @@ func ReadFilterMapExtRow(db ethdb.KeyValueReader, mapRowIndex uint64, bitLength
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(encRow)%byteLength != 0 {
|
if len(encRow)%byteLength != 0 {
|
||||||
return nil, errors.New("Invalid encoded extended filter row length")
|
return nil, errors.New("invalid encoded extended filter row length")
|
||||||
}
|
}
|
||||||
row := make([]uint32, len(encRow)/byteLength)
|
row := make([]uint32, len(encRow)/byteLength)
|
||||||
var b [4]byte
|
var b [4]byte
|
||||||
|
|
@ -318,7 +405,7 @@ func ReadFilterMapBaseRows(db ethdb.KeyValueReader, mapRowIndex uint64, rowCount
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteFilterMapRow stores a filter map row at the given mapRowIndex or deletes
|
// WriteFilterMapExtRow stores a filter map row at the given mapRowIndex or deletes
|
||||||
// any existing entry if the row is empty.
|
// any existing entry if the row is empty.
|
||||||
func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) {
|
func WriteFilterMapExtRow(db ethdb.KeyValueWriter, mapRowIndex uint64, row []uint32, bitLength uint) {
|
||||||
byteLength := int(bitLength) / 8
|
byteLength := int(bitLength) / 8
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package rawdb
|
package rawdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ func TestLookupStorage(t *testing.T) {
|
||||||
|
|
||||||
// Check that no transactions entries are in a pristine database
|
// Check that no transactions entries are in a pristine database
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil {
|
if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
|
||||||
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
|
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +99,7 @@ func TestLookupStorage(t *testing.T) {
|
||||||
tc.writeTxLookupEntriesByBlock(db, block)
|
tc.writeTxLookupEntriesByBlock(db, block)
|
||||||
|
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil {
|
if txn, hash, number, index := ReadCanonicalTransaction(db, tx.Hash()); txn == nil {
|
||||||
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
|
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
|
||||||
} else {
|
} else {
|
||||||
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
|
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
|
||||||
|
|
@ -112,7 +113,7 @@ func TestLookupStorage(t *testing.T) {
|
||||||
// Delete the transactions and check purge
|
// Delete the transactions and check purge
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
DeleteTxLookupEntry(db, tx.Hash())
|
DeleteTxLookupEntry(db, tx.Hash())
|
||||||
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil {
|
if txn, _, _, _ := ReadCanonicalTransaction(db, tx.Hash()); txn != nil {
|
||||||
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
|
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -219,3 +220,80 @@ func TestFindTxInBlockBody(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractReceiptFields(t *testing.T) {
|
||||||
|
receiptWithPostState := types.ReceiptForStorage(types.Receipt{
|
||||||
|
Type: types.LegacyTxType,
|
||||||
|
PostState: []byte{0x1, 0x2, 0x3},
|
||||||
|
CumulativeGasUsed: 100,
|
||||||
|
})
|
||||||
|
receiptWithPostStateBlob, _ := rlp.EncodeToBytes(&receiptWithPostState)
|
||||||
|
|
||||||
|
receiptNoLogs := types.ReceiptForStorage(types.Receipt{
|
||||||
|
Type: types.LegacyTxType,
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
CumulativeGasUsed: 100,
|
||||||
|
})
|
||||||
|
receiptNoLogBlob, _ := rlp.EncodeToBytes(&receiptNoLogs)
|
||||||
|
|
||||||
|
receiptWithLogs := types.ReceiptForStorage(types.Receipt{
|
||||||
|
Type: types.LegacyTxType,
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
CumulativeGasUsed: 100,
|
||||||
|
Logs: []*types.Log{
|
||||||
|
{
|
||||||
|
Address: common.BytesToAddress([]byte{0x1}),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.BytesToHash([]byte{0x1}),
|
||||||
|
},
|
||||||
|
Data: []byte{0x1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.BytesToAddress([]byte{0x2}),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.BytesToHash([]byte{0x2}),
|
||||||
|
},
|
||||||
|
Data: []byte{0x2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
receiptWithLogBlob, _ := rlp.EncodeToBytes(&receiptWithLogs)
|
||||||
|
|
||||||
|
invalidReceipt := types.ReceiptForStorage(types.Receipt{
|
||||||
|
Type: types.LegacyTxType,
|
||||||
|
Status: types.ReceiptStatusSuccessful,
|
||||||
|
CumulativeGasUsed: 100,
|
||||||
|
})
|
||||||
|
invalidReceiptBlob, _ := rlp.EncodeToBytes(&invalidReceipt)
|
||||||
|
invalidReceiptBlob[len(invalidReceiptBlob)-1] = 0xf
|
||||||
|
|
||||||
|
var cases = []struct {
|
||||||
|
logs rlp.RawValue
|
||||||
|
expErr error
|
||||||
|
expGasUsed uint64
|
||||||
|
expLogs uint
|
||||||
|
}{
|
||||||
|
{receiptWithPostStateBlob, nil, 100, 0},
|
||||||
|
{receiptNoLogBlob, nil, 100, 0},
|
||||||
|
{receiptWithLogBlob, nil, 100, 2},
|
||||||
|
{invalidReceiptBlob, rlp.ErrExpectedList, 100, 0},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
gasUsed, logs, err := extractReceiptFields(c.logs)
|
||||||
|
if c.expErr != nil {
|
||||||
|
if !errors.Is(err, c.expErr) {
|
||||||
|
t.Fatalf("Unexpected error, want: %v, got: %v", c.expErr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %v", err)
|
||||||
|
}
|
||||||
|
if gasUsed != c.expGasUsed {
|
||||||
|
t.Fatalf("Unexpected gas used, want %d, got %d", c.expGasUsed, gasUsed)
|
||||||
|
}
|
||||||
|
if logs != c.expLogs {
|
||||||
|
t.Fatalf("Unexpected logs, want %d, got %d", c.expLogs, logs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool
|
||||||
}
|
}
|
||||||
defer close(rlpCh)
|
defer close(rlpCh)
|
||||||
for n != end {
|
for n != end {
|
||||||
data := ReadCanonicalBodyRLP(db, n)
|
data := ReadCanonicalBodyRLP(db, n, nil)
|
||||||
// Feed the block to the aggregator, or abort on interrupt
|
// Feed the block to the aggregator, or abort on interrupt
|
||||||
select {
|
select {
|
||||||
case rlpCh <- &numberRlp{n, data}:
|
case rlpCh <- &numberRlp{n, data}:
|
||||||
|
|
|
||||||
|
|
@ -258,6 +258,62 @@ func (r *Receipt) Size() common.StorageSize {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeriveReceiptContext holds the contextual information needed to derive a receipt
|
||||||
|
type DeriveReceiptContext struct {
|
||||||
|
BlockHash common.Hash
|
||||||
|
BlockNumber uint64
|
||||||
|
BlockTime uint64
|
||||||
|
BaseFee *big.Int
|
||||||
|
BlobGasPrice *big.Int
|
||||||
|
GasUsed uint64
|
||||||
|
LogIndex uint // Number of logs in the block until this receipt
|
||||||
|
Tx *Transaction
|
||||||
|
TxIndex uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveFields fills the receipt with computed fields based on consensus
|
||||||
|
// data and contextual infos like containing block and transactions.
|
||||||
|
func (r *Receipt) DeriveFields(signer Signer, context DeriveReceiptContext) {
|
||||||
|
// The transaction type and hash can be retrieved from the transaction itself
|
||||||
|
r.Type = context.Tx.Type()
|
||||||
|
r.TxHash = context.Tx.Hash()
|
||||||
|
r.GasUsed = context.GasUsed
|
||||||
|
r.EffectiveGasPrice = context.Tx.inner.effectiveGasPrice(new(big.Int), context.BaseFee)
|
||||||
|
|
||||||
|
// EIP-4844 blob transaction fields
|
||||||
|
if context.Tx.Type() == BlobTxType {
|
||||||
|
r.BlobGasUsed = context.Tx.BlobGas()
|
||||||
|
r.BlobGasPrice = context.BlobGasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block location fields
|
||||||
|
r.BlockHash = context.BlockHash
|
||||||
|
r.BlockNumber = new(big.Int).SetUint64(context.BlockNumber)
|
||||||
|
r.TransactionIndex = context.TxIndex
|
||||||
|
|
||||||
|
// The contract address can be derived from the transaction itself
|
||||||
|
if context.Tx.To() == nil {
|
||||||
|
// Deriving the signer is expensive, only do if it's actually needed
|
||||||
|
from, _ := Sender(signer, context.Tx)
|
||||||
|
r.ContractAddress = crypto.CreateAddress(from, context.Tx.Nonce())
|
||||||
|
} else {
|
||||||
|
r.ContractAddress = common.Address{}
|
||||||
|
}
|
||||||
|
// The derived log fields can simply be set from the block and transaction
|
||||||
|
logIndex := context.LogIndex
|
||||||
|
for j := 0; j < len(r.Logs); j++ {
|
||||||
|
r.Logs[j].BlockNumber = context.BlockNumber
|
||||||
|
r.Logs[j].BlockHash = context.BlockHash
|
||||||
|
r.Logs[j].BlockTimestamp = context.BlockTime
|
||||||
|
r.Logs[j].TxHash = r.TxHash
|
||||||
|
r.Logs[j].TxIndex = context.TxIndex
|
||||||
|
r.Logs[j].Index = logIndex
|
||||||
|
logIndex++
|
||||||
|
}
|
||||||
|
// Also derive the Bloom if not derived yet
|
||||||
|
r.Bloom = CreateBloom(r)
|
||||||
|
}
|
||||||
|
|
||||||
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization
|
// ReceiptForStorage is a wrapper around a Receipt with RLP serialization
|
||||||
// that omits the Bloom field. The Bloom field is recomputed by DeriveFields.
|
// that omits the Bloom field. The Bloom field is recomputed by DeriveFields.
|
||||||
type ReceiptForStorage Receipt
|
type ReceiptForStorage Receipt
|
||||||
|
|
@ -323,58 +379,30 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
|
||||||
|
|
||||||
// DeriveFields fills the receipts with their computed fields based on consensus
|
// DeriveFields fills the receipts with their computed fields based on consensus
|
||||||
// data and contextual infos like containing block and transactions.
|
// data and contextual infos like containing block and transactions.
|
||||||
func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error {
|
func (rs Receipts) DeriveFields(config *params.ChainConfig, blockHash common.Hash, blockNumber uint64, blockTime uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error {
|
||||||
signer := MakeSigner(config, new(big.Int).SetUint64(number), time)
|
signer := MakeSigner(config, new(big.Int).SetUint64(blockNumber), blockTime)
|
||||||
|
|
||||||
logIndex := uint(0)
|
logIndex := uint(0)
|
||||||
if len(txs) != len(rs) {
|
if len(txs) != len(rs) {
|
||||||
return errors.New("transaction and receipt count mismatch")
|
return errors.New("transaction and receipt count mismatch")
|
||||||
}
|
}
|
||||||
for i := 0; i < len(rs); i++ {
|
for i := 0; i < len(rs); i++ {
|
||||||
// The transaction type and hash can be retrieved from the transaction itself
|
var cumulativeGasUsed uint64
|
||||||
rs[i].Type = txs[i].Type()
|
if i > 0 {
|
||||||
rs[i].TxHash = txs[i].Hash()
|
cumulativeGasUsed = rs[i-1].CumulativeGasUsed
|
||||||
rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee)
|
|
||||||
|
|
||||||
// EIP-4844 blob transaction fields
|
|
||||||
if txs[i].Type() == BlobTxType {
|
|
||||||
rs[i].BlobGasUsed = txs[i].BlobGas()
|
|
||||||
rs[i].BlobGasPrice = blobGasPrice
|
|
||||||
}
|
}
|
||||||
|
rs[i].DeriveFields(signer, DeriveReceiptContext{
|
||||||
// block location fields
|
BlockHash: blockHash,
|
||||||
rs[i].BlockHash = hash
|
BlockNumber: blockNumber,
|
||||||
rs[i].BlockNumber = new(big.Int).SetUint64(number)
|
BlockTime: blockTime,
|
||||||
rs[i].TransactionIndex = uint(i)
|
BaseFee: baseFee,
|
||||||
|
BlobGasPrice: blobGasPrice,
|
||||||
// The contract address can be derived from the transaction itself
|
GasUsed: rs[i].CumulativeGasUsed - cumulativeGasUsed,
|
||||||
if txs[i].To() == nil {
|
LogIndex: logIndex,
|
||||||
// Deriving the signer is expensive, only do if it's actually needed
|
Tx: txs[i],
|
||||||
from, _ := Sender(signer, txs[i])
|
TxIndex: uint(i),
|
||||||
rs[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce())
|
})
|
||||||
} else {
|
logIndex += uint(len(rs[i].Logs))
|
||||||
rs[i].ContractAddress = common.Address{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The used gas can be calculated based on previous r
|
|
||||||
if i == 0 {
|
|
||||||
rs[i].GasUsed = rs[i].CumulativeGasUsed
|
|
||||||
} else {
|
|
||||||
rs[i].GasUsed = rs[i].CumulativeGasUsed - rs[i-1].CumulativeGasUsed
|
|
||||||
}
|
|
||||||
|
|
||||||
// The derived log fields can simply be set from the block and transaction
|
|
||||||
for j := 0; j < len(rs[i].Logs); j++ {
|
|
||||||
rs[i].Logs[j].BlockNumber = number
|
|
||||||
rs[i].Logs[j].BlockHash = hash
|
|
||||||
rs[i].Logs[j].BlockTimestamp = time
|
|
||||||
rs[i].Logs[j].TxHash = rs[i].TxHash
|
|
||||||
rs[i].Logs[j].TxIndex = uint(i)
|
|
||||||
rs[i].Logs[j].Index = logIndex
|
|
||||||
logIndex++
|
|
||||||
}
|
|
||||||
// also derive the Bloom if not derived yet
|
|
||||||
rs[i].Bloom = CreateBloom(rs[i])
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,10 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type
|
||||||
return b.eth.blockchain.GetReceiptsByHash(hash), nil
|
return b.eth.blockchain.GetReceiptsByHash(hash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||||
|
return b.eth.blockchain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
||||||
return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
|
return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil
|
||||||
}
|
}
|
||||||
|
|
@ -354,14 +358,16 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction
|
||||||
return b.eth.txPool.Get(hash)
|
return b.eth.txPool.Get(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTransaction retrieves the lookup along with the transaction itself associate
|
// GetCanonicalTransaction retrieves the lookup along with the transaction itself
|
||||||
// with the given transaction hash.
|
// associate with the given transaction hash.
|
||||||
//
|
//
|
||||||
// A null will be returned if the transaction is not found. The transaction is not
|
// A null will be returned if the transaction is not found. The transaction is not
|
||||||
// existent from the node's perspective. This can be due to the transaction indexer
|
// existent from the node's perspective. This can be due to the transaction indexer
|
||||||
// not being finished. The caller must explicitly check the indexer progress.
|
// not being finished. The caller must explicitly check the indexer progress.
|
||||||
func (b *EthAPIBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
//
|
||||||
lookup, tx := b.eth.blockchain.GetTransactionLookup(txHash)
|
// Notably, only the transaction in the canonical chain is visible.
|
||||||
|
func (b *EthAPIBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||||
|
lookup, tx := b.eth.blockchain.GetCanonicalTransaction(txHash)
|
||||||
if lookup == nil || tx == nil {
|
if lookup == nil || tx == nil {
|
||||||
return false, nil, common.Hash{}, 0, 0
|
return false, nil, common.Hash{}, 0, 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ type Backend interface {
|
||||||
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||||
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
|
||||||
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
|
GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
|
||||||
TxIndexDone() bool
|
TxIndexDone() bool
|
||||||
RPCGasCap() uint64
|
RPCGasCap() uint64
|
||||||
ChainConfig() *params.ChainConfig
|
ChainConfig() *params.ChainConfig
|
||||||
|
|
@ -863,7 +863,7 @@ func containsTx(block *types.Block, hash common.Hash) bool {
|
||||||
// TraceTransaction returns the structured logs created during the execution of EVM
|
// TraceTransaction returns the structured logs created during the execution of EVM
|
||||||
// and returns them as a JSON object.
|
// and returns them as a JSON object.
|
||||||
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
|
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) {
|
||||||
found, _, blockHash, blockNumber, index := api.backend.GetTransaction(hash)
|
found, _, blockHash, blockNumber, index := api.backend.GetCanonicalTransaction(hash)
|
||||||
if !found {
|
if !found {
|
||||||
// Warn in case tx indexer is not done.
|
// Warn in case tx indexer is not done.
|
||||||
if !api.backend.TxIndexDone() {
|
if !api.backend.TxIndexDone() {
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber)
|
||||||
return b.chain.GetBlockByNumber(uint64(number)), nil
|
return b.chain.GetBlockByNumber(uint64(number)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
func (b *testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||||
tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash)
|
tx, hash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.chaindb, txHash)
|
||||||
return tx != nil, tx, hash, blockNumber, index
|
return tx != nil, tx, hash, blockNumber, index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block)
|
||||||
return t.tx, t.block
|
return t.tx, t.block
|
||||||
}
|
}
|
||||||
// Try to return an already finalized transaction
|
// Try to return an already finalized transaction
|
||||||
found, tx, blockHash, _, index := t.r.backend.GetTransaction(t.hash)
|
found, tx, blockHash, _, index := t.r.backend.GetCanonicalTransaction(t.hash)
|
||||||
if found {
|
if found {
|
||||||
t.tx = tx
|
t.tx = tx
|
||||||
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
|
blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
|
||||||
|
|
|
||||||
|
|
@ -1334,7 +1334,7 @@ func (api *TransactionAPI) GetTransactionCount(ctx context.Context, address comm
|
||||||
// GetTransactionByHash returns the transaction for the given hash
|
// GetTransactionByHash returns the transaction for the given hash
|
||||||
func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
|
func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) (*RPCTransaction, error) {
|
||||||
// Try to return an already finalized transaction
|
// Try to return an already finalized transaction
|
||||||
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash)
|
found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash)
|
||||||
if !found {
|
if !found {
|
||||||
// No finalized transaction, try to retrieve it from the pool
|
// No finalized transaction, try to retrieve it from the pool
|
||||||
if tx := api.b.GetPoolTransaction(hash); tx != nil {
|
if tx := api.b.GetPoolTransaction(hash); tx != nil {
|
||||||
|
|
@ -1357,7 +1357,7 @@ func (api *TransactionAPI) GetTransactionByHash(ctx context.Context, hash common
|
||||||
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
|
// GetRawTransactionByHash returns the bytes of the transaction for the given hash.
|
||||||
func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
// Retrieve a finalized transaction, or a pooled otherwise
|
// Retrieve a finalized transaction, or a pooled otherwise
|
||||||
found, tx, _, _, _ := api.b.GetTransaction(hash)
|
found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash)
|
||||||
if !found {
|
if !found {
|
||||||
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
||||||
return tx.MarshalBinary()
|
return tx.MarshalBinary()
|
||||||
|
|
@ -1374,7 +1374,7 @@ func (api *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash com
|
||||||
|
|
||||||
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
|
// GetTransactionReceipt returns the transaction receipt for the given transaction hash.
|
||||||
func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
|
func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) {
|
||||||
found, tx, blockHash, blockNumber, index := api.b.GetTransaction(hash)
|
found, tx, blockHash, blockNumber, index := api.b.GetCanonicalTransaction(hash)
|
||||||
if !found {
|
if !found {
|
||||||
// Make sure indexer is done.
|
// Make sure indexer is done.
|
||||||
if !api.b.TxIndexDone() {
|
if !api.b.TxIndexDone() {
|
||||||
|
|
@ -1383,22 +1383,12 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo
|
||||||
// No such tx.
|
// No such tx.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
header, err := api.b.HeaderByHash(ctx, blockHash)
|
receipt, err := api.b.GetCanonicalReceipt(tx, blockHash, blockNumber, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
receipts, err := api.b.GetReceipts(ctx, blockHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if uint64(len(receipts)) <= index {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
receipt := receipts[index]
|
|
||||||
|
|
||||||
// Derive the sender.
|
// Derive the sender.
|
||||||
signer := types.MakeSigner(api.b.ChainConfig(), header.Number, header.Time)
|
return marshalReceipt(receipt, blockHash, blockNumber, api.signer, tx, int(index)), nil
|
||||||
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// marshalReceipt marshals a transaction receipt into a JSON object.
|
// marshalReceipt marshals a transaction receipt into a JSON object.
|
||||||
|
|
@ -1781,7 +1771,7 @@ func (api *DebugAPI) GetRawReceipts(ctx context.Context, blockNrOrHash rpc.Block
|
||||||
// GetRawTransaction returns the bytes of the transaction for the given hash.
|
// GetRawTransaction returns the bytes of the transaction for the given hash.
|
||||||
func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
func (api *DebugAPI) GetRawTransaction(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
// Retrieve a finalized transaction, or a pooled otherwise
|
// Retrieve a finalized transaction, or a pooled otherwise
|
||||||
found, tx, _, _, _ := api.b.GetTransaction(hash)
|
found, tx, _, _, _ := api.b.GetCanonicalTransaction(hash)
|
||||||
if !found {
|
if !found {
|
||||||
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
if tx = api.b.GetPoolTransaction(hash); tx != nil {
|
||||||
return tx.MarshalBinary()
|
return tx.MarshalBinary()
|
||||||
|
|
|
||||||
|
|
@ -586,9 +586,12 @@ func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even
|
||||||
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
func (b testBackend) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||||
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash)
|
tx, blockHash, blockNumber, index := rawdb.ReadCanonicalTransaction(b.db, txHash)
|
||||||
return true, tx, blockHash, blockNumber, index
|
return tx != nil, tx, blockHash, blockNumber, index
|
||||||
|
}
|
||||||
|
func (b testBackend) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||||
|
return b.chain.GetCanonicalReceipt(tx, blockHash, blockNumber, blockIndex)
|
||||||
}
|
}
|
||||||
func (b testBackend) TxIndexDone() bool {
|
func (b testBackend) TxIndexDone() bool {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,14 @@ type Backend interface {
|
||||||
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
|
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
|
||||||
Pending() (*types.Block, types.Receipts, *state.StateDB)
|
Pending() (*types.Block, types.Receipts, *state.StateDB)
|
||||||
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
|
||||||
|
GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error)
|
||||||
GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
|
GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM
|
||||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
||||||
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
|
||||||
|
|
||||||
// Transaction pool API
|
// Transaction pool API
|
||||||
SendTx(ctx context.Context, signedTx *types.Transaction) error
|
SendTx(ctx context.Context, signedTx *types.Transaction) error
|
||||||
GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
|
GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64)
|
||||||
TxIndexDone() bool
|
TxIndexDone() bool
|
||||||
GetPoolTransactions() (types.Transactions, error)
|
GetPoolTransactions() (types.Transactions, error)
|
||||||
GetPoolTransaction(txHash common.Hash) *types.Transaction
|
GetPoolTransaction(txHash common.Hash) *types.Transaction
|
||||||
|
|
|
||||||
|
|
@ -369,6 +369,9 @@ func (b *backendMock) Pending() (*types.Block, types.Receipts, *state.StateDB) {
|
||||||
func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
func (b *backendMock) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
func (b *backendMock) GetCanonicalReceipt(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
|
func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -380,7 +383,7 @@ func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) eve
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
|
func (b *backendMock) SendTx(ctx context.Context, signedTx *types.Transaction) error { return nil }
|
||||||
func (b *backendMock) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
func (b *backendMock) GetCanonicalTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
|
||||||
return false, nil, [32]byte{}, 0, 0
|
return false, nil, [32]byte{}, 0, 0
|
||||||
}
|
}
|
||||||
func (b *backendMock) TxIndexDone() bool { return true }
|
func (b *backendMock) TxIndexDone() bool { return true }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue