Skip to content

Commit

Permalink
all: avoid storing computable receipt metadata (ethereum#19345)
Browse files Browse the repository at this point in the history
  • Loading branch information
gzliudan committed Jan 23, 2025
1 parent ee71745 commit 606ff25
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 29 deletions.
21 changes: 14 additions & 7 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,19 @@ const (

// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
//
// During the process of upgrading the database version from 3 to 4,
// the following incompatible database changes were added.
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
// * the `Bloom` field of receipt is deleted
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
BlockChainVersion uint64 = 4
// Changelog:
//
// - Version 4
// The following incompatible database changes were added:
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
// * the `Bloom` field of receipt is deleted
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
// - Version 5
// The following incompatible database changes were added:
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the
// receipts' corresponding block
BlockChainVersion uint64 = 5

// Maximum length of chain to cache by block's number
blocksHashCacheLimit = 900
Expand Down Expand Up @@ -1226,7 +1233,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
}
// Compute all the non-consensus fields of the receipts
if err := receipts.DeriveFields(bc.chainConfig, blockHash, blockNumber, block.BaseFee(), block.Transactions()); err != nil {
return i, fmt.Errorf("failed to set receipts data: %v", err)
return i, fmt.Errorf("failed to derive receipts data: %v", err)
}
// Write all the data out into the database
rawdb.WriteBody(batch, blockHash, blockNumber, block.Body())
Expand Down
43 changes: 35 additions & 8 deletions core/rawdb/accessors_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package rawdb
import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
"os"
"testing"
Expand Down Expand Up @@ -339,22 +340,48 @@ func TestBlockReceiptStorage(t *testing.T) {
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
t.Fatalf("no receipts returned")
} else {
for i := 0; i < len(receipts); i++ {
rlpHave, _ := rlp.EncodeToBytes(rs[i])
rlpWant, _ := rlp.EncodeToBytes(receipts[i])

if !bytes.Equal(rlpHave, rlpWant) {
t.Fatalf("receipt #%d: receipt mismatch: have %v, want %v", i, rs[i], receipts[i])
}
if err := checkReceiptsRLP(rs, receipts); err != nil {
t.Fatalf("fail to checkReceiptsRLP %v", err)
}
}
// Delete the receipt slice and check purge
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
DeleteBody(db, hash, 0)
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil {
t.Fatalf("receipts returned when body was deleted: %v", rs)
}
// Ensure that receipts without metadata can be returned without the block body too
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
t.Fatalf("fail to checkReceiptsRLP %v", err)
}
// Sanity check that body alone without the receipt is a full purge
WriteBody(db, hash, 0, body)

DeleteReceipts(db, hash, 0)
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
t.Fatalf("deleted receipts returned: %v", rs)
}
}

func checkReceiptsRLP(have, want types.Receipts) error {
if len(have) != len(want) {
return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want))
}
for i := 0; i < len(want); i++ {
rlpHave, err := rlp.EncodeToBytes(have[i])
if err != nil {
return err
}
rlpWant, err := rlp.EncodeToBytes(want[i])
if err != nil {
return err
}
if !bytes.Equal(rlpHave, rlpWant) {
return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
}
}
return nil
}

