eth/protocols/eth: fix blockAccessList empty marker (#35286)

This PR addresses an issue in the eth71 `BlockAccessListsMsg` handler,

specifically: 
- if the requested bal is not accessible in the server side, 0x80
(EmptyString) will be returned as the marker
- at the client side, old message definition
`rlp.RawList[RawBlockAccessList]` assumes all the elements are List
- the message with 0x80 (kind = string) won't be decoded correctly
- the peer will be disconnected

The message definition has been changed to `rlp.RawList[rlp.RawValue]`,
which is aligned with the one in SNAP/2 protocol.
This commit is contained in:
rjl493456442 2026-07-03 17:15:07 +08:00 committed by GitHub
parent aa3d286f54
commit 3006c4411b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 12 deletions

View file

@ -742,7 +742,7 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
var ( var (
hashes []common.Hash hashes []common.Hash
expect rlp.RawList[RawBlockAccessList] expect rlp.RawList[rlp.RawValue]
) )
for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ {
block := backend.chain.GetBlockByNumber(i) block := backend.chain.GetBlockByNumber(i)
@ -771,6 +771,42 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
} }
} }
// TestBlockAccessListsUnavailableDecode checks that a BlockAccessLists response
// containing the EIP-8159 unavailability marker (RLP empty string).
func TestBlockAccessListsUnavailableDecode(t *testing.T) {
t.Parallel()
balRaw := makeTestBAL(t, common.Address{0x11})
// Assemble a response the way the serving side does, with the middle
// entry signaled as unavailable.
var list rlp.RawList[rlp.RawValue]
list.AppendRaw(balRaw)
list.AppendRaw(rlp.EmptyString)
list.AppendRaw(balRaw)
enc, err := rlp.EncodeToBytes(&BlockAccessListPacket{RequestId: 42, List: list})
if err != nil {
t.Fatalf("failed to encode packet: %v", err)
}
var packet BlockAccessListPacket
if err := rlp.DecodeBytes(enc, &packet); err != nil {
t.Fatalf("failed to decode packet: %v", err)
}
bals, err := packet.List.Items()
if err != nil {
t.Fatalf("failed to decode BAL entries: %v", err)
}
if len(bals) != 3 {
t.Fatalf("wrong entry count: got %d, want 3", len(bals))
}
for i, want := range [][]byte{balRaw, rlp.EmptyString, balRaw} {
if !bytes.Equal(bals[i], want) {
t.Errorf("entry %d mismatch: got %x, want %x", i, bals[i], want)
}
}
}
type decoder struct { type decoder struct {
msg []byte msg []byte
} }

View file

@ -679,10 +679,10 @@ func handleGetBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
// serviceGetBlockAccessListsQuery assembles the response to a BAL query. // serviceGetBlockAccessListsQuery assembles the response to a BAL query.
// Unavailable BALs are returned as empty list entries. // Unavailable BALs are returned as empty list entries.
func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[RawBlockAccessList] { func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[rlp.RawValue] {
var ( var (
bytes int bytes int
bals rlp.RawList[RawBlockAccessList] bals rlp.RawList[rlp.RawValue]
) )
for _, hash := range query { for _, hash := range query {
if bytes >= softResponseLimit || bals.Len() >= maxBALsServe { if bytes >= softResponseLimit || bals.Len() >= maxBALsServe {
@ -720,7 +720,12 @@ func handleBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
metadata := func() interface{} { metadata := func() interface{} {
hashes := make([]common.Hash, len(bals)) hashes := make([]common.Hash, len(bals))
for i := range bals { for i := range bals {
hashes[i] = crypto.Keccak256Hash(bals[i].Bytes()) // Unavailable BALs (signaled by the empty string) are marked
// with the zero hash
if bytes.Equal(bals[i], rlp.EmptyString) {
continue
}
hashes[i] = crypto.Keccak256Hash(bals[i])
} }
return hashes return hashes
} }

View file

@ -258,7 +258,7 @@ func (p *Peer) ReplyReceiptsRLP70(id uint64, receipts rlp.RawList[*ReceiptList],
} }
// ReplyBlockAccessLists is the response to GetBlockAccessLists (EIP-8159). // ReplyBlockAccessLists is the response to GetBlockAccessLists (EIP-8159).
func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[RawBlockAccessList]) error { func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[rlp.RawValue]) error {
return p2p.Send(p.rw, BlockAccessListsMsg, &BlockAccessListPacket{ return p2p.Send(p.rw, BlockAccessListsMsg, &BlockAccessListPacket{
RequestId: id, RequestId: id,
List: list, List: list,

View file

@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -299,15 +298,14 @@ type GetBlockAccessListsPacket struct {
GetBlockAccessListsRequest GetBlockAccessListsRequest
} }
type RawBlockAccessList struct { // BlockAccessListResponse holds one raw entry per requested hash. Entries are
rlp.RawList[bal.AccountAccess] // kept as raw values because, per EIP-8159, the RLP empty string signals an
} // unavailable BAL (an empty list is itself a valid BAL).
type BlockAccessListResponse []rlp.RawValue
type BlockAccessListResponse []RawBlockAccessList
type BlockAccessListPacket struct { type BlockAccessListPacket struct {
RequestId uint64 RequestId uint64
List rlp.RawList[RawBlockAccessList] List rlp.RawList[rlp.RawValue]
} }
func (*StatusPacket) Name() string { return "Status" } func (*StatusPacket) Name() string { return "Status" }