Skip to content

Commit 78d90c4

Browse files
authored
Merge pull request #19345 from Matthalp/optimize-receipt-storage
core, eth, les, light: avoid storing computable receipt metadata
2 parents 73fc65b + ce9a289 commit 78d90c4

20 files changed

+558
-152
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
160160

161161
// TransactionReceipt returns the receipt of a transaction.
162162
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
163-
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash)
163+
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config)
164164
return receipt, nil
165165
}
166166

@@ -464,15 +464,15 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ
464464
if number == nil {
465465
return nil, nil
466466
}
467-
return rawdb.ReadReceipts(fb.db, hash, *number), nil
467+
return rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config()), nil
468468
}
469469

470470
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
471471
number := rawdb.ReadHeaderNumber(fb.db, hash)
472472
if number == nil {
473473
return nil, nil
474474
}
475-
receipts := rawdb.ReadReceipts(fb.db, hash, *number)
475+
receipts := rawdb.ReadReceipts(fb.db, hash, *number, fb.bc.Config())
476476
if receipts == nil {
477477
return nil, nil
478478
}

core/bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
297297
if full {
298298
hash := header.Hash()
299299
rawdb.ReadBody(db, hash, n)
300-
rawdb.ReadReceipts(db, hash, n)
300+
rawdb.ReadReceipts(db, hash, n, chain.Config())
301301
}
302302
}
303303
chain.Stop()

core/blockchain.go

Lines changed: 18 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ import (
3535
"github.com/ethereum/go-ethereum/core/state"
3636
"github.com/ethereum/go-ethereum/core/types"
3737
"github.com/ethereum/go-ethereum/core/vm"
38-
"github.com/ethereum/go-ethereum/crypto"
3938
"github.com/ethereum/go-ethereum/ethdb"
4039
"github.com/ethereum/go-ethereum/event"
4140
"github.com/ethereum/go-ethereum/log"
4241
"github.com/ethereum/go-ethereum/metrics"
4342
"github.com/ethereum/go-ethereum/params"
4443
"github.com/ethereum/go-ethereum/rlp"
4544
"github.com/ethereum/go-ethereum/trie"
46-
lru "github.com/hashicorp/golang-lru"
45+
"github.com/hashicorp/golang-lru"
4746
)
4847

4948
var (
@@ -79,12 +78,19 @@ const (
7978

8079
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
8180
//
82-
// During the process of upgrading the database version from 3 to 4,
83-
// the following incompatible database changes were added.
84-
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
85-
// * the `Bloom` field of receipt is deleted
86-
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
87-
BlockChainVersion uint64 = 4
81+
// Changelog:
82+
//
83+
// - Version 4
84+
// The following incompatible database changes were added:
85+
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
86+
// * the `Bloom` field of receipt is deleted
87+
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
88+
// - Version 5
89+
// The following incompatible database changes were added:
90+
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt
91+
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the
92+
// receipts' corresponding block
93+
BlockChainVersion uint64 = 5
8894
)
8995

9096
// CacheConfig contains the configuration values for the trie caching/pruning
@@ -647,7 +653,7 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
647653
if number == nil {
648654
return nil
649655
}
650-
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
656+
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
651657
if receipts == nil {
652658
return nil
653659
}
@@ -784,49 +790,6 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
784790
}
785791
}
786792