// Tests that logs associated with a single block can be retrieved.
func TestReadLogs(t *testing.T) {
db := NewMemoryDatabase()
Expand Down
1 change: 1 addition & 0 deletions core/rawdb/accessors_indexes.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
if blockNumber == nil {
return nil, common.Hash{}, 0, 0
}
// Read all the receipts from the block and return the one with the matching hash
receipts := ReadReceipts(db, blockHash, *blockNumber, config)
for receiptIndex, receipt := range receipts {
if receipt.TxHash == hash {
Expand Down
33 changes: 29 additions & 4 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ type receiptRLP struct {
Logs []*Log
}

// storedReceiptRLP is the storage encoding of a receipt.
type storedReceiptRLP struct {
PostStateOrStatus []byte
CumulativeGasUsed uint64
Logs []*LogForStorage
}

// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4.
type v4StoredReceiptRLP struct {
PostStateOrStatus []byte
Expand Down Expand Up @@ -276,13 +283,10 @@ type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
// into an RLP stream.
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
enc := &v4StoredReceiptRLP{
enc := &storedReceiptRLP{
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
CumulativeGasUsed: r.CumulativeGasUsed,
TxHash: r.TxHash,
ContractAddress: r.ContractAddress,
Logs: make([]*LogForStorage, len(r.Logs)),
GasUsed: r.GasUsed,
}
for i, log := range r.Logs {
enc.Logs[i] = (*LogForStorage)(log)
Expand All @@ -301,12 +305,33 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
// Try decoding from the newest format for future proofness, then the older one
// for old nodes that just upgraded. V4 was an intermediate unreleased format so
// we do need to decode it, but it's not common (try last).
if err := decodeStoredReceiptRLP(r, blob); err == nil {
return nil
}
if err := decodeV3StoredReceiptRLP(r, blob); err == nil {
return nil
}
return decodeV4StoredReceiptRLP(r, blob)
}

func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
var stored storedReceiptRLP
if err := rlp.DecodeBytes(blob, &stored); err != nil {
return err
}
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
return err
}
r.CumulativeGasUsed = stored.CumulativeGasUsed
r.Logs = make([]*Log, len(stored.Logs))
for i, log := range stored.Logs {
r.Logs[i] = (*Log)(log)
}
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})

return nil
}

func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
var stored v4StoredReceiptRLP
if err := rlp.DecodeBytes(blob, &stored); err != nil {
Expand Down
35 changes: 35 additions & 0 deletions core/types/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ func TestLegacyReceiptDecoding(t *testing.T) {
name string
encode func(*Receipt) ([]byte, error)
}{
{
"StoredReceiptRLP",
encodeAsStoredReceiptRLP,
},
{
"V4StoredReceiptRLP",
encodeAsV4StoredReceiptRLP,
},
{
"V3StoredReceiptRLP",
encodeAsV3StoredReceiptRLP,
Expand Down Expand Up @@ -356,6 +364,33 @@ func TestLegacyReceiptDecoding(t *testing.T) {
}
}

func encodeAsStoredReceiptRLP(want *Receipt) ([]byte, error) {
stored := &storedReceiptRLP{
PostStateOrStatus: want.statusEncoding(),
CumulativeGasUsed: want.CumulativeGasUsed,
Logs: make([]*LogForStorage, len(want.Logs)),
}
for i, log := range want.Logs {
stored.Logs[i] = (*LogForStorage)(log)
}
return rlp.EncodeToBytes(stored)
}

func encodeAsV4StoredReceiptRLP(want *Receipt) ([]byte, error) {
stored := &v4StoredReceiptRLP{
PostStateOrStatus: want.statusEncoding(),
CumulativeGasUsed: want.CumulativeGasUsed,
TxHash: want.TxHash,
ContractAddress: want.ContractAddress,
Logs: make([]*LogForStorage, len(want.Logs)),
GasUsed: want.GasUsed,
}
for i, log := range want.Logs {
stored.Logs[i] = (*LogForStorage)(log)
}
return rlp.EncodeToBytes(stored)
}

func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
stored := &v3StoredReceiptRLP{
PostStateOrStatus: want.statusEncoding(),
Expand Down
1 change: 0 additions & 1 deletion eth/filters/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"testing"

"github.com/XinFinOrg/XDPoSChain/core/rawdb"

"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
Expand Down
9 changes: 7 additions & 2 deletions les/odr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
lpm.synchronise(lpeer)

test := func(expFail uint64) {
// Mark this as a helper to put the failures at the correct lines
t.Helper()

for i := uint64(0); i <= pm.blockchain.CurrentHeader().Number.Uint64(); i++ {
bhash := rawdb.ReadCanonicalHash(db, i)
b1 := fn(light.NoOdr, db, pm.chainConfig, pm.blockchain.(*core.BlockChain), nil, bhash)
Expand All @@ -208,10 +211,10 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
eq := bytes.Equal(b1, b2)
exp := i < expFail
if exp && !eq {
t.Errorf("odr mismatch")
t.Fatalf("odr mismatch: have %x, want %x", b2, b1)
}
if !exp && eq {
t.Errorf("unexpected odr match")
t.Fatalf("unexpected odr match")
}
}
}
Expand All @@ -221,13 +224,15 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
peers.Unregister(lpeer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
test(expFail)

// expect all retrievals to pass
peers.Register(lpeer)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
lpeer.lock.Lock()
lpeer.hasBlock = func(common.Hash, uint64) bool { return true }
lpeer.lock.Unlock()
test(5)

// still expect all retrievals to pass, now data should be cached locally
peers.Unregister(lpeer.id)
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
Expand Down
11 changes: 4 additions & 7 deletions light/odr_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,10 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
// GetBlockLogs retrieves the logs generated by the transactions included in a
// block given by its hash. Logs will be filled in with context data.
func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
receipts, _ := GetBlockReceipts(ctx, odr, hash, number)
if receipts == nil {
r := &ReceiptsRequest{Hash: hash, Number: number}
if err := odr.Retrieve(ctx, r); err != nil {
return nil, err
}
receipts = r.Receipts
// Retrieve the potentially incomplete receipts from disk or network
receipts, err := GetBlockReceipts(ctx, odr, hash, number)
if err != nil {
return nil, err
}
// Return the logs without deriving any computed fields on the receipts
logs := make([][]*types.Log, len(receipts))
Expand Down

0 comments on commit 606ff25

Please sign in to comment.