Skip to content

Commit 783e97e

Browse files
authored
core/rawdb: avoid unnecessary receipt processing for log filtering (#23147)
* core/types: rm extranous check in test * core/rawdb: add lightweight types for block logs * core/rawdb,eth: use lightweight accessor for log filtering * core/rawdb: add bench for decoding into rlpLogs
1 parent ab2caae commit 783e97e

File tree

5 files changed

+301
-9
lines changed

5 files changed

+301
-9
lines changed

core/rawdb/accessors_chain.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package rawdb
1919
import (
2020
"bytes"
2121
"encoding/binary"
22+
"errors"
2223
"fmt"
2324
"math/big"
2425
"sort"
@@ -663,6 +664,86 @@ func DeleteReceipts(db ethdb.KeyValueWriter, hash common.Hash, number uint64) {
663664
}
664665
}
665666

667+
// storedReceiptRLP is the storage encoding of a receipt.
668+
// Re-definition in core/types/receipt.go.
669+
type storedReceiptRLP struct {
670+
PostStateOrStatus []byte
671+
CumulativeGasUsed uint64
672+
Logs []*types.LogForStorage
673+
}
674+
675+
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
676+
// the list of logs. When decoding a stored receipt into this object we
677+
// avoid creating the bloom filter.
678+
type receiptLogs struct {
679+
Logs []*types.Log
680+
}
681+
682+
// DecodeRLP implements rlp.Decoder.
683+
func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
684+
var stored storedReceiptRLP
685+
if err := s.Decode(&stored); err != nil {
686+
return err
687+
}
688+
r.Logs = make([]*types.Log, len(stored.Logs))
689+
for i, log := range stored.Logs {
690+
r.Logs[i] = (*types.Log)(log)
691+
}
692+
return nil
693+
}
694+
695+
// DeriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc.
696+
func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error {
697+
logIndex := uint(0)
698+
if len(txs) != len(receipts) {
699+
return errors.New("transaction and receipt count mismatch")
700+
}
701+
for i := 0; i < len(receipts); i++ {
702+
txHash := txs[i].Hash()
703+
// The derived log fields can simply be set from the block and transaction
704+
for j := 0; j < len(receipts[i].Logs); j++ {
705+
receipts[i].Logs[j].BlockNumber = number
706+
receipts[i].Logs[j].BlockHash = hash
707+
receipts[i].Logs[j].TxHash = txHash
708+
receipts[i].Logs[j].TxIndex = uint(i)
709+
receipts[i].Logs[j].Index = logIndex
710+
logIndex++
711+
}
712+
}
713+
return nil
714+
}
715+
716+
// ReadLogs retrieves the logs for all transactions in a block. The log fields
717+
// are populated with metadata. In case the receipts or the block body
718+
// are not found, a nil is returned.
719+
func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64) [][]*types.Log {
720+
// Retrieve the flattened receipt slice
721+
data := ReadReceiptsRLP(db, hash, number)
722+
if len(data) == 0 {
723+
return nil
724+
}
725+
receipts := []*receiptLogs{}
726+
if err := rlp.DecodeBytes(data, &receipts); err != nil {
727+
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
728+
return nil
729+
}
730+
731+
body := ReadBody(db, hash, number)
732+
if body == nil {
733+
log.Error("Missing body but have receipt", "hash", hash, "number", number)
734+
return nil
735+
}
736+
if err := deriveLogFields(receipts, hash, number, body.Transactions); err != nil {
737+
log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err)
738+
return nil
739+
}
740+
logs := make([][]*types.Log, len(receipts))
741+
for i, receipt := range receipts {
742+
logs[i] = receipt.Logs
743+
}
744+
return logs
745+
}
746+
666747
// ReadBlock retrieves an entire block corresponding to the hash, assembling it
667748
// back from the stored header and body. If either the header or body could not
668749
// be retrieved nil is returned.

