diff --git a/core/types/bloom9.go b/core/types/bloom9.go index 962ba46d47..08120666cd 100644 --- a/core/types/bloom9.go +++ b/core/types/bloom9.go @@ -59,11 +59,12 @@ func (b *Bloom) SetBytes(d []byte) { // Add adds d to the filter. Future calls of Test(d) will return true. func (b *Bloom) Add(d []byte) { - b.add(d, make([]byte, 6)) + var buf [6]byte + b.AddWithBuffer(d, &buf) } // add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes) -func (b *Bloom) add(d []byte, buf []byte) { +func (b *Bloom) AddWithBuffer(d []byte, buf *[6]byte) { i1, v1, i2, v2, i3, v3 := bloomValues(d, buf) b[i1] |= v1 b[i2] |= v2 @@ -84,7 +85,8 @@ func (b Bloom) Bytes() []byte { // Test checks if the given topic is present in the bloom filter func (b Bloom) Test(topic []byte) bool { - i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6)) + var buf [6]byte + i1, v1, i2, v2, i3, v3 := bloomValues(topic, &buf) return v1 == v1&b[i1] && v2 == v2&b[i2] && v3 == v3&b[i3] @@ -107,9 +109,9 @@ func CreateBloom(receipt *Receipt) Bloom { buf = make([]byte, 6) ) for _, log := range receipt.Logs { - bin.add(log.Address.Bytes(), buf) + bin.AddWithBuffer(log.Address.Bytes(), buf) for _, b := range log.Topics { - bin.add(b[:], buf) + bin.AddWithBuffer(b[:], buf) } } return bin @@ -139,21 +141,20 @@ func Bloom9(data []byte) []byte { } // bloomValues returns the bytes (index-value pairs) to set for the given data -func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) { +func bloomValues(data []byte, hashbuf *[6]byte) (uint, byte, uint, byte, uint, byte) { sha := hasherPool.Get().(crypto.KeccakState) sha.Reset() sha.Write(data) - sha.Read(hashbuf) + sha.Read(hashbuf[:]) hasherPool.Put(sha) // The actual bits to flip v1 := byte(1 << (hashbuf[1] & 0x7)) v2 := byte(1 << (hashbuf[3] & 0x7)) v3 := byte(1 << (hashbuf[5] & 0x7)) // The indices for the bytes to OR in - i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1 + i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[0:])&0x7ff)>>3) - 1 i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1 i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 - return i1, v1, i2, v2, i3, v3 } diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 0ea67d11c1..fa031d9899 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -699,41 +699,3 @@ 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 f102ec22df..6d4fc9644a 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -17,7 +17,6 @@ package eth import ( - "bytes" "encoding/json" "errors" "fmt" @@ -319,7 +318,7 @@ func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) if header.ReceiptHash != types.EmptyReceiptsHash { body := new(types.Body) if err := rlp.DecodeBytes(chain.GetBodyRLP(hash), &body); err == nil { - results = transformReceipts(results, body.Transactions) + results = blockReceiptsToNetwork(results, body.Transactions) } } } @@ -329,33 +328,6 @@ func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) 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 } @@ -440,32 +412,23 @@ func handleReceipts69(backend Backend, msg Decoder, peer *Peer) error { 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) + buffers := new(receiptListBuffers) + for _, rl := range res.List { + rl.buf = buffers + // WIP } 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) + hashes := make([]common.Hash, len(res.List)) + for i, rl := range res.List { + hashes[i] = types.DeriveSha(&rl, hasher) } return hashes } return peer.dispatchResponse(&Response{ id: res.RequestId, code: ReceiptsMsg, - Res: &response, + Res: &res, }, metadata) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 7236058266..743d0c5882 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -260,14 +260,11 @@ 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 + List []ReceiptList69 } // ReceiptsRLPResponse is used for receipts, when we already have it encoded diff --git a/eth/protocols/eth/receipt.go b/eth/protocols/eth/receipt.go index bdeee47b56..b388eb5897 100644 --- a/eth/protocols/eth/receipt.go +++ b/eth/protocols/eth/receipt.go @@ -17,64 +17,206 @@ package eth import ( - "errors" + "bytes" + "fmt" "io" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) -type receiptForNetwork types.Receipt +// Receipt is the eth/69 network encoding of a receipt. +type Receipt struct { + TxType byte + PostStateOrStatus []byte + GasUsed uint64 + Logs rlp.RawValue +} -func (r *receiptForNetwork) DecodeRLP(s *rlp.Stream) error { - kind, size, err := s.Kind() - if err != nil { +func (r *Receipt) DecodeRLP(s *rlp.Stream) error { + if _, err := s.List(); err != nil { return err } - if kind != rlp.List { - return errors.New("invalid receipt") + txtype, err := s.Uint8() + if err != nil { + return fmt.Errorf("invalid txType: %w", err) } - 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) + postStateOrStatus, err := s.Bytes() + if err != nil { + return fmt.Errorf("invalid postStateOrStatus: %w", err) + } + gasUsed, err := s.Uint64() + if err != nil { + return fmt.Errorf("invalid gasUsed: %w", err) + } + logs, err := s.Raw() + if err != nil { + return fmt.Errorf("invalid logs: %w", err) + } + *r = Receipt{ + TxType: txtype, + PostStateOrStatus: postStateOrStatus, + GasUsed: gasUsed, + Logs: logs, } 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) - } +func (r *Receipt) EncodeRLP(_w io.Writer) error { 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}) - } + list := w.List() + w.WriteUint64(uint64(r.TxType)) + w.WriteBytes(r.PostStateOrStatus) 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) + w.Write(r.Logs) + w.ListEnd(list) return w.Flush() } + +// bloom computes the bloom filter of the receipt. +// Note this doesn't check the validity of encoding, and will produce an invalid filter +// for invalid input. This is acceptable for the purpose of this function, which is +// recomputing the receipt hash. +func (r *Receipt) bloom(buffer *[6]byte) types.Bloom { + var b types.Bloom + logsIter, err := rlp.NewListIterator(r.Logs) + if err != nil { + return b + } + for logsIter.Next() { + log := logsIter.Value() + address, log, _ := rlp.SplitString(log) + b.AddWithBuffer(address, buffer) + topicsIter, err := rlp.NewListIterator(log) + if err != nil { + return b + } + for topicsIter.Next() { + b.AddWithBuffer(topicsIter.Value(), buffer) + } + } + return b +} + +// ReceiptList69 is the block receipt list as downloaded by eth/69. +// This implements types.DerivableList for validation purposes. +type ReceiptList69 struct { + buf *receiptListBuffers + items []Receipt +} + +type receiptListBuffers struct { + enc rlp.EncoderBuffer + bloom [6]byte +} + +// Len returns the length of the list. +func (rl *ReceiptList69) Len() int { + return len(rl.items) +} + +// EncodeIndex implements types.DerivableList. +func (rl *ReceiptList69) EncodeIndex(i int, b *bytes.Buffer) { + if rl.buf == nil { + rl.buf = new(receiptListBuffers) + } + + var ( + r = &rl.items[i] + bloom = r.bloom(&rl.buf.bloom) + w = &rl.buf.enc + ) + // encode receipt list: [postStateOrStatus, gasUsed, bloom, logs] + w.Reset(b) + l := w.List() + w.WriteBytes(r.PostStateOrStatus) + w.WriteUint64(r.GasUsed) + w.WriteBytes(bloom[:]) + w.Write(r.Logs) + w.ListEnd(l) + if err := w.Flush(); err != nil { + return + } + // if this is a legacy transaction receipt, we are done. + if r.TxType == 0 { + return + } + // Otherwise it's a typed transaction receipt, which has the type prefix and + // the inner list as a byte-array: tx-type || rlp(list). + // Since b contains the correct inner list, we can reuse its content. + w.Reset(b) + w.WriteUint64(uint64(r.TxType)) + w.WriteBytes(b.Bytes()) + w.Flush() +} + +// EncodeForDatabase +func (rl *ReceiptList69) EncodeForDatabase() []byte { + if rl.buf == nil { + rl.buf = new(receiptListBuffers) + } + + var ( + r = &rl.items[i] + bloom = r.bloom(&rl.buf.bloom) + w = &rl.buf.enc + ) + // encode receipt list: [postStateOrStatus, gasUsed, bloom, logs] + w.Reset(b) + l := w.List() + w.WriteBytes(r.PostStateOrStatus) + w.WriteUint64(r.GasUsed) + w.WriteBytes(bloom[:]) + w.Write(r.Logs) + w.ListEnd(l) + if err := w.Flush(); err != nil { + return + } + // if this is a legacy transaction receipt, we are done. + if r.TxType == 0 { + return + } + // Otherwise it's a typed transaction receipt, which has the type prefix and + // the inner list as a byte-array: tx-type || rlp(list). + // Since b contains the correct inner list, we can reuse its content. + w.Reset(b) + w.WriteUint64(uint64(r.TxType)) + w.WriteBytes(b.Bytes()) + w.Flush() + +} + +func (rl *ReceiptList69) toReceipts() []*types.Receipt { + list := make([]*types.Receipt, len(rl.items)) + for i, r := range rl.items { + list[i] = &types.Receipt{ + Type: r.TxType, + PostState: r.PostStateOrStatus, + CumulativeGasUsed: r.GasUsed, + } + rlp.DecodeBytes(r.Logs, &list[i].Logs) + } + return list +} + +// blockReceiptsToNetwork 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 blockReceiptsToNetwork(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++ { + 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() +} diff --git a/eth/protocols/eth/receipt_test.go b/eth/protocols/eth/receipt_test.go new file mode 100644 index 0000000000..0438d6be6e --- /dev/null +++ b/eth/protocols/eth/receipt_test.go @@ -0,0 +1,64 @@ +// 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 ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +func TestTransformReceipts(t *testing.T) { + tests := []struct { + input []types.ReceiptForStorage + txs []*types.Transaction + output []Receipt + }{ + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.LegacyTx{})}, + output: []Receipt{{GasUsed: 123, PostStateOrStatus: []byte{1}, Logs: nil}}, + }, + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.DynamicFeeTx{})}, + output: []Receipt{{GasUsed: 123, PostStateOrStatus: []byte{1}, Logs: nil, TxType: 2}}, + }, + { + input: []types.ReceiptForStorage{{CumulativeGasUsed: 123, Status: 1, Logs: nil}}, + txs: []*types.Transaction{types.NewTx(&types.AccessListTx{})}, + output: []Receipt{{GasUsed: 123, PostStateOrStatus: []byte{1}, Logs: nil, TxType: 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: []Receipt{{GasUsed: 123, PostStateOrStatus: []byte{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 := blockReceiptsToNetwork(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) + } + } +}