From e429b91c46c7e45e2877ff9ffd3d035eca3230c6 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 1 Mar 2024 16:44:11 +0100 Subject: [PATCH] eth/protocols/eth: started work on eth/69, drop receipt blooms --- cmd/devp2p/internal/ethtest/conn.go | 4 +- core/blockchain_reader.go | 6 +- core/rawdb/accessors_chain.go | 4 +- core/rawdb/accessors_chain_test.go | 6 +- core/types/receipt.go | 6 +- core/types/receipt_test.go | 299 +++++++++++++++------------- eth/downloader/downloader_test.go | 2 +- eth/protocols/eth/handler.go | 26 ++- eth/protocols/eth/handler_test.go | 38 ++++ eth/protocols/eth/handlers.go | 117 ++++++++++- eth/protocols/eth/handshake.go | 7 +- eth/protocols/eth/protocol.go | 15 +- eth/protocols/eth/receipt.go | 80 ++++++++ 13 files changed, 446 insertions(+), 164 deletions(-) create mode 100644 eth/protocols/eth/receipt.go diff --git a/cmd/devp2p/internal/ethtest/conn.go b/cmd/devp2p/internal/ethtest/conn.go index a7bc70cbf5..dfa0bfd55b 100644 --- a/cmd/devp2p/internal/ethtest/conn.go +++ b/cmd/devp2p/internal/ethtest/conn.go @@ -66,10 +66,10 @@ func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) { return nil, err } conn.caps = []p2p.Cap{ - {Name: "eth", Version: 67}, {Name: "eth", Version: 68}, + {Name: "eth", Version: 69}, } - conn.ourHighestProtoVersion = 68 + conn.ourHighestProtoVersion = 69 return &conn, nil } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index fefeb37542..80ec073c82 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -234,12 +234,14 @@ func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { return receipts } -func (bc *BlockChain) GetRawReceiptsByHash(hash common.Hash) types.Receipts { +// GetRawReceiptsByHash retrieves the receipts for all transactions in a given block +// without deriving the internal fields and the Bloom. +func (bc *BlockChain) GetRawReceiptsByHash(hash common.Hash) rlp.RawValue { number := rawdb.ReadHeaderNumber(bc.db, hash) if number == nil { return nil } - return rawdb.ReadRawReceipts(bc.db, hash, *number) + return rawdb.ReadReceiptsRLP(bc.db, hash, *number) } // GetUnclesInChain retrieves all the uncles from a given block backwards until diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 2f62d86e4b..a34154ccef 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -545,8 +545,8 @@ func ReadReceiptsRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawVa } // ReadRawReceipts retrieves all the transaction receipts belonging to a block. -// The receipt metadata fields are not guaranteed to be populated, so they -// should not be used. Use ReadReceipts instead if the metadata is needed. +// The receipt metadata fields and the Bloom are not guaranteed to be populated, +// so they should not be used. Use ReadReceipts instead if the metadata is needed. func ReadRawReceipts(db ethdb.Reader, hash common.Hash, number uint64) types.Receipts { // Retrieve the flattened receipt slice data := ReadReceiptsRLP(db, hash, number) diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 247e277582..4eaff39e5b 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -377,7 +377,11 @@ func TestBlockReceiptStorage(t *testing.T) { 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 { + raw := ReadRawReceipts(db, hash, 0) + for _, r := range raw { + r.Bloom = types.CreateBloom(r) + } + if err := checkReceiptsRLP(raw, receipts); err != nil { t.Fatal(err) } // Sanity check that body alone without the receipt is a full purge diff --git a/core/types/receipt.go b/core/types/receipt.go index e52a0c6477..08276f070b 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -258,7 +258,7 @@ func (r *Receipt) Size() common.StorageSize { } // ReceiptForStorage is a wrapper around a Receipt with RLP serialization -// that omits the Bloom field and deserialization that re-computes it. +// that omits the Bloom field. The Bloom field is recomputed by DeriveFields. type ReceiptForStorage Receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt @@ -291,7 +291,6 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { } r.CumulativeGasUsed = stored.CumulativeGasUsed r.Logs = stored.Logs - r.Bloom = CreateBloom((*Receipt)(r)) return nil } @@ -372,6 +371,9 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu rs[i].Logs[j].Index = logIndex logIndex++ } + // also derive the Bloom if not derived yet + rs[i].Bloom = CreateBloom(rs[i]) } + return nil } diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 78b43c7e49..6c67b811db 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -22,6 +22,7 @@ import ( "math" "math/big" "reflect" + "sync" "testing" "github.com/ethereum/go-ethereum/common" @@ -154,148 +155,162 @@ var ( blockNumber = big.NewInt(1) blockTime = uint64(2) blockHash = common.BytesToHash([]byte{0x03, 0x14}) - - // Create the corresponding receipts - receipts = Receipts{ - &Receipt{ - Status: ReceiptStatusFailed, - CumulativeGasUsed: 1, - Logs: []*Log{ - { - Address: common.BytesToAddress([]byte{0x11}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - // derived fields: - BlockNumber: blockNumber.Uint64(), - TxHash: txs[0].Hash(), - TxIndex: 0, - BlockHash: blockHash, - Index: 0, - }, - { - Address: common.BytesToAddress([]byte{0x01, 0x11}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - // derived fields: - BlockNumber: blockNumber.Uint64(), - TxHash: txs[0].Hash(), - TxIndex: 0, - BlockHash: blockHash, - Index: 1, - }, - }, - // derived fields: - TxHash: txs[0].Hash(), - ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), - GasUsed: 1, - EffectiveGasPrice: big.NewInt(11), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 0, - }, - &Receipt{ - PostState: common.Hash{2}.Bytes(), - CumulativeGasUsed: 3, - Logs: []*Log{ - { - Address: common.BytesToAddress([]byte{0x22}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - // derived fields: - BlockNumber: blockNumber.Uint64(), - TxHash: txs[1].Hash(), - TxIndex: 1, - BlockHash: blockHash, - Index: 2, - }, - { - Address: common.BytesToAddress([]byte{0x02, 0x22}), - Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, - // derived fields: - BlockNumber: blockNumber.Uint64(), - TxHash: txs[1].Hash(), - TxIndex: 1, - BlockHash: blockHash, - Index: 3, - }, - }, - // derived fields: - TxHash: txs[1].Hash(), - GasUsed: 2, - EffectiveGasPrice: big.NewInt(22), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 1, - }, - &Receipt{ - Type: AccessListTxType, - PostState: common.Hash{3}.Bytes(), - CumulativeGasUsed: 6, - Logs: []*Log{}, - // derived fields: - TxHash: txs[2].Hash(), - GasUsed: 3, - EffectiveGasPrice: big.NewInt(33), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 2, - }, - &Receipt{ - Type: DynamicFeeTxType, - PostState: common.Hash{4}.Bytes(), - CumulativeGasUsed: 10, - Logs: []*Log{}, - // derived fields: - TxHash: txs[3].Hash(), - GasUsed: 4, - EffectiveGasPrice: big.NewInt(1044), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 3, - }, - &Receipt{ - Type: DynamicFeeTxType, - PostState: common.Hash{5}.Bytes(), - CumulativeGasUsed: 15, - Logs: []*Log{}, - // derived fields: - TxHash: txs[4].Hash(), - GasUsed: 5, - EffectiveGasPrice: big.NewInt(1055), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 4, - }, - &Receipt{ - Type: BlobTxType, - PostState: common.Hash{6}.Bytes(), - CumulativeGasUsed: 21, - Logs: []*Log{}, - // derived fields: - TxHash: txs[5].Hash(), - GasUsed: 6, - EffectiveGasPrice: big.NewInt(1066), - BlobGasUsed: params.BlobTxBlobGasPerBlob, - BlobGasPrice: big.NewInt(920), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 5, - }, - &Receipt{ - Type: BlobTxType, - PostState: common.Hash{7}.Bytes(), - CumulativeGasUsed: 28, - Logs: []*Log{}, - // derived fields: - TxHash: txs[6].Hash(), - GasUsed: 7, - EffectiveGasPrice: big.NewInt(1077), - BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob, - BlobGasPrice: big.NewInt(920), - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: 6, - }, - } ) +var receiptsOnce sync.Once +var testReceipts Receipts + +func getTestReceipts() Receipts { + + // Compute the blooms only once + receiptsOnce.Do(func() { + // Create the corresponding receipts + r := Receipts{ + &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 0, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[0].Hash(), + TxIndex: 0, + BlockHash: blockHash, + Index: 1, + }, + }, + // derived fields: + TxHash: txs[0].Hash(), + ContractAddress: common.HexToAddress("0x5a443704dd4b594b382c22a083e2bd3090a6fef3"), + GasUsed: 1, + EffectiveGasPrice: big.NewInt(11), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 0, + }, + &Receipt{ + PostState: common.Hash{2}.Bytes(), + CumulativeGasUsed: 3, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x22}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 2, + }, + { + Address: common.BytesToAddress([]byte{0x02, 0x22}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + // derived fields: + BlockNumber: blockNumber.Uint64(), + TxHash: txs[1].Hash(), + TxIndex: 1, + BlockHash: blockHash, + Index: 3, + }, + }, + // derived fields: + TxHash: txs[1].Hash(), + GasUsed: 2, + EffectiveGasPrice: big.NewInt(22), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 1, + }, + &Receipt{ + Type: AccessListTxType, + PostState: common.Hash{3}.Bytes(), + CumulativeGasUsed: 6, + Logs: []*Log{}, + // derived fields: + TxHash: txs[2].Hash(), + GasUsed: 3, + EffectiveGasPrice: big.NewInt(33), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 2, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{4}.Bytes(), + CumulativeGasUsed: 10, + Logs: []*Log{}, + // derived fields: + TxHash: txs[3].Hash(), + GasUsed: 4, + EffectiveGasPrice: big.NewInt(1044), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 3, + }, + &Receipt{ + Type: DynamicFeeTxType, + PostState: common.Hash{5}.Bytes(), + CumulativeGasUsed: 15, + Logs: []*Log{}, + // derived fields: + TxHash: txs[4].Hash(), + GasUsed: 5, + EffectiveGasPrice: big.NewInt(1055), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 4, + }, + &Receipt{ + Type: BlobTxType, + PostState: common.Hash{6}.Bytes(), + CumulativeGasUsed: 21, + Logs: []*Log{}, + // derived fields: + TxHash: txs[5].Hash(), + GasUsed: 6, + EffectiveGasPrice: big.NewInt(1066), + BlobGasUsed: params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 5, + }, + &Receipt{ + Type: BlobTxType, + PostState: common.Hash{7}.Bytes(), + CumulativeGasUsed: 28, + Logs: []*Log{}, + // derived fields: + TxHash: txs[6].Hash(), + GasUsed: 7, + EffectiveGasPrice: big.NewInt(1077), + BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: 6, + }, + } + for _, receipt := range r { + receipt.Bloom = CreateBloom(receipt) + } + testReceipts = r + }) + return testReceipts +} + func TestDecodeEmptyTypedReceipt(t *testing.T) { input := []byte{0x80} var r Receipt @@ -310,6 +325,7 @@ func TestDeriveFields(t *testing.T) { // Re-derive receipts. basefee := big.NewInt(1000) blobGasPrice := big.NewInt(920) + receipts := getTestReceipts() derivedReceipts := clearComputedFieldsOnReceipts(receipts) err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) if err != nil { @@ -335,6 +351,7 @@ func TestDeriveFields(t *testing.T) { // Test that we can marshal/unmarshal receipts to/from json without errors. // This also confirms that our test receipts contain all the required fields. func TestReceiptJSON(t *testing.T) { + receipts := getTestReceipts() for i := range receipts { b, err := receipts[i].MarshalJSON() if err != nil { @@ -351,6 +368,7 @@ func TestReceiptJSON(t *testing.T) { // Test we can still parse receipt without EffectiveGasPrice for backwards compatibility, even // though it is required per the spec. func TestEffectiveGasPriceNotRequired(t *testing.T) { + receipts := getTestReceipts() r := *receipts[0] r.EffectiveGasPrice = nil b, err := r.MarshalJSON() @@ -511,6 +529,7 @@ func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt { cpy.EffectiveGasPrice = big.NewInt(0) cpy.BlobGasUsed = 0 cpy.BlobGasPrice = nil + cpy.Bloom = CreateBloom(&cpy) return &cpy } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 3a145b1958..3628122b21 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -255,7 +255,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et // peer in the download tester. The returned function can be used to retrieve // batches of block receipts from the particularly requested peer. func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) { - blobs := eth.ServiceGetReceiptsQuery(dlp.chain, hashes) + blobs := eth.ServiceGetReceiptsQuery68(dlp.chain, hashes) receipts := make([][]*types.Receipt, len(blobs)) for i, blob := range blobs { diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index f2a3cb0292..4dd480b7f0 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -175,8 +175,21 @@ var eth68 = map[uint64]msgHandler{ BlockHeadersMsg: handleBlockHeaders, GetBlockBodiesMsg: handleGetBlockBodies, BlockBodiesMsg: handleBlockBodies, - GetReceiptsMsg: handleGetReceipts, - ReceiptsMsg: handleReceipts, + GetReceiptsMsg: handleGetReceipts68, + ReceiptsMsg: handleReceipts68, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, +} + +var eth69 = map[uint64]msgHandler{ + TransactionsMsg: handleTransactions, + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetReceiptsMsg: handleGetReceipts69, + ReceiptsMsg: handleReceipts69, GetPooledTransactionsMsg: handleGetPooledTransactions, PooledTransactionsMsg: handlePooledTransactions, } @@ -194,7 +207,14 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() - var handlers = eth68 + var handlers map[uint64]msgHandler + if peer.version == ETH68 { + handlers = eth68 + } else if peer.version == ETH69 { + handlers = eth69 + } else { + return fmt.Errorf("unknown eth protocol version: %v", peer.version) + } // Track the amount of time it takes to serve the request and run the handler if metrics.Enabled() { diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index fa031d9899..0ea67d11c1 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -699,3 +699,41 @@ func testGetPooledTransaction(t *testing.T, blobTx bool) { t.Errorf("pooled transaction mismatch: %v", err) } } + +func TestTransformReceipts(t *testing.T) { + tests := []struct { + input []types.ReceiptForStorage + txs []*types.Transaction + output []receiptForNetwork + }{ + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.LegacyTx{})}, + output: []receiptForNetwork{{GasUsed: 123, Status: 1, Logs: nil}}, + }, + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.DynamicFeeTx{})}, + output: []receiptForNetwork{{GasUsed: 123, Status: 1, Logs: nil, Type: 2}}, + }, + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.AccessListTx{})}, + output: []receiptForNetwork{{GasUsed: 123, Status: 1, Logs: nil, Type: 1}}, + }, + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: []*types.Log{{Address: common.Address{1}, Topics: []common.Hash{{1}}}}}}, + txs: []*types.Transaction{types.NewTx(&types.AccessListTx{})}, + output: []receiptForNetwork{{GasUsed: 123, Status: 1, Logs: []*types.Log{{Address: common.Address{1}, Topics: []common.Hash{{1}}}}, Type: 1}}, + }, + } + + for i, test := range tests { + in, _ := rlp.EncodeToBytes(test.input) + have := transformReceipts(in, test.txs) + out, _ := rlp.EncodeToBytes(test.output) + if !bytes.Equal(have, out) { + t.Fatalf("transforming receipt mismatch, test %v: want %v have %v", i, out, have) + } + } +} diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index fda650da1c..f102ec22df 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -17,6 +17,7 @@ package eth import ( + "bytes" "encoding/json" "errors" "fmt" @@ -243,19 +244,29 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequ return bodies } -func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error { +func handleGetReceipts68(backend Backend, msg Decoder, peer *Peer) error { // Decode the block receipts retrieval message var query GetReceiptsPacket if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsRequest) + response := ServiceGetReceiptsQuery68(backend.Chain(), query.GetReceiptsRequest) return peer.ReplyReceiptsRLP(query.RequestId, response) } -// ServiceGetReceiptsQuery assembles the response to a receipt query. It is +func handleGetReceipts69(backend Backend, msg Decoder, peer *Peer) error { + // Decode the block receipts retrieval message + var query GetReceiptsPacket + if err := msg.Decode(&query); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + response := serviceGetReceiptsQuery69(backend.Chain(), query.GetReceiptsRequest) + return peer.ReplyReceiptsRLP(query.RequestId, response) +} + +// ServiceGetReceiptsQuery68 assembles the response to a receipt query. It is // exposed to allow external packages to test protocol behavior. -func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue { +func ServiceGetReceiptsQuery68(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue { // Gather state data until the fetch or network limits is reached var ( bytes int @@ -284,6 +295,67 @@ func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) [ return receipts } +// serviceGetReceiptsQuery69 assembles the response to a receipt query. +// It does not send the bloom filters for the receipts +func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue { + // Gather state data until the fetch or network limits is reached + var ( + bytes int + receipts []rlp.RawValue + ) + for lookups, hash := range query { + if bytes >= softResponseLimit || len(receipts) >= maxReceiptsServe || + lookups >= 2*maxReceiptsServe { + break + } + // Retrieve the requested block's receipts + results := chain.GetRawReceiptsByHash(hash) + if results == nil { + if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { + continue + } + } else { + header := chain.GetHeaderByHash(hash) + if header.ReceiptHash != types.EmptyReceiptsHash { + body := new(types.Body) + if err := rlp.DecodeBytes(chain.GetBodyRLP(hash), &body); err == nil { + results = transformReceipts(results, body.Transactions) + } + } + } + receipts = append(receipts, results) + bytes += len(results) + } + return receipts +} + +// transformReceipts takes a slice of rlp-encoded receipts, and transactions, +// and applies the type-encoding on the receipts (for non-legacy receipts). +// e.g. for non-legacy receipts: receipt-data -> {tx-type || receipt-data} +func transformReceipts(blockReceipts []byte, txs []*types.Transaction) []byte { + var ( + out bytes.Buffer + enc = rlp.NewEncoderBuffer(&out) + it, _ = rlp.NewListIterator(blockReceipts) + ) + outer := enc.List() + for i := 0; it.Next(); i++ { + if txs[i].Type() == types.LegacyTxType { + enc.Write(it.Value()) + continue + } + content, _, _ := rlp.SplitList(it.Value()) + receiptList := enc.List() + enc.Write([]byte{txs[i].Type()}) + enc.Write(content) + enc.ListEnd(receiptList) + } + enc.ListEnd(outer) + enc.Flush() + + return out.Bytes() +} + func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error { return errors.New("block announcements disallowed") // We dropped support for non-merge networks } @@ -341,7 +413,7 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { }, metadata) } -func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { +func handleReceipts68(backend Backend, msg Decoder, peer *Peer) error { // A batch of receipts arrived to one of our previous requests res := new(ReceiptsPacket) if err := msg.Decode(res); err != nil { @@ -362,6 +434,41 @@ func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { }, metadata) } +func handleReceipts69(backend Backend, msg Decoder, peer *Peer) error { + // A batch of receipts arrived to one of our previous requests + res := new(ReceiptsPacket69) + if err := msg.Decode(res); err != nil { + return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) + } + var response ReceiptsResponse + // calculate the bloom filter before dispatching + for _, receipts := range res.ReceiptsResponse69 { + var rec []*types.Receipt + for _, receipt := range receipts { + receipt.Bloom = types.CreateBloom((*types.Receipt)(receipt)) + rec = append(rec, (*types.Receipt)(receipt)) + } + response = append(response, rec) + } + metadata := func() interface{} { + hasher := trie.NewStackTrie(nil) + hashes := make([]common.Hash, len(response)) + for i, receipts := range response { + var r []*types.Receipt + for _, receipt := range receipts { + r = append(r, (*types.Receipt)(receipt)) + } + hashes[i] = types.DeriveSha(types.Receipts(r), hasher) + } + return hashes + } + return peer.dispatchResponse(&Response{ + id: res.RequestId, + code: ReceiptsMsg, + Res: &response, + }, metadata) +} + func handleNewPooledTransactionHashes(backend Backend, msg Decoder, peer *Peer) error { // New transaction announcement arrived, make sure we have // a valid and fresh chain to handle them diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 0b6f110e3d..e1f8972dad 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -19,7 +19,6 @@ package eth import ( "errors" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/common" @@ -43,14 +42,14 @@ func (p *Peer) Handshake(network uint64, head common.Hash, genesis common.Hash, var status StatusPacket // safe to read after two values have been received from errc go func() { - errc <- p2p.Send(p.rw, StatusMsg, &StatusPacket{ + pkt := &StatusPacket{ ProtocolVersion: uint32(p.version), NetworkID: network, - TD: new(big.Int), // unknown for post-merge tail=pruned networks Head: head, Genesis: genesis, ForkID: forkID, - }) + } + errc <- p2p.Send(p.rw, StatusMsg, pkt) }() go func() { errc <- p.readStatus(network, &status, genesis, forkFilter) diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index aeef4330ff..7236058266 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -31,6 +31,7 @@ import ( // Constants to match up protocol versions and messages const ( ETH68 = 68 + ETH69 = 69 ) // ProtocolName is the official short name of the `eth` protocol used during @@ -39,11 +40,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH68} +var ProtocolVersions = []uint{ETH69, ETH68} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH68: 17} +var protocolLengths = map[uint]uint64{ETH68: 17, ETH69: 17} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -259,6 +260,16 @@ type ReceiptsPacket struct { ReceiptsResponse } +// ReceiptsResponse is the network packet for block receipts distribution. +type ReceiptsResponse69 [][]*receiptForNetwork + +// ReceiptsPacket is the network packet for block receipts distribution with +// request ID wrapping. +type ReceiptsPacket69 struct { + RequestId uint64 + ReceiptsResponse69 +} + // ReceiptsRLPResponse is used for receipts, when we already have it encoded type ReceiptsRLPResponse []rlp.RawValue diff --git a/eth/protocols/eth/receipt.go b/eth/protocols/eth/receipt.go new file mode 100644 index 0000000000..bdeee47b56 --- /dev/null +++ b/eth/protocols/eth/receipt.go @@ -0,0 +1,80 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package eth + +import ( + "errors" + "io" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +type receiptForNetwork types.Receipt + +func (r *receiptForNetwork) DecodeRLP(s *rlp.Stream) error { + kind, size, err := s.Kind() + if err != nil { + return err + } + if kind != rlp.List { + return errors.New("invalid receipt") + } + if size == 4 { + var rec = new(struct { + Type byte + Status uint64 + GasUsed uint64 + Logs []*types.Log + }) + if err := s.Decode(rec); err != nil { + return err + } + r.Type = rec.Type + r.Status = rec.Status + r.GasUsed = rec.GasUsed + r.Logs = rec.Logs + } else { + s.Decode(&r) + } + return nil +} + +func (r *receiptForNetwork) EncodeRLP(_w io.Writer) error { + data := &types.ReceiptForStorage{Status: r.Status, CumulativeGasUsed: r.GasUsed, Logs: r.Logs} + if r.Type == types.LegacyTxType { + return rlp.Encode(_w, data) + } + w := rlp.NewEncoderBuffer(_w) + outerList := w.List() + w.Write([]byte{r.Type}) + if r.Status == types.ReceiptStatusSuccessful { + w.Write([]byte{0x01}) + } else { + w.Write([]byte{0x00}) + } + w.WriteUint64(r.GasUsed) + logList := w.List() + for _, log := range r.Logs { + if err := log.EncodeRLP(w); err != nil { + return err + } + } + w.ListEnd(logList) + w.ListEnd(outerList) + return w.Flush() +}