core/rawdb/accessors_chain_test.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,3 +670,216 @@ func makeTestReceipts(n int, nPerBlock int) []types.Receipts {
670670
}
671671
return allReceipts
672672
}
673+
674+
type fullLogRLP struct {
675+
Address common.Address
676+
Topics []common.Hash
677+
Data []byte
678+
BlockNumber uint64
679+
TxHash common.Hash
680+
TxIndex uint
681+
BlockHash common.Hash
682+
Index uint
683+
}
684+
685+
func newFullLogRLP(l *types.Log) *fullLogRLP {
686+
return &fullLogRLP{
687+
Address: l.Address,
688+
Topics: l.Topics,
689+
Data: l.Data,
690+
BlockNumber: l.BlockNumber,
691+
TxHash: l.TxHash,
692+
TxIndex: l.TxIndex,
693+
BlockHash: l.BlockHash,
694+
Index: l.Index,
695+
}
696+
}
697+
698+
// Tests that logs associated with a single block can be retrieved.
699+
func TestReadLogs(t *testing.T) {
700+
db := NewMemoryDatabase()
701+
702+
// Create a live block since we need metadata to reconstruct the receipt
703+
tx1 := types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, big.NewInt(1), nil)
704+
tx2 := types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, big.NewInt(2), nil)
705+
706+
body := &types.Body{Transactions: types.Transactions{tx1, tx2}}
707+
708+
// Create the two receipts to manage afterwards
709+
receipt1 := &types.Receipt{
710+
Status: types.ReceiptStatusFailed,
711+
CumulativeGasUsed: 1,
712+
Logs: []*types.Log{
713+
{Address: common.BytesToAddress([]byte{0x11})},
714+
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
715+
},
716+
TxHash: tx1.Hash(),
717+
ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}),
718+
GasUsed: 111111,
719+
}
720+
receipt1.Bloom = types.CreateBloom(types.Receipts{receipt1})
721+
722+
receipt2 := &types.Receipt{
723+
PostState: common.Hash{2}.Bytes(),
724+
CumulativeGasUsed: 2,
725+
Logs: []*types.Log{
726+
{Address: common.BytesToAddress([]byte{0x22})},
727+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
728+
},
729+
TxHash: tx2.Hash(),
730+
ContractAddress: common.BytesToAddress([]byte{0x02, 0x22, 0x22}),
731+
GasUsed: 222222,
732+
}
733+
receipt2.Bloom = types.CreateBloom(types.Receipts{receipt2})
734+
receipts := []*types.Receipt{receipt1, receipt2}
735+
736+
hash := common.BytesToHash([]byte{0x03, 0x14})
737+
// Check that no receipt entries are in a pristine database
738+
if rs := ReadReceipts(db, hash, 0, params.TestChainConfig); len(rs) != 0 {
739+
t.Fatalf("non existent receipts returned: %v", rs)
740+
}
741+
// Insert the body that corresponds to the receipts
742+
WriteBody(db, hash, 0, body)
743+
744+
// Insert the receipt slice into the database and check presence
745+
WriteReceipts(db, hash, 0, receipts)
746+
747+
logs := ReadLogs(db, hash, 0)
748+
if len(logs) == 0 {
749+
t.Fatalf("no logs returned")
750+
}
751+
if have, want := len(logs), 2; have != want {
752+
t.Fatalf("unexpected number of logs returned, have %d want %d", have, want)
753+
}
754+
if have, want := len(logs[0]), 2; have != want {
755+
t.Fatalf("unexpected number of logs[0] returned, have %d want %d", have, want)
756+
}
757+
if have, want := len(logs[1]), 2; have != want {
758+
t.Fatalf("unexpected number of logs[1] returned, have %d want %d", have, want)
759+
}
760+
761+
// Fill in log fields so we can compare their rlp encoding
762+
if err := types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, 0, body.Transactions); err != nil {
763+
t.Fatal(err)
764+
}
765+
for i, pr := range receipts {
766+
for j, pl := range pr.Logs {
767+
rlpHave, err := rlp.EncodeToBytes(newFullLogRLP(logs[i][j]))
768+
if err != nil {
769+
t.Fatal(err)
770+
}
771+
rlpWant, err := rlp.EncodeToBytes(newFullLogRLP(pl))
772+
if err != nil {
773+
t.Fatal(err)
774+
}
775+
if !bytes.Equal(rlpHave, rlpWant) {
776+
t.Fatalf("receipt #%d: receipt mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
777+
}
778+
}
779+
}
780+
}
781+
782+
func TestDeriveLogFields(t *testing.T) {
783+
// Create a few transactions to have receipts for
784+
to2 := common.HexToAddress("0x2")
785+
to3 := common.HexToAddress("0x3")
786+
txs := types.Transactions{
787+
types.NewTx(&types.LegacyTx{
788+
Nonce: 1,
789+
Value: big.NewInt(1),
790+
Gas: 1,
791+
GasPrice: big.NewInt(1),
792+
}),
793+
types.NewTx(&types.LegacyTx{
794+
To: &to2,
795+
Nonce: 2,
796+
Value: big.NewInt(2),
797+
Gas: 2,
798+
GasPrice: big.NewInt(2),
799+
}),
800+
types.NewTx(&types.AccessListTx{
801+
To: &to3,
802+
Nonce: 3,
803+
Value: big.NewInt(3),
804+
Gas: 3,
805+
GasPrice: big.NewInt(3),
806+
}),
807+
}
808+
// Create the corresponding receipts
809+
receipts := []*receiptLogs{
810+
{
811+
Logs: []*types.Log{
812+
{Address: common.BytesToAddress([]byte{0x11})},
813+
{Address: common.BytesToAddress([]byte{0x01, 0x11})},
814+
},
815+
},
816+
{
817+
Logs: []*types.Log{
818+
{Address: common.BytesToAddress([]byte{0x22})},
819+
{Address: common.BytesToAddress([]byte{0x02, 0x22})},
820+
},
821+
},
822+
{
823+
Logs: []*types.Log{
824+
{Address: common.BytesToAddress([]byte{0x33})},
825+
{Address: common.BytesToAddress([]byte{0x03, 0x33})},
826+
},
827+
},
828+
}
829+
830+
// Derive log metadata fields
831+
number := big.NewInt(1)
832+
hash := common.BytesToHash([]byte{0x03, 0x14})
833+
if err := deriveLogFields(receipts, hash, number.Uint64(), txs); err != nil {
834+
t.Fatal(err)
835+
}
836+
837+
// Iterate over all the computed fields and check that they're correct
838+
logIndex := uint(0)
839+
for i := range receipts {
840+
for j := range receipts[i].Logs {
841+
if receipts[i].Logs[j].BlockNumber != number.Uint64() {
842+
t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64())
843+
}
844+
if receipts[i].Logs[j].BlockHash != hash {
845+
t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
846+
}
847+
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
848+
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
849+
}
850+
if receipts[i].Logs[j].TxIndex != uint(i) {
851+
t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
852+
}
853+
if receipts[i].Logs[j].Index != logIndex {
854+
t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex)
855+
}
856+
logIndex++
857+
}
858+
}
859+
}
860+
861+
func BenchmarkDecodeRLPLogs(b *testing.B) {
862+
// Encoded receipts from block 0x14ee094309fbe8f70b65f45ebcc08fb33f126942d97464aad5eb91cfd1e2d269
863+
buf, err := ioutil.ReadFile("testdata/stored_receipts.bin")
864+
if err != nil {
865+
b.Fatal(err)
866+
}
867+
b.Run("ReceiptForStorage", func(b *testing.B) {
868+
b.ReportAllocs()
869+
var r []*types.ReceiptForStorage
870+
for i := 0; i < b.N; i++ {
871+
if err := rlp.DecodeBytes(buf, &r); err != nil {
872+
b.Fatal(err)
873+
}
874+
}
875+
})
876+
b.Run("rlpLogs", func(b *testing.B) {
877+
b.ReportAllocs()
878+
var r []*receiptLogs
879+
for i := 0; i < b.N; i++ {
880+
if err := rlp.DecodeBytes(buf, &r); err != nil {
881+
b.Fatal(err)
882+
}
883+
}
884+
})
885+
}
97.6 KB
Binary file not shown.

core/types/receipt_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,6 @@ func TestDeriveFields(t *testing.T) {
273273
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
274274
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
275275
}
276-
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
277-
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
278-
}
279276
if receipts[i].Logs[j].TxIndex != uint(i) {
280277
t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
281278
}

eth/api_backend.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,14 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type
181181
}
182182

183183
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
184-
receipts := b.eth.blockchain.GetReceiptsByHash(hash)
185-
if receipts == nil {
186-
return nil, nil
184+
db := b.eth.ChainDb()
185+
number := rawdb.ReadHeaderNumber(db, hash)
186+
if number == nil {
187+
return nil, errors.New("failed to get block number from hash")
187188
}
188-
logs := make([][]*types.Log, len(receipts))
189-
for i, receipt := range receipts {
190-
logs[i] = receipt.Logs
189+
logs := rawdb.ReadLogs(db, hash, *number)
190+
if logs == nil {
191+
return nil, errors.New("failed to get logs for block")
191192
}
192193
return logs, nil
193194
}

0 commit comments

Comments
 (0)