787-
// SetReceiptsData computes all the non-consensus fields of the receipts
788-
func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) error {
789-
signer := types.MakeSigner(config, block.Number())
790-
791-
transactions, logIndex := block.Transactions(), uint(0)
792-
if len(transactions) != len(receipts) {
793-
return errors.New("transaction and receipt count mismatch")
794-
}
795-
796-
for j := 0; j < len(receipts); j++ {
797-
// The transaction hash can be retrieved from the transaction itself
798-
receipts[j].TxHash = transactions[j].Hash()
799-
800-
// block location fields
801-
receipts[j].BlockHash = block.Hash()
802-
receipts[j].BlockNumber = block.Number()
803-
receipts[j].TransactionIndex = uint(j)
804-
805-
// The contract address can be derived from the transaction itself
806-
if transactions[j].To() == nil {
807-
// Deriving the signer is expensive, only do if it's actually needed
808-
from, _ := types.Sender(signer, transactions[j])
809-
receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce())
810-
}
811-
// The used gas can be calculated based on previous receipts
812-
if j == 0 {
813-
receipts[j].GasUsed = receipts[j].CumulativeGasUsed
814-
} else {
815-
receipts[j].GasUsed = receipts[j].CumulativeGasUsed - receipts[j-1].CumulativeGasUsed
816-
}
817-
// The derived log fields can simply be set from the block and transaction
818-
for k := 0; k < len(receipts[j].Logs); k++ {
819-
receipts[j].Logs[k].BlockNumber = block.NumberU64()
820-
receipts[j].Logs[k].BlockHash = block.Hash()
821-
receipts[j].Logs[k].TxHash = receipts[j].TxHash
822-
receipts[j].Logs[k].TxIndex = uint(j)
823-
receipts[j].Logs[k].Index = logIndex
824-
logIndex++
825-
}
826-
}
827-
return nil
828-
}
829-
830793
// InsertReceiptChain attempts to complete an already existing header chain with
831794
// transaction and receipt data.
832795
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
@@ -865,8 +828,8 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
865828
continue
866829
}
867830
// Compute all the non-consensus fields of the receipts
868-
if err := SetReceiptsData(bc.chainConfig, block, receipts); err != nil {
869-
return i, fmt.Errorf("failed to set receipts data: %v", err)
831+
if err := receipts.DeriveFields(bc.chainConfig, block.Hash(), block.NumberU64(), block.Transactions()); err != nil {
832+
return i, fmt.Errorf("failed to derive receipts data: %v", err)
870833
}
871834
// Write all the data out into the database
872835
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
@@ -1488,7 +1451,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
14881451
if number == nil {
14891452
return
14901453
}
1491-
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
1454+
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
14921455
for _, receipt := range receipts {
14931456
for _, log := range receipt.Logs {
14941457
l := *log

core/blockchain_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ func TestFastVsFullChains(t *testing.T) {
658658
} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) {
659659
t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles())
660660
}
661-
if freceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash)), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
661+
if freceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash), fast.Config()), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash), archive.Config()); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
662662
t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts)
663663
}
664664
}
@@ -843,7 +843,7 @@ func TestChainTxReorgs(t *testing.T) {
843843
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil {
844844
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
845845
}
846-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt != nil {
846+
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt != nil {
847847
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
848848
}
849849
}
@@ -852,7 +852,7 @@ func TestChainTxReorgs(t *testing.T) {
852852
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
853853
t.Errorf("add %d: expected tx to be found", i)
854854
}
855-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil {
855+
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
856856
t.Errorf("add %d: expected receipt to be found", i)
857857
}
858858
}
@@ -861,7 +861,7 @@ func TestChainTxReorgs(t *testing.T) {
861861
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
862862
t.Errorf("share %d: expected tx to be found", i)
863863
}
864-
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil {
864+
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash(), blockchain.Config()); rcpt == nil {
865865
t.Errorf("share %d: expected receipt to be found", i)
866866
}
867867
}

core/chain_makers.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
104104
b.receipts = append(b.receipts, receipt)
105105
}
106106

107+
// AddUncheckedTx forcefully adds a transaction to the block without any
108+
// validation.
109+
//
110+
// AddUncheckedTx will cause consensus failures when used during real
111+
// chain processing. This is best used in conjunction with raw block insertion.
112+
func (b *BlockGen) AddUncheckedTx(tx *types.Transaction) {
113+
b.txs = append(b.txs, tx)
114+
}
115+
107116
// Number returns the block number of the block being generated.
108117
func (b *BlockGen) Number() *big.Int {
109118
return new(big.Int).Set(b.header.Number)

core/rawdb/accessors_chain.go

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/ethereum/go-ethereum/core/types"
2626
"github.com/ethereum/go-ethereum/ethdb"
2727
"github.com/ethereum/go-ethereum/log"
28+
"github.com/ethereum/go-ethereum/params"
2829
"github.com/ethereum/go-ethereum/rlp"
2930
)
3031

@@ -299,8 +300,10 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa
299300
return data
300301
}
301302

302-
// ReadReceipts retrieves all the transaction receipts belonging to a block.
303-
func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts {
303+
// ReadRawReceipts retrieves all the transaction receipts belonging to a block.
304+
// The receipt metadata fields are not guaranteed to be populated, so they
305+
// should not be used. Use ReadReceipts instead if the metadata is needed.
306+
func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts {
304307
// Retrieve the flattened receipt slice
305308
data := ReadReceiptsRLP(db, hash, number)
306309
if len(data) == 0 {
@@ -313,21 +316,33 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receip
313316
return nil
314317
}
315318
receipts := make(types.Receipts, len(storageReceipts))
316-
logIndex := uint(0)
317-
for i, receipt := range storageReceipts {
318-
// Assemble deriving fields for log.
319-
for _, log := range receipt.Logs {
320-
log.TxHash = receipt.TxHash
321-
log.BlockHash = hash
322-
log.BlockNumber = number
323-
log.TxIndex = uint(i)
324-
log.Index = logIndex
325-
logIndex += 1
326-
}
327-
receipts[i] = (*types.Receipt)(receipt)
328-
receipts[i].BlockHash = hash
329-
receipts[i].BlockNumber = big.NewInt(0).SetUint64(number)
330-
receipts[i].TransactionIndex = uint(i)
319+
for i, storageReceipt := range storageReceipts {
320+
receipts[i] = (*types.Receipt)(storageReceipt)
321+
}
322+
return receipts
323+
}
324+
325+
// ReadReceipts retrieves all the transaction receipts belonging to a block, including
326+
// its correspoinding metadata fields. If it is unable to populate these metadata
327+
// fields then nil is returned.
328+
//
329+
// The current implementation populates these metadata fields by reading the receipts'
330+
// corresponding block body, so if the block body is not found it will return nil even
331+
// if the receipt itself is stored.
332+
func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) types.Receipts {
333+
// We're deriving many fields from the block body, retrieve beside the receipt
334+
receipts := ReadRawReceipts(db, hash, number)
335+
if receipts == nil {
336+
return nil
337+
}
338+
body := ReadBody(db, hash, number)
339+
if body == nil {
340+
log.Error("Missing body but have receipt", "hash", hash, "number", number)
341+
return nil
342+
}
343+
if err := receipts.DeriveFields(config, hash, number, body.Transactions); err != nil {
344+
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
345+
return nil
331346
}
332347
return receipts
333348
}

core/rawdb/accessors_chain_test.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ package rawdb
1818

1919
import (
2020
"bytes"
21+
"encoding/hex"
22+
"fmt"
2123
"math/big"
2224
"testing"
2325

2426
"github.com/ethereum/go-ethereum/common"
2527
"github.com/ethereum/go-ethereum/core/types"
28+
"github.com/ethereum/go-ethereum/params"
2629
"github.com/ethereum/go-ethereum/rlp"
2730
"golang.org/x/crypto/sha3"
2831
)
@@ -267,26 +270,34 @@ func TestHeadStorage(t *testing.T) {
267270
func TestBlockReceiptStorage(t *testing.T) {
268271
db := NewMemoryDatabase()
269272

273+
// Create a live block since we need metadata to reconstruct the receipt
274+
tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
275+
tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
276+
277+
body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
278+
279+
// Create the two receipts to manage afterwards
270280
receipt1 := &types.Receipt{
271281
Status: types.ReceiptStatusFailed,
272282
CumulativeGasUsed: 1,
273283
Logs: []*types.Log{
274284
{Address: common.BytesToAddress([]byte{0x11})},
275285
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
276286
},
277-
TxHash: common.BytesToHash([]byte{0x11, 0x11}),
287+
TxHash: tx1.Hash(),
278288
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
279289
GasUsed: 111111,
280290
}
281291
receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
292+
282293
receipt2 := &types.Receipt{
283294
PostState: common.Hash{2}.Bytes(),
284295
CumulativeGasUsed: 2,
285296
Logs: []*types.Log{
286297
{Address: common.BytesToAddress([]byte{0x22})},
287298
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
288299
},
289-
TxHash: common.BytesToHash([]byte{0x22, 0x22}),
300+
TxHash: tx2.Hash(),
290301
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
291302
GasUsed: 222222,
292303
}
@@ -295,26 +306,55 @@ func TestBlockReceiptStorage(t *testing.T) {
295306

296307
// Check that no receipt entries are in a pristine database
297308
hash := common.BytesToHash([]byte{0x03, 0x14})
298-
if rs := ReadReceipts(db, hash, 0); len(rs) != 0 {
309+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
299310
t.Fatalf("non existent receipts returned: %v", rs)
300311
}
312+
// Insert the body that corresponds to the receipts
313+
WriteBody(db, hash, 0, body)
314+
301315
// Insert the receipt slice into the database and check presence
302316
WriteReceipts(db, hash, 0, receipts)
303-
if rs := ReadReceipts(db, hash, 0); len(rs) == 0 {
317+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) == 0 {
304318
t.Fatalf("no receipts returned")
305319
} else {
306-
for i := 0; i < len(receipts); i++ {
307-
rlpHave, _ := rlp.EncodeToBytes(rs[i])
308-
rlpWant, _ := rlp.EncodeToBytes(receipts[i])
309-
310-
if !bytes.Equal(rlpHave, rlpWant) {
311-
t.Fatalf("receipt #%d: receipt mismatch: have %v, want %v", i, rs[i], receipts[i])
312-
}
320+
if err := checkReceiptsRLP(rs, receipts); err != nil {
321+
t.Fatalf(err.Error())
313322
}
314323
}
315-
// Delete the receipt slice and check purge
324+
// Delete the body and ensure that the receipts are no longer returned (metadata can't be recomputed)
325+
DeleteBody(db, hash, 0)
326+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); rs != nil {
327+
t.Fatalf("receipts returned when body was deleted: %v", rs)
328+
}
329+
// Ensure that receipts without metadata can be returned without the block body too
330+
if err := checkReceiptsRLP(ReadRawReceipts(db, hash, 0), receipts); err != nil {
331+
t.Fatalf(err.Error())
332+
}
333+
// Sanity check that body alone without the receipt is a full purge
334+
WriteBody(db, hash, 0, body)
335+
316336
DeleteReceipts(db, hash, 0)
317-
if rs := ReadReceipts(db, hash, 0); len(rs) != 0 {
337+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
318338
t.Fatalf("deleted receipts returned: %v", rs)
319339
}
320340
}
341+
342+
func checkReceiptsRLP(have, want types.Receipts) error {
343+
if len(have) != len(want) {
344+
return fmt.Errorf("receipts sizes mismatch: have %d, want %d", len(have), len(want))
345+
}
346+
for i := 0; i < len(want); i++ {
347+
rlpHave, err := rlp.EncodeToBytes(have[i])
348+
if err != nil {
349+
return err
350+
}
351+
rlpWant, err := rlp.EncodeToBytes(want[i])
352+
if err != nil {
353+
return err
354+
}
355+
if !bytes.Equal(rlpHave, rlpWant) {
356+
return fmt.Errorf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
357+
}
358+
}
359+
return nil
360+
}

0 commit comments

Comments
 (0)