This commit is contained in:
Felix Lange 2025-01-14 11:09:13 +01:00
parent d86f0547cf
commit 08b6d495a9
6 changed files with 269 additions and 140 deletions

View file

@ -59,11 +59,12 @@ func (b *Bloom) SetBytes(d []byte) {
// Add adds d to the filter. Future calls of Test(d) will return true. // Add adds d to the filter. Future calls of Test(d) will return true.
func (b *Bloom) Add(d []byte) { 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) // 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) i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
b[i1] |= v1 b[i1] |= v1
b[i2] |= v2 b[i2] |= v2
@ -84,7 +85,8 @@ func (b Bloom) Bytes() []byte {
// Test checks if the given topic is present in the bloom filter // Test checks if the given topic is present in the bloom filter
func (b Bloom) Test(topic []byte) bool { 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] && return v1 == v1&b[i1] &&
v2 == v2&b[i2] && v2 == v2&b[i2] &&
v3 == v3&b[i3] v3 == v3&b[i3]
@ -107,9 +109,9 @@ func CreateBloom(receipt *Receipt) Bloom {
buf = make([]byte, 6) buf = make([]byte, 6)
) )
for _, log := range receipt.Logs { for _, log := range receipt.Logs {
bin.add(log.Address.Bytes(), buf) bin.AddWithBuffer(log.Address.Bytes(), buf)
for _, b := range log.Topics { for _, b := range log.Topics {
bin.add(b[:], buf) bin.AddWithBuffer(b[:], buf)
} }
} }
return bin return bin
@ -139,21 +141,20 @@ func Bloom9(data []byte) []byte {
} }
// bloomValues returns the bytes (index-value pairs) to set for the given data // 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 := hasherPool.Get().(crypto.KeccakState)
sha.Reset() sha.Reset()
sha.Write(data) sha.Write(data)
sha.Read(hashbuf) sha.Read(hashbuf[:])
hasherPool.Put(sha) hasherPool.Put(sha)
// The actual bits to flip // The actual bits to flip
v1 := byte(1 << (hashbuf[1] & 0x7)) v1 := byte(1 << (hashbuf[1] & 0x7))
v2 := byte(1 << (hashbuf[3] & 0x7)) v2 := byte(1 << (hashbuf[3] & 0x7))
v3 := byte(1 << (hashbuf[5] & 0x7)) v3 := byte(1 << (hashbuf[5] & 0x7))
// The indices for the bytes to OR in // 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 i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1 i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
return i1, v1, i2, v2, i3, v3 return i1, v1, i2, v2, i3, v3
} }

View file

@ -699,41 +699,3 @@ func testGetPooledTransaction(t *testing.T, blobTx bool) {
t.Errorf("pooled transaction mismatch: %v", err) 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)
}
}
}

View file

@ -17,7 +17,6 @@
package eth package eth
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -319,7 +318,7 @@ func serviceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest)
if header.ReceiptHash != types.EmptyReceiptsHash { if header.ReceiptHash != types.EmptyReceiptsHash {
body := new(types.Body) body := new(types.Body)
if err := rlp.DecodeBytes(chain.GetBodyRLP(hash), &body); err == nil { 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 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 { func handleNewBlockhashes(backend Backend, msg Decoder, peer *Peer) error {
return errors.New("block announcements disallowed") // We dropped support for non-merge networks 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 { if err := msg.Decode(res); err != nil {
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
} }
var response ReceiptsResponse buffers := new(receiptListBuffers)
// calculate the bloom filter before dispatching for _, rl := range res.List {
for _, receipts := range res.ReceiptsResponse69 { rl.buf = buffers
var rec []*types.Receipt // WIP
for _, receipt := range receipts {
receipt.Bloom = types.CreateBloom((*types.Receipt)(receipt))
rec = append(rec, (*types.Receipt)(receipt))
}
response = append(response, rec)
} }
metadata := func() interface{} { metadata := func() interface{} {
hasher := trie.NewStackTrie(nil) hasher := trie.NewStackTrie(nil)
hashes := make([]common.Hash, len(response)) hashes := make([]common.Hash, len(res.List))
for i, receipts := range response { for i, rl := range res.List {
var r []*types.Receipt hashes[i] = types.DeriveSha(&rl, hasher)
for _, receipt := range receipts {
r = append(r, (*types.Receipt)(receipt))
}
hashes[i] = types.DeriveSha(types.Receipts(r), hasher)
} }
return hashes return hashes
} }
return peer.dispatchResponse(&Response{ return peer.dispatchResponse(&Response{
id: res.RequestId, id: res.RequestId,
code: ReceiptsMsg, code: ReceiptsMsg,
Res: &response, Res: &res,
}, metadata) }, metadata)
} }

View file

@ -260,14 +260,11 @@ type ReceiptsPacket struct {
ReceiptsResponse ReceiptsResponse
} }
// ReceiptsResponse is the network packet for block receipts distribution.
type ReceiptsResponse69 [][]*receiptForNetwork
// ReceiptsPacket is the network packet for block receipts distribution with // ReceiptsPacket is the network packet for block receipts distribution with
// request ID wrapping. // request ID wrapping.
type ReceiptsPacket69 struct { type ReceiptsPacket69 struct {
RequestId uint64 RequestId uint64
ReceiptsResponse69 List []ReceiptList69
} }
// ReceiptsRLPResponse is used for receipts, when we already have it encoded // ReceiptsRLPResponse is used for receipts, when we already have it encoded

View file

@ -17,64 +17,206 @@
package eth package eth
import ( import (
"errors" "bytes"
"fmt"
"io" "io"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
type receiptForNetwork types.Receipt // Receipt is the eth/69 network encoding of a receipt.
type Receipt struct {
func (r *receiptForNetwork) DecodeRLP(s *rlp.Stream) error { TxType byte
kind, size, err := s.Kind() PostStateOrStatus []byte
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 GasUsed uint64
Logs []*types.Log Logs rlp.RawValue
}) }
if err := s.Decode(rec); err != nil {
func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
if _, err := s.List(); err != nil {
return err return err
} }
r.Type = rec.Type txtype, err := s.Uint8()
r.Status = rec.Status if err != nil {
r.GasUsed = rec.GasUsed return fmt.Errorf("invalid txType: %w", err)
r.Logs = rec.Logs }
} else { postStateOrStatus, err := s.Bytes()
s.Decode(&r) 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 return nil
} }
func (r *receiptForNetwork) EncodeRLP(_w io.Writer) error { func (r *Receipt) 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) w := rlp.NewEncoderBuffer(_w)
outerList := w.List() list := w.List()
w.Write([]byte{r.Type}) w.WriteUint64(uint64(r.TxType))
if r.Status == types.ReceiptStatusSuccessful { w.WriteBytes(r.PostStateOrStatus)
w.Write([]byte{0x01})
} else {
w.Write([]byte{0x00})
}
w.WriteUint64(r.GasUsed) w.WriteUint64(r.GasUsed)
logList := w.List() w.Write(r.Logs)
for _, log := range r.Logs { w.ListEnd(list)
if err := log.EncodeRLP(w); err != nil {
return err
}
}
w.ListEnd(logList)
w.ListEnd(outerList)
return w.Flush() 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()
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}
}