mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-17 18:30:45 +00:00
all: avoid storing computable receipt metadata (#19345)
This commit is contained in:
parent
92f0d07e6d
commit
2b8b7e68f7
8 changed files with 125 additions and 29 deletions
|
|
@ -101,12 +101,19 @@ const (
|
||||||
|
|
||||||
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
|
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
|
||||||
//
|
//
|
||||||
// During the process of upgrading the database version from 3 to 4,
|
// Changelog:
|
||||||
// the following incompatible database changes were added.
|
//
|
||||||
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
|
// - Version 4
|
||||||
// * the `Bloom` field of receipt is deleted
|
// The following incompatible database changes were added:
|
||||||
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
|
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
|
||||||
BlockChainVersion uint64 = 4
|
// * 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
|
// Maximum length of chain to cache by block's number
|
||||||
blocksHashCacheLimit = 900
|
blocksHashCacheLimit = 900
|
||||||
|
|
@ -1226,7 +1233,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||||
}
|
}
|
||||||
// Compute all the non-consensus fields of the receipts
|
// Compute all the non-consensus fields of the receipts
|
||||||
if err := receipts.DeriveFields(bc.chainConfig, blockHash, blockNumber, block.BaseFee(), block.Transactions()); err != nil {
|
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
|
// Write all the data out into the database
|
||||||
rawdb.WriteBody(batch, blockHash, blockNumber, block.Body())
|
rawdb.WriteBody(batch, blockHash, blockNumber, block.Body())
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package rawdb
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -339,22 +340,48 @@ func TestBlockReceiptStorage(t *testing.T) {
|
||||||
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
|
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
|
||||||
t.Fatalf("no receipts returned")
|
t.Fatalf("no receipts returned")
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < len(receipts); i++ {
|
if err := checkReceiptsRLP(rs, receipts); err != nil {
|
||||||
rlpHave, _ := rlp.EncodeToBytes(rs[i])
|
t.Fatalf("fail to checkReceiptsRLP %v", err)
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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)
|
DeleteReceipts(db, hash, 0)
|
||||||
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
|
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
|
||||||
t.Fatalf("deleted receipts returned: %v", rs)
|
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.
|
// Tests that logs associated with a single block can be retrieved.
|
||||||
func TestReadLogs(t *testing.T) {
|
func TestReadLogs(t *testing.T) {
|
||||||
db := NewMemoryDatabase()
|
db := NewMemoryDatabase()
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ func ReadReceipt(db ethdb.Reader, hash common.Hash, config *params.ChainConfig)
|
||||||
if blockNumber == nil {
|
if blockNumber == nil {
|
||||||
return nil, common.Hash{}, 0, 0
|
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)
|
receipts := ReadReceipts(db, blockHash, *blockNumber, config)
|
||||||
for receiptIndex, receipt := range receipts {
|
for receiptIndex, receipt := range receipts {
|
||||||
if receipt.TxHash == hash {
|
if receipt.TxHash == hash {
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,13 @@ type receiptRLP struct {
|
||||||
Logs []*Log
|
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.
|
// v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4.
|
||||||
type v4StoredReceiptRLP struct {
|
type v4StoredReceiptRLP struct {
|
||||||
PostStateOrStatus []byte
|
PostStateOrStatus []byte
|
||||||
|
|
@ -276,13 +283,10 @@ type ReceiptForStorage Receipt
|
||||||
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
|
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
|
||||||
// into an RLP stream.
|
// into an RLP stream.
|
||||||
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
|
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
|
||||||
enc := &v4StoredReceiptRLP{
|
enc := &storedReceiptRLP{
|
||||||
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
|
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
|
||||||
CumulativeGasUsed: r.CumulativeGasUsed,
|
CumulativeGasUsed: r.CumulativeGasUsed,
|
||||||
TxHash: r.TxHash,
|
|
||||||
ContractAddress: r.ContractAddress,
|
|
||||||
Logs: make([]*LogForStorage, len(r.Logs)),
|
Logs: make([]*LogForStorage, len(r.Logs)),
|
||||||
GasUsed: r.GasUsed,
|
|
||||||
}
|
}
|
||||||
for i, log := range r.Logs {
|
for i, log := range r.Logs {
|
||||||
enc.Logs[i] = (*LogForStorage)(log)
|
enc.Logs[i] = (*LogForStorage)(log)
|
||||||
|
|
@ -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
|
// 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
|
// 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).
|
// 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 {
|
if err := decodeV3StoredReceiptRLP(r, blob); err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return decodeV4StoredReceiptRLP(r, blob)
|
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 {
|
func decodeV4StoredReceiptRLP(r *ReceiptForStorage, blob []byte) error {
|
||||||
var stored v4StoredReceiptRLP
|
var stored v4StoredReceiptRLP
|
||||||
if err := rlp.DecodeBytes(blob, &stored); err != nil {
|
if err := rlp.DecodeBytes(blob, &stored); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,14 @@ func TestLegacyReceiptDecoding(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
encode func(*Receipt) ([]byte, error)
|
encode func(*Receipt) ([]byte, error)
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
"StoredReceiptRLP",
|
||||||
|
encodeAsStoredReceiptRLP,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"V4StoredReceiptRLP",
|
||||||
|
encodeAsV4StoredReceiptRLP,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"V3StoredReceiptRLP",
|
"V3StoredReceiptRLP",
|
||||||
encodeAsV3StoredReceiptRLP,
|
encodeAsV3StoredReceiptRLP,
|
||||||
|
|
@ -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) {
|
func encodeAsV3StoredReceiptRLP(want *Receipt) ([]byte, error) {
|
||||||
stored := &v3StoredReceiptRLP{
|
stored := &v3StoredReceiptRLP{
|
||||||
PostStateOrStatus: want.statusEncoding(),
|
PostStateOrStatus: want.statusEncoding(),
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||||
|
|
||||||
"github.com/XinFinOrg/XDPoSChain/common"
|
"github.com/XinFinOrg/XDPoSChain/common"
|
||||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||||
"github.com/XinFinOrg/XDPoSChain/core"
|
"github.com/XinFinOrg/XDPoSChain/core"
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,9 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
lpm.synchronise(lpeer)
|
lpm.synchronise(lpeer)
|
||||||
|
|
||||||
test := func(expFail uint64) {
|
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++ {
|
for i := uint64(0); i <= pm.blockchain.CurrentHeader().Number.Uint64(); i++ {
|
||||||
bhash := rawdb.ReadCanonicalHash(db, i)
|
bhash := rawdb.ReadCanonicalHash(db, i)
|
||||||
b1 := fn(light.NoOdr, db, pm.chainConfig, pm.blockchain.(*core.BlockChain), nil, bhash)
|
b1 := fn(light.NoOdr, db, pm.chainConfig, pm.blockchain.(*core.BlockChain), nil, bhash)
|
||||||
|
|
@ -208,10 +211,10 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
eq := bytes.Equal(b1, b2)
|
eq := bytes.Equal(b1, b2)
|
||||||
exp := i < expFail
|
exp := i < expFail
|
||||||
if exp && !eq {
|
if exp && !eq {
|
||||||
t.Errorf("odr mismatch")
|
t.Fatalf("odr mismatch: have %x, want %x", b2, b1)
|
||||||
}
|
}
|
||||||
if !exp && eq {
|
if !exp && eq {
|
||||||
t.Errorf("unexpected odr match")
|
t.Fatalf("unexpected odr match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,6 +224,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
peers.Unregister(lpeer.id)
|
peers.Unregister(lpeer.id)
|
||||||
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
||||||
test(expFail)
|
test(expFail)
|
||||||
|
|
||||||
// expect all retrievals to pass
|
// expect all retrievals to pass
|
||||||
peers.Register(lpeer)
|
peers.Register(lpeer)
|
||||||
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
||||||
|
|
@ -228,6 +232,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
lpeer.hasBlock = func(common.Hash, uint64) bool { return true }
|
lpeer.hasBlock = func(common.Hash, uint64) bool { return true }
|
||||||
lpeer.lock.Unlock()
|
lpeer.lock.Unlock()
|
||||||
test(5)
|
test(5)
|
||||||
|
|
||||||
// still expect all retrievals to pass, now data should be cached locally
|
// still expect all retrievals to pass, now data should be cached locally
|
||||||
peers.Unregister(lpeer.id)
|
peers.Unregister(lpeer.id)
|
||||||
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed
|
||||||
|
|
|
||||||
|
|
@ -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
|
// GetBlockLogs retrieves the logs generated by the transactions included in a
|
||||||
// block given by its hash. Logs will be filled in with context data.
|
// 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) {
|
func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
||||||
receipts, _ := GetBlockReceipts(ctx, odr, hash, number)
|
// Retrieve the potentially incomplete receipts from disk or network
|
||||||
if receipts == nil {
|
receipts, err := GetBlockReceipts(ctx, odr, hash, number)
|
||||||
r := &ReceiptsRequest{Hash: hash, Number: number}
|
if err != nil {
|
||||||
if err := odr.Retrieve(ctx, r); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
receipts = r.Receipts
|
|
||||||
}
|
}
|
||||||
// Return the logs without deriving any computed fields on the receipts
|
// Return the logs without deriving any computed fields on the receipts
|
||||||
logs := make([][]*types.Log, len(receipts))
|
logs := make([][]*types.Log, len(receipts))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue