diff --git a/core/blockchain.go b/core/blockchain.go index 70aef73bd8..2a735a2685 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 @@ -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()) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index d3d2b0e84a..8d45ed5d2c 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -19,6 +19,7 @@ package rawdb import ( "bytes" "encoding/hex" + "fmt" "math/big" "os" "testing" @@ -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() diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index 68c16660c2..e72a871f12 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -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 { diff --git a/core/types/receipt.go b/core/types/receipt.go index 097c7aab10..54b107eaa0 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -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 @@ -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) @@ -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 { diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index deaf813f10..0936e73403 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -290,6 +290,14 @@ func TestLegacyReceiptDecoding(t *testing.T) { name string encode func(*Receipt) ([]byte, error) }{ + { + "StoredReceiptRLP", + encodeAsStoredReceiptRLP, + }, + { + "V4StoredReceiptRLP", + encodeAsV4StoredReceiptRLP, + }, { "V3StoredReceiptRLP", 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) { stored := &v3StoredReceiptRLP{ PostStateOrStatus: want.statusEncoding(), diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index a578a34f71..e1a87796bb 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -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" diff --git a/les/odr_test.go b/les/odr_test.go index 74e9440982..d47f0ee71f 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -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) @@ -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") } } } @@ -221,6 +224,7 @@ 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 @@ -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.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 diff --git a/light/odr_util.go b/light/odr_util.go index 60e538448b..4dd44f8433 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -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))