core: implement GetReceiptByIndex

this is an attempt at reducing wasted effort in GetReceiptByHash by
not decoding or deriving unrelated receipts
This commit is contained in:
Ömer Faruk IRMAK 2025-06-19 17:25:49 +03:00
parent 846d13a31a
commit 83366fdf5c
10 changed files with 399 additions and 57 deletions

View file

@ -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,42 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
return return
} }
// GetReceiptByIndex allows fetching a receipt for a transaction that was already
// looked up on the index.
func (bc *BlockChain) GetReceiptByIndex(tx *types.Transaction, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, error) {
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.ReadRawReceipt(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 {

View file

@ -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
@ -1016,8 +1019,17 @@ func testChainTxReorgs(t *testing.T, scheme string) {
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { if txn, _, _, _ := rawdb.ReadTransaction(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.ReadReceipt(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.ReadRawReceipt(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
@ -1025,8 +1037,17 @@ func testChainTxReorgs(t *testing.T, scheme string) {
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { if txn, _, _, _ := rawdb.ReadTransaction(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.ReadReceipt(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.ReadRawReceipt(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.GetReceiptByIndex(body.Transactions[indx], receipt.BlockHash,
receipt.BlockNumber.Uint64(), uint64(indx))
assert.NoError(t, err)
assert.Equal(t, receipt, receiptByLookup)
}
}
}
}
func TestGetReceiptByIndex(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.GetReceiptByIndex(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)
}
} }
} }
} }

View file

@ -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"
@ -220,7 +221,89 @@ 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
}
// ReadRawReceipt 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.
func ReadRawReceipt(db ethdb.Reader, blockHash common.Hash, blockNumber, txIndex uint64) (*types.Receipt, RawReceiptContext, error) {
receiptIt, err := rlp.NewListIterator(ReadReceiptsRLP(db, blockHash, blockNumber))
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 +330,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 +401,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

View file

@ -17,6 +17,7 @@
package rawdb package rawdb
import ( import (
"errors"
"math/big" "math/big"
"testing" "testing"
@ -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)
}
}
}
}

View file

@ -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
} }

View file

@ -276,6 +276,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) GetReceiptByIndex(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
return b.eth.blockchain.GetReceiptByIndex(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
} }

View file

@ -1386,15 +1386,10 @@ func (api *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash commo
if err != nil { if err != nil {
return nil, err return nil, err
} }
receipts, err := api.b.GetReceipts(ctx, blockHash) receipt, err := api.b.GetReceiptByIndex(tx, blockHash, blockNumber, index)
if err != nil { if err != nil {
return nil, err 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) signer := types.MakeSigner(api.b.ChainConfig(), header.Number, header.Time)
return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil return marshalReceipt(receipt, blockHash, blockNumber, signer, tx, int(index)), nil

View file

@ -588,7 +588,10 @@ func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) er
} }
func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) { func (b testBackend) GetTransaction(txHash common.Hash) (bool, *types.Transaction, common.Hash, uint64, uint64) {
tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash)
return true, tx, blockHash, blockNumber, index return tx != nil, tx, blockHash, blockNumber, index
}
func (b testBackend) GetReceiptByIndex(tx *types.Transaction, blockHash common.Hash, blockNumber, blockIndex uint64) (*types.Receipt, error) {
return b.chain.GetReceiptByIndex(tx, blockHash, blockNumber, blockIndex)
} }
func (b testBackend) TxIndexDone() bool { func (b testBackend) TxIndexDone() bool {
return true return true

View file

@ -68,6 +68,7 @@ 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)
GetReceiptByIndex(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

View file

@ -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) GetReceiptByIndex(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
} }