diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 88bb8c39b9..2467e0c713 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -176,7 +176,7 @@ var eth68 = map[uint64]msgHandler{ GetBlockBodiesMsg: handleGetBlockBodies, BlockBodiesMsg: handleBlockBodies, GetReceiptsMsg: handleGetReceipts68, - ReceiptsMsg: handleReceipts68, + ReceiptsMsg: handleReceipts[*ReceiptList68], GetPooledTransactionsMsg: handleGetPooledTransactions, PooledTransactionsMsg: handlePooledTransactions, } @@ -189,7 +189,7 @@ var eth69 = map[uint64]msgHandler{ GetBlockBodiesMsg: handleGetBlockBodies, BlockBodiesMsg: handleBlockBodies, GetReceiptsMsg: handleGetReceipts69, - ReceiptsMsg: handleReceipts69, + ReceiptsMsg: handleReceipts[*ReceiptList69], GetPooledTransactionsMsg: handleGetPooledTransactions, PooledTransactionsMsg: handlePooledTransactions, BlockRangeUpdateMsg: handleBlockRangeUpdate, diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 0884782edc..11c926191f 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -529,22 +529,23 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { // Collect the hashes to request, and the response to expect var ( hashes []common.Hash - receipts []types.Receipts + receipts []*ReceiptList68 ) for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { block := backend.chain.GetBlockByNumber(i) - hashes = append(hashes, block.Hash()) - receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) + trs := backend.chain.GetReceiptsByHash(block.Hash()) + receipts = append(receipts, NewReceiptList68(trs)) } + // Send the hash request and verify the response p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket{ RequestId: 123, GetReceiptsRequest: hashes, }) - if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket{ - RequestId: 123, - ReceiptsResponse: receipts, + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket[*ReceiptList68]{ + RequestId: 123, + List: receipts, }); err != nil { t.Errorf("receipts mismatch: %v", err) } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index d496e6679d..73f299096e 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -282,19 +282,25 @@ func ServiceGetReceiptsQuery68(chain *core.BlockChain, query GetReceiptsRequest) break } // Retrieve the requested block's receipts - results := chain.GetReceiptsByHash(hash) + results := chain.GetRawReceiptsByHash(hash) if results == nil { if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { continue } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) } else { - receipts = append(receipts, encoded) - bytes += len(encoded) + body := chain.GetBodyRLP(hash) + if body == nil { + continue + } + var err error + results, err = blockReceiptsToNetwork68(results, body) + if err != nil { + log.Error("Error in block receipts conversion", "hash", hash, "err", err) + continue + } } + receipts = append(receipts, results) + bytes += len(results) } return receipts } @@ -324,7 +330,7 @@ func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) continue } var err error - results, err = blockReceiptsToNetwork(results, body) + results, err = blockReceiptsToNetwork69(results, body) if err != nil { log.Error("Error in block receipts conversion", "hash", hash, "err", err) continue @@ -393,53 +399,30 @@ func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { }, metadata) } -func handleReceipts68(backend Backend, msg Decoder, peer *Peer) error { +func handleReceipts[L ReceiptsList](backend Backend, msg Decoder, peer *Peer) error { // A batch of receipts arrived to one of our previous requests - res := new(ReceiptsPacket) + res := new(ReceiptsPacket[L]) if err := msg.Decode(res); err != nil { return err } - metadata := func() interface{} { - hasher := trie.NewStackTrie(nil) - hashes := make([]common.Hash, len(res.ReceiptsResponse)) - for i, receipt := range res.ReceiptsResponse { - hashes[i] = types.DeriveSha(receipt, hasher) - } - return hashes - } - encoded := ReceiptsRLPResponse(types.ReceiptsToRLP(res.ReceiptsResponse)) - return peer.dispatchResponse(&Response{ - id: res.RequestId, - code: ReceiptsMsg, - Res: &encoded, - }, metadata) -} - -func handleReceipts69(backend Backend, msg Decoder, peer *Peer) error { - // A batch of receipts arrived to one of our previous requests - var res ReceiptsPacket69 - if err := msg.Decode(&res); err != nil { - return err - } // Assign temporary hashing buffer to each list item, the same buffer is shared // between all receipt list instances. buffers := new(receiptListBuffers) for i := range res.List { - res.List[i].buf = buffers + res.List[i].setBuffers(buffers) } - metadata := func() any { + + metadata := func() interface{} { hasher := trie.NewStackTrie(nil) hashes := make([]common.Hash, len(res.List)) for i := range res.List { - hashes[i] = types.DeriveSha(&res.List[i], hasher) + hashes[i] = types.DeriveSha(res.List[i], hasher) } return hashes } - - // TODO this can probably be made 0-alloc. var enc ReceiptsRLPResponse for i := range res.List { - enc = append(enc, res.List[i].toStorageReceiptsRLP()) + enc = append(enc, res.List[i].EncodeForStorage()) } return peer.dispatchResponse(&Response{ id: res.RequestId, diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 25b0a3de5d..0b99860e32 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -265,18 +265,19 @@ type GetReceiptsPacket struct { // ReceiptsResponse is the network packet for block receipts distribution. type ReceiptsResponse []types.Receipts -// ReceiptsPacket is the network packet for block receipts distribution with -// request ID wrapping. -type ReceiptsPacket struct { - RequestId uint64 - ReceiptsResponse +// ReceiptsList is a type constraint for block receceipt list types. +type ReceiptsList interface { + *ReceiptList68 | *ReceiptList69 + setBuffers(*receiptListBuffers) + EncodeForStorage() rlp.RawValue + types.DerivableList } // ReceiptsPacket is the network packet for block receipts distribution with // request ID wrapping. -type ReceiptsPacket69 struct { +type ReceiptsPacket[L ReceiptsList] struct { RequestId uint64 - List []ReceiptList69 + List []L } // ReceiptsRLPResponse is used for receipts, when we already have it encoded diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index a1bce2f745..8a2559a6c5 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -75,7 +75,7 @@ func TestEmptyMessages(t *testing.T) { // All empty messages encodes to the same format want := common.FromHex("c4820457c0") - for i, msg := range []interface{}{ + for i, msg := range []any{ // Headers GetBlockHeadersPacket{1111, nil}, BlockHeadersPacket{1111, nil}, @@ -85,7 +85,6 @@ func TestEmptyMessages(t *testing.T) { BlockBodiesRLPPacket{1111, nil}, // Receipts GetReceiptsPacket{1111, nil}, - ReceiptsPacket{1111, nil}, // Transactions GetPooledTransactionsPacket{1111, nil}, PooledTransactionsPacket{1111, nil}, @@ -99,7 +98,8 @@ func TestEmptyMessages(t *testing.T) { BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{})}, // Receipts GetReceiptsPacket{1111, GetReceiptsRequest([]common.Hash{})}, - ReceiptsPacket{1111, ReceiptsResponse([]types.Receipts{})}, + ReceiptsPacket[*ReceiptList68]{1111, []*ReceiptList68{}}, + ReceiptsPacket[*ReceiptList69]{1111, []*ReceiptList69{}}, // Transactions GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest([]common.Hash{})}, PooledTransactionsPacket{1111, PooledTransactionsResponse([]*types.Transaction{})}, @@ -168,7 +168,7 @@ func TestMessages(t *testing.T) { receipts = []*types.Receipt{ { Status: types.ReceiptStatusFailed, - CumulativeGasUsed: 1, + CumulativeGasUsed: 333, Logs: []*types.Log{ { Address: common.BytesToAddress([]byte{0x11}), @@ -176,11 +176,21 @@ func TestMessages(t *testing.T) { Data: []byte{0x01, 0x00, 0xff}, }, }, - TxHash: hashes[0], - ContractAddress: common.BytesToAddress([]byte{0x01, 0x11, 0x11}), - GasUsed: 111111, + }, + { + Status: types.ReceiptStatusSuccessful, + CumulativeGasUsed: 444, + Logs: []*types.Log{ + { + Address: common.BytesToAddress([]byte{0x22}), + Topics: []common.Hash{common.HexToHash("05668"), common.HexToHash("9773")}, + Data: []byte{0x02, 0x0f, 0x0f, 0x0f, 0x06, 0x08}, + }, + }, }, } + miniDeriveFields(receipts[0], 0) + miniDeriveFields(receipts[1], 1) rlpData, err := rlp.EncodeToBytes(receipts) if err != nil { t.Fatal(err) @@ -221,12 +231,17 @@ func TestMessages(t *testing.T) { common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), }, { - ReceiptsPacket{1111, ReceiptsResponse([]types.Receipts{receipts})}, - common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + ReceiptsPacket[*ReceiptList68]{1111, []*ReceiptList68{NewReceiptList68(receipts)}}, + common.FromHex("f902e6820457f902e0f902ddf901688082014db9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ffb9016f01f9016b018201bcb9010000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000001000000000000000000000000000000000000000000000000040000000000000000000000000004000000000000000000000000000000000000000000000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000000040f862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"), }, { + // Identical to the eth/68 encoding above. ReceiptsRLPPacket{1111, ReceiptsRLPResponse([]rlp.RawValue{receiptsRlp})}, - common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), + common.FromHex("f902e6820457f902e0f902ddf901688082014db9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ffb9016f01f9016b018201bcb9010000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000001000000000000000000000000000000000000000000000000040000000000000000000000000004000000000000000000000000000000000000000000000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000000040f862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"), + }, + { + ReceiptsPacket[*ReceiptList69]{1111, []*ReceiptList69{NewReceiptList69(receipts)}}, + common.FromHex("f8da820457f8d5f8d3f866808082014df85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff86901018201bcf862f860940000000000000000000000000000000000000022f842a00000000000000000000000000000000000000000000000000000000000005668a0000000000000000000000000000000000000000000000000000000000000977386020f0f0f0608"), }, { GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest(hashes)}, diff --git a/eth/protocols/eth/receipt.go b/eth/protocols/eth/receipt.go index eb26b7c5bf..45c4766b17 100644 --- a/eth/protocols/eth/receipt.go +++ b/eth/protocols/eth/receipt.go @@ -19,13 +19,18 @@ package eth import ( "bytes" "fmt" + "io" "iter" + "math/big" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) -// Receipt is the eth/69 network encoding of a receipt. +// This is just a sanity limit for the size of a single receipt. +const maxReceiptSize = 16 * 1024 * 1024 + +// Receipt is the representation of receipts for networking purposes. type Receipt struct { TxType byte PostStateOrStatus []byte @@ -33,38 +38,91 @@ type Receipt struct { Logs rlp.RawValue } -func (r *Receipt) DecodeRLP(s *rlp.Stream) error { - if _, err := s.List(); err != nil { +func newReceipt(tr *types.Receipt) Receipt { + r := Receipt{TxType: tr.Type, GasUsed: tr.CumulativeGasUsed} + if tr.PostState != nil { + r.PostStateOrStatus = tr.PostState + } else { + r.PostStateOrStatus = new(big.Int).SetUint64(tr.Status).Bytes() + } + r.Logs, _ = rlp.EncodeToBytes(tr.Logs) + return r +} + +// decode68 parses a receipt in the eth/68 network encoding. +func (r *Receipt) decode68(buf *receiptListBuffers, s *rlp.Stream) error { + k, size, err := s.Kind() + if err != nil { return err } - txtype, err := s.Uint8() - if err != nil { - return fmt.Errorf("invalid txType: %w", err) + + *r = Receipt{} + if k == rlp.List { + // Legacy receipt. + return r.decodeInnerList(s, false, true) } - postStateOrStatus, err := s.Bytes() + // Typed receipt. + if size < 2 || size > maxReceiptSize { + return fmt.Errorf("invalid receipt size %d", size) + } + buf.tmp.Reset() + buf.tmp.Grow(int(size)) + payload := buf.tmp.Bytes()[:int(size)] + if err := s.ReadBytes(payload); err != nil { + return err + } + r.TxType = payload[0] + s2 := rlp.NewStream(bytes.NewReader(payload[1:]), 0) + return r.decodeInnerList(s2, false, true) +} + +// decode69 parses a receipt in the eth/69 network encoding. +func (r *Receipt) decode69(s *rlp.Stream) error { + *r = Receipt{} + return r.decodeInnerList(s, true, false) +} + +// decodeDatabase parses a receipt in the basic database encoding. +func (r *Receipt) decodeDatabase(txType byte, s *rlp.Stream) error { + *r = Receipt{TxType: txType} + return r.decodeInnerList(s, false, false) +} + +func (r *Receipt) decodeInnerList(s *rlp.Stream, readTxType, readBloom bool) error { + _, err := s.List() + if err != nil { + return err + } + if readTxType { + r.TxType, err = s.Uint8() + if err != nil { + return fmt.Errorf("invalid txType: %w", err) + } + } + r.PostStateOrStatus, err = s.Bytes() if err != nil { return fmt.Errorf("invalid postStateOrStatus: %w", err) } - gasUsed, err := s.Uint64() + r.GasUsed, err = s.Uint64() if err != nil { return fmt.Errorf("invalid gasUsed: %w", err) } - logs, err := s.Raw() + if readBloom { + var b types.Bloom + if err := s.ReadBytes(b[:]); err != nil { + return fmt.Errorf("invalid bloom: %v", err) + } + } + r.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 s.ListEnd() } -// encodeStorage writes the storage encoding, i.e. the result matches -// types.ReceiptForStorage. -func (r *Receipt) encodeStorage(w *rlp.EncoderBuffer) { +// encodeForStorage produces the the storage encoding, i.e. the result matches +// the RLP encoding of types.ReceiptForStorage. +func (r *Receipt) encodeForStorage(w *rlp.EncoderBuffer) { list := w.List() w.WriteBytes(r.PostStateOrStatus) w.WriteUint64(r.GasUsed) @@ -72,6 +130,60 @@ func (r *Receipt) encodeStorage(w *rlp.EncoderBuffer) { w.ListEnd(list) } +// encodeForNetwork68 produces the eth/68 network protocol encoding of a receipt. +// Note this recomputes the bloom filter of the receipt. +func (r *Receipt) encodeForNetwork68(buf *receiptListBuffers, w *rlp.EncoderBuffer) { + writeInner := func(w *rlp.EncoderBuffer) { + list := w.List() + w.WriteBytes(r.PostStateOrStatus) + w.WriteUint64(r.GasUsed) + bloom := r.bloom(&buf.bloom) + w.WriteBytes(bloom[:]) + w.Write(r.Logs) + w.ListEnd(list) + } + + if r.TxType == 0 { + writeInner(w) + } else { + buf.tmp.Reset() + buf.tmp.WriteByte(r.TxType) + buf.enc.Reset(&buf.tmp) + writeInner(&buf.enc) + buf.enc.Flush() + w.WriteBytes(buf.tmp.Bytes()) + } +} + +// encodeForNetwork69 produces the eth/69 network protocol encoding of a receipt. +func (r *Receipt) encodeForNetwork69(w *rlp.EncoderBuffer) { + list := w.List() + w.WriteUint64(uint64(r.TxType)) + w.WriteBytes(r.PostStateOrStatus) + w.WriteUint64(r.GasUsed) + w.Write(r.Logs) + w.ListEnd(list) +} + +// encodeForHash encodes a receipt for the block receiptsRoot derivation. +func (r *Receipt) encodeForHash(buf *receiptListBuffers, out *bytes.Buffer) { + // For typed receipts, add the tx type. + if r.TxType != 0 { + out.WriteByte(r.TxType) + } + // Encode list = [postStateOrStatus, gasUsed, bloom, logs]. + w := &buf.enc + w.Reset(out) + l := w.List() + w.WriteBytes(r.PostStateOrStatus) + w.WriteUint64(r.GasUsed) + bloom := r.bloom(&buf.bloom) + w.WriteBytes(bloom[:]) + w.Write(r.Logs) + w.ListEnd(l) + 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 @@ -83,8 +195,7 @@ func (r *Receipt) bloom(buffer *[6]byte) types.Bloom { return b } for logsIter.Next() { - log := logsIter.Value() - log, _, _ = rlp.SplitList(log) + log, _, _ := rlp.SplitList(logsIter.Value()) address, log, _ := rlp.SplitString(log) b.AddWithBuffer(address, buffer) topicsIter, err := rlp.NewListIterator(log) @@ -99,6 +210,133 @@ func (r *Receipt) bloom(buffer *[6]byte) types.Bloom { return b } +type receiptListBuffers struct { + enc rlp.EncoderBuffer + bloom [6]byte + tmp bytes.Buffer +} + +func initBuffers(buf **receiptListBuffers) { + if *buf == nil { + *buf = new(receiptListBuffers) + } +} + +// encodeForStorage encodes a list of receipts for the database. +func (buf *receiptListBuffers) encodeForStorage(rs []Receipt) rlp.RawValue { + var out bytes.Buffer + w := &buf.enc + w.Reset(&out) + outer := w.List() + for _, receipts := range rs { + receipts.encodeForStorage(w) + } + w.ListEnd(outer) + w.Flush() + return out.Bytes() +} + +// ReceiptList68 is a block receipt list as downloaded by eth/68. +// This also implements types.DerivableList for validation purposes. +type ReceiptList68 struct { + buf *receiptListBuffers + items []Receipt +} + +// NewReceiptList68 creates a receipt list. +// This is slow, and exists for testing purposes. +func NewReceiptList68(trs []*types.Receipt) *ReceiptList68 { + rl := &ReceiptList68{items: make([]Receipt, len(trs))} + for i, tr := range trs { + rl.items[i] = newReceipt(tr) + } + return rl +} + +func blockReceiptsToNetwork68(blockReceipts, blockBody rlp.RawValue) ([]byte, error) { + txTypesIter, err := txTypesInBody(blockBody) + if err != nil { + return nil, fmt.Errorf("invalid block body: %v", err) + } + nextTxType, stopTxTypes := iter.Pull(txTypesIter) + defer stopTxTypes() + + var ( + out bytes.Buffer + buf receiptListBuffers + ) + blockReceiptIter, _ := rlp.NewListIterator(blockReceipts) + innerReader := bytes.NewReader(nil) + innerStream := rlp.NewStream(innerReader, 0) + w := rlp.NewEncoderBuffer(&out) + outer := w.List() + for i := 0; blockReceiptIter.Next(); i++ { + content := blockReceiptIter.Value() + innerReader.Reset(content) + innerStream.Reset(innerReader, uint64(len(content))) + var r Receipt + txType, _ := nextTxType() + if err := r.decodeDatabase(txType, innerStream); err != nil { + return nil, fmt.Errorf("invalid database receipt %d: %v", i, err) + } + r.encodeForNetwork68(&buf, &w) + } + w.ListEnd(outer) + w.Flush() + return out.Bytes(), nil +} + +// setBuffers implements ReceiptsList. +func (rl *ReceiptList68) setBuffers(buf *receiptListBuffers) { + rl.buf = buf +} + +// EncodeForStorage encodes the receipts for storage into the database. +func (rl *ReceiptList68) EncodeForStorage() rlp.RawValue { + initBuffers(&rl.buf) + return rl.buf.encodeForStorage(rl.items) +} + +// Len implements types.DerivableList. +func (rl *ReceiptList68) Len() int { + return len(rl.items) +} + +// EncodeIndex implements types.DerivableList. +func (rl *ReceiptList68) EncodeIndex(i int, out *bytes.Buffer) { + initBuffers(&rl.buf) + rl.items[i].encodeForHash(rl.buf, out) +} + +// DecodeRLP decodes a list of receipts from the network format. +func (rl *ReceiptList68) DecodeRLP(s *rlp.Stream) error { + initBuffers(&rl.buf) + if _, err := s.List(); err != nil { + return err + } + for i := 0; s.MoreDataInList(); i++ { + var item Receipt + err := item.decode68(rl.buf, s) + if err != nil { + return fmt.Errorf("receipt %d: %v", i, err) + } + rl.items = append(rl.items, item) + } + return s.ListEnd() +} + +// EncodeRLP encodes the list into the network format of eth/68. +func (rl *ReceiptList68) EncodeRLP(_w io.Writer) error { + initBuffers(&rl.buf) + w := rlp.NewEncoderBuffer(_w) + outer := w.List() + for i := range rl.items { + rl.items[i].encodeForNetwork68(rl.buf, &w) + } + w.ListEnd(outer) + return w.Flush() +} + // ReceiptList69 is the block receipt list as downloaded by eth/69. // This implements types.DerivableList for validation purposes. type ReceiptList69 struct { @@ -106,39 +344,36 @@ type ReceiptList69 struct { items []Receipt } -type receiptListBuffers struct { - enc rlp.EncoderBuffer - bloom [6]byte +// NewReceiptList69 creates a receipt list. +// This is slow, and exists for testing purposes. +func NewReceiptList69(trs []*types.Receipt) *ReceiptList69 { + rl := &ReceiptList69{items: make([]Receipt, len(trs))} + for i, tr := range trs { + rl.items[i] = newReceipt(tr) + } + return rl } -// Len returns the length of the list. +// setBuffers implements ReceiptsList. +func (rl *ReceiptList69) setBuffers(buf *receiptListBuffers) { + rl.buf = buf +} + +// EncodeForStorage encodes the receipts for storage into the database. +func (rl *ReceiptList69) EncodeForStorage() rlp.RawValue { + initBuffers(&rl.buf) + return rl.buf.encodeForStorage(rl.items) +} + +// Len implements types.DerivableList. 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 - ) - if r.TxType != 0 { - b.WriteByte(r.TxType) - } - // encode 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) - w.Flush() +func (rl *ReceiptList69) EncodeIndex(i int, out *bytes.Buffer) { + initBuffers(&rl.buf) + rl.items[i].encodeForHash(rl.buf, out) } // DecodeRLP decodes a list receipts from the network format. @@ -148,7 +383,7 @@ func (rl *ReceiptList69) DecodeRLP(s *rlp.Stream) error { } for i := 0; s.MoreDataInList(); i++ { var item Receipt - err := item.DecodeRLP(s) + err := item.decode69(s) if err != nil { return fmt.Errorf("receipt %d: %v", i, err) } @@ -157,30 +392,21 @@ func (rl *ReceiptList69) DecodeRLP(s *rlp.Stream) error { return s.ListEnd() } -// toStorageReceiptsRLP encodes the receipts for storage into the database. -func (rl *ReceiptList69) toStorageReceiptsRLP() rlp.RawValue { - if rl.buf == nil { - rl.buf = new(receiptListBuffers) - } - - var ( - out bytes.Buffer - w = &rl.buf.enc - ) - w.Reset(&out) +// EncodeRLP encodes the list into the network format of eth/69. +func (rl *ReceiptList69) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) outer := w.List() - for _, receipts := range rl.items { - receipts.encodeStorage(w) + for i := range rl.items { + rl.items[i].encodeForNetwork69(&w) } w.ListEnd(outer) - w.Flush() - return out.Bytes() + return w.Flush() } -// blockReceiptsToNetwork takes a slice of rlp-encoded receipts, and transactions, +// blockReceiptsToNetwork69 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, blockBody rlp.RawValue) ([]byte, error) { +func blockReceiptsToNetwork69(blockReceipts, blockBody rlp.RawValue) ([]byte, error) { txTypesIter, err := txTypesInBody(blockBody) if err != nil { return nil, fmt.Errorf("invalid block body: %v", err) diff --git a/eth/protocols/eth/receipt_test.go b/eth/protocols/eth/receipt_test.go index 78676c66c0..e2d15ced41 100644 --- a/eth/protocols/eth/receipt_test.go +++ b/eth/protocols/eth/receipt_test.go @@ -26,7 +26,13 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -func TestTransformReceipts(t *testing.T) { +// miniDeriveFields derives the necessary receipt fields to make types.DeriveSha work. +func miniDeriveFields(r *types.Receipt, txType byte) { + r.Type = txType + r.Bloom = types.CreateBloom(r) +} + +func TestReceiptList69(t *testing.T) { logs := []*types.Log{{Address: common.Address{1}, Topics: []common.Hash{{1}}}} encLogs, _ := rlp.EncodeToBytes(logs) @@ -66,7 +72,7 @@ func TestTransformReceipts(t *testing.T) { encBlockBody, _ := rlp.EncodeToBytes(blockBody) // convert from storage encoding to network encoding - network, err := blockReceiptsToNetwork(in, encBlockBody) + network, err := blockReceiptsToNetwork69(in, encBlockBody) if err != nil { t.Fatalf("test[%d]: blockReceiptsToNetwork error: %v", i, err) } @@ -76,7 +82,7 @@ func TestTransformReceipts(t *testing.T) { if err := rlp.DecodeBytes(network, &rl); err != nil { t.Fatalf("test[%d]: can't decode network receipts: %v", i, err) } - storageEnc := rl.toStorageReceiptsRLP() + storageEnc := rl.EncodeForStorage() if !bytes.Equal(storageEnc, in) { t.Fatalf("test[%d]: re-encoded receipts not equal\nhave: %x\nwant: %x", i, storageEnc, in) } @@ -85,9 +91,7 @@ func TestTransformReceipts(t *testing.T) { receipts := make(types.Receipts, len(test.input)) for i := range test.input { r := types.Receipt(test.input[i]) - // need to derive these fields to get the correct result from DeriveSha. - r.Type = test.txs[i].Type() - r.Bloom = types.CreateBloom(&r) + miniDeriveFields(&r, test.txs[i].Type()) receipts[i] = &r } expectedHash := types.DeriveSha(receipts, trie.NewStackTrie(nil)) @@ -99,14 +103,3 @@ func TestTransformReceipts(t *testing.T) { } } } - -func TestReceiptsMessage69(t *testing.T) { - msg := common.FromHex("f9037c880e17dea70d94cb05f90370e7e680a0c97ed91127dd6f6a099889686685b3b204a5888a9d84431747324654eca53e4c825208c0e7e680a00ba7c3d24be245396fc5d3f80d8a69813637c63b2750a0aecca0ecf1893ae6de825208c0e7e680a0b1bdb7832a63083237ae909f02b5290e5ead11e947f90ad406ce58af42d9cbf2825208c0e7e680a0a8db4c173896d1961aab985d4084276cfbb5b460184c72ed9148093427813e50825208c0e7e680a02ac83e5239694103e4b8bfe88055573eb6947eb9ec456bd1ecd40c3a390c1184825208c0e7e680a05d61474327cd80ea904886eb01497db86aa4e1826f60ba8041b342b7e3271451825208c0e7e680a0c586c7bffdaa7eafd1e64755f6438d4737d3da328e4bf15bf36f425e35dcba57825208c0e7e680a0db8c7c749080c80efc9b8fec0036b7aebe1fdb130cc0529cd41658f0e4073f32825208c0e7e680a052820bf047785bce72e004175cf20430228ed6e3aafd2f0dc9d881ab4b8247b0825208c0e7e680a00b8fa31c4ba7486ba43779175b05f07e9a913d97f0d784294283c67ca08ecade825208c0e7e680a059d6258b1d014be483b4393850a81fa109d80d0d222acc0ca4897332839f8760825208c0e7e680a0128b6226c363fe1fe0a94bc576b46569ee4d59cfcff913df74165294bb1478de825208c0e7e680a0525f09191eb703dce45154db01474a84191b45bddd36e99b6cbf59a5a40ee935825208c0e7e680a032664bd056b1e2ae4dd7293e578b5d7300f97e1a1a81432d310d7100d1931c2d825208c0e7e680a07d29340b854047e215e787d04073bbf4451a4fbdeaaaac8ef67934eb2612d264825208c0e7e680a054b35228ab2fd0a096976f3c144cb86bed4f48e952ac78b4c4bd8ee377ce686a8256d0c0e7e680a0bf5fb6d0da92232bc4b0a5c8bc6675a9f298c23ce747877074fe24f6e2d75dfa825208c0e7e680a04fbf751cb29c49f34c9da0d8fe456afc8672b2e6049d51b01ff40456661456e9825208c0e7e680a050397035c265d9d628bf382e072fec34e72c98a86f48f7c6afdd7bbb128ea7e2825208c0e7e680a0ed10826649c2bdb34763b3f293cc18564cc883d6b6cbb84df75e7e328d3e2594825208c0e7e680a02ca0ecfc7149619fac0b5376a8a6fd1b0da6371ad875d50603d2adb667f12c2f825208c0e7e680a0801743bd8c5e43b7b77024ff80c4e437d8b505f8295dfb179f667b22c0762637825208c0") - var dec ReceiptsPacket69 - if err := rlp.DecodeBytes(msg, &dec); err != nil { - t.Fatal(err) - } - if len(dec.List) != 22 { - t.Fatalf("wrong list length %d", len(dec.List)) - } -}