diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 18afa9ce9d..a540bbc11d 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -296,6 +296,7 @@ func (bc *BlockChain) GetReceiptsRLP(hash common.Hash) rlp.RawValue { return rawdb.ReadReceiptsRLP(bc.db, hash, number) } +// GetAccessListRLP retrieves the block access list of a block in RLP encoding. func (bc *BlockChain) GetAccessListRLP(hash common.Hash) rlp.RawValue { number, ok := rawdb.ReadHeaderNumber(bc.db, hash) if !ok { diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index f7d25bd8ca..154b75130c 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -53,6 +53,9 @@ const ( // containing 200+ transactions nowadays, the practical limit will always // be softResponseLimit. maxReceiptsServe = 1024 + + // maxBALsServe is the maximum number of block access lists to serve. + maxBALsServe = 1024 ) // Handler is a callback to invoke from an outside runner after the boilerplate @@ -197,6 +200,22 @@ var eth70 = map[uint64]msgHandler{ BlockRangeUpdateMsg: handleBlockRangeUpdate, } +var eth71 = map[uint64]msgHandler{ + TransactionsMsg: handleTransactions, + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes, + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetReceiptsMsg: handleGetReceipts70, + ReceiptsMsg: handleReceipts70, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, + BlockRangeUpdateMsg: handleBlockRangeUpdate, + GetBlockAccessListsMsg: handleGetBlockAccessLists, + BlockAccessListsMsg: handleBlockAccessLists, +} + // handleMessage is invoked whenever an inbound message is received from a remote // peer. The remote connection is torn down upon returning any error. func handleMessage(backend Backend, peer *Peer) error { @@ -216,6 +235,8 @@ func handleMessage(backend Backend, peer *Peer) error { handlers = eth69 case ETH70: handlers = eth70 + case ETH71: + handlers = eth71 default: return fmt.Errorf("unknown eth protocol version: %v", peer.version) } diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index d056d121d9..3f40fdb3b3 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/ethdb" @@ -709,6 +710,67 @@ func testGetBlockPartialReceipts(t *testing.T, protocol int) { } } +// makeTestBAL creates a BAL with a given address access and balance change, +// and returns its RLP encoding. This is used for injection into the chain DB via +// rawdb.WriteAccessListRLP. +// TODO: Should be deleted when bal is integrated with chain maker. +func makeTestBAL(t *testing.T, addr common.Address) rlp.RawValue { + cb := bal.NewConstructionBlockAccessList() + cb.AccountRead(addr) + cb.BalanceChange(0, addr, uint256.NewInt(1)) + var buf bytes.Buffer + if err := cb.EncodeRLP(&buf); err != nil { + t.Fatalf("failed to encode BAL: %v", err) + } + return buf.Bytes() +} + +// TestGetBlockAccessLists checks serving part of bal exchange +func TestGetBlockAccessLists(t *testing.T) { testGetBlockAccessLists(t, ETH71) } + +func testGetBlockAccessLists(t *testing.T, protocol uint) { + t.Parallel() + + backend := newTestBackend(5) + defer backend.close() + + peer, _ := newTestPeer("peer", protocol, backend) + defer peer.close() + + bal1 := makeTestBAL(t, common.Address{0x11}) + bal2 := makeTestBAL(t, common.Address{0x22}) + + var ( + hashes []common.Hash + expect rlp.RawList[RawBlockAccessList] + ) + for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { + block := backend.chain.GetBlockByNumber(i) + hashes = append(hashes, block.Hash()) + switch i { + case 1: + rawdb.WriteAccessListRLP(backend.db, block.Hash(), i, bal1) + expect.AppendRaw(bal1) + case 3: + rawdb.WriteAccessListRLP(backend.db, block.Hash(), i, bal2) + expect.AppendRaw(bal2) + default: + expect.AppendRaw(rlp.EmptyString) + } + } + + p2p.Send(peer.app, GetBlockAccessListsMsg, &GetBlockAccessListsPacket{ + RequestId: 123, + GetBlockAccessListsRequest: hashes, + }) + if err := p2p.ExpectMsg(peer.app, BlockAccessListsMsg, &BlockAccessListPacket{ + RequestId: 123, + List: expect, + }); err != nil { + t.Errorf("BAL response mismatch: %v", err) + } +} + type decoder struct { msg []byte } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 3254a0abc2..71942cc9ad 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -666,3 +666,68 @@ func handleBlockRangeUpdate(backend Backend, msg Decoder, peer *Peer) error { peer.lastRange.Store(&update) return nil } + +// handleGetBlockAccessLists serves a GetBlockAccessLists request. +func handleGetBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error { + var query GetBlockAccessListsPacket + if err := msg.Decode(&query); err != nil { + return err + } + response := serviceGetBlockAccessListsQuery(backend.Chain(), query.GetBlockAccessListsRequest) + return peer.ReplyBlockAccessLists(query.RequestId, response) +} + +// serviceGetBlockAccessListsQuery assembles the response to a BAL query. +// Unavailable BALs are returned as empty list entries. +func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[RawBlockAccessList] { + var ( + bytes int + bals rlp.RawList[RawBlockAccessList] + ) + for _, hash := range query { + if bytes >= softResponseLimit || bals.Len() >= maxBALsServe { + break + } + data := chain.GetAccessListRLP(hash) + if len(data) == 0 { + // The signal for missing BAL is the empty string, because + // an empty list is also a valid BAL. + bals.AppendRaw(rlp.EmptyString) + continue + } + bals.AppendRaw(data) + bytes += len(data) + } + return bals +} + +// handleBlockAccessLists processes an incoming BlockAccessLists response, +// validates it against the request tracker, and dispatches it to the waiting caller. +func handleBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error { + res := new(BlockAccessListPacket) + if err := msg.Decode(res); err != nil { + return err + } + tresp := tracker.Response{ID: res.RequestId, MsgCode: BlockAccessListsMsg, Size: res.List.Len()} + if err := peer.tracker.Fulfil(tresp); err != nil { + return fmt.Errorf("BlockAccessLists: %w", err) + } + bals, err := res.List.Items() + if err != nil { + return fmt.Errorf("BlockAccessLists: %w", err) + } + + metadata := func() interface{} { + hashes := make([]common.Hash, len(bals)) + for i := range bals { + hashes[i] = crypto.Keccak256Hash(bals[i].Bytes()) + } + return hashes + } + + return peer.dispatchResponse(&Response{ + id: res.RequestId, + code: BlockAccessListsMsg, + Res: (*BlockAccessListResponse)(&bals), + }, metadata) +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 754fd02be3..2d7079fa12 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -251,6 +251,36 @@ func (p *Peer) ReplyReceiptsRLP70(id uint64, receipts rlp.RawList[*ReceiptList], }) } +// ReplyBlockAccessLists is the response to GetBlockAccessLists (EIP-8159). +func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[RawBlockAccessList]) error { + return p2p.Send(p.rw, BlockAccessListsMsg, &BlockAccessListPacket{ + RequestId: id, + List: list, + }) +} + +// RequestBALs fetches block access lists for the given block hashes (EIP-8159) +func (p *Peer) RequestBALs(hashes []common.Hash, sink chan *Response) (*Request, error) { + p.Log().Debug("Fetching block access lists", "count", len(hashes)) + id := rand.Uint64() + + req := &Request{ + id: id, + sink: sink, + code: GetBlockAccessListsMsg, + want: BlockAccessListsMsg, + numItems: len(hashes), + data: &GetBlockAccessListsPacket{ + RequestId: id, + GetBlockAccessListsRequest: hashes, + }, + } + if err := p.dispatchRequest(req); err != nil { + return nil, err + } + return req, nil +} + // RequestOneHeader is a wrapper around the header query functions to fetch a // single header. It is used solely by the fetcher. func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request, error) { diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 0df0776c27..a6c45f83ec 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/rlp" ) @@ -31,6 +32,7 @@ import ( const ( ETH69 = 69 ETH70 = 70 + ETH71 = 71 ) // ProtocolName is the official short name of the `eth` protocol used during @@ -39,11 +41,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH70, ETH69} +var ProtocolVersions = []uint{ETH71, ETH70, ETH69} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH69: 18, ETH70: 18} +var protocolLengths = map[uint]uint64{ETH71: 20, ETH69: 18, ETH70: 18} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -66,6 +68,8 @@ const ( GetReceiptsMsg = 0x0f ReceiptsMsg = 0x10 BlockRangeUpdateMsg = 0x11 + GetBlockAccessListsMsg = 0x12 + BlockAccessListsMsg = 0x13 ) var ( @@ -288,6 +292,24 @@ type BlockRangeUpdatePacket struct { LatestBlockHash common.Hash } +type GetBlockAccessListsRequest []common.Hash + +type GetBlockAccessListsPacket struct { + RequestId uint64 + GetBlockAccessListsRequest +} + +type RawBlockAccessList struct { + rlp.RawList[bal.AccountAccess] +} + +type BlockAccessListResponse []RawBlockAccessList + +type BlockAccessListPacket struct { + RequestId uint64 + List rlp.RawList[RawBlockAccessList] +} + func (*StatusPacket) Name() string { return "Status" } func (*StatusPacket) Kind() byte { return StatusMsg } @@ -326,3 +348,9 @@ func (*ReceiptsRLPResponse) Kind() byte { return ReceiptsMsg } func (*BlockRangeUpdatePacket) Name() string { return "BlockRangeUpdate" } func (*BlockRangeUpdatePacket) Kind() byte { return BlockRangeUpdateMsg } + +func (*GetBlockAccessListsRequest) Name() string { return "GetBlockAccessLists" } +func (*GetBlockAccessListsRequest) Kind() byte { return GetBlockAccessListsMsg } + +func (*BlockAccessListResponse) Name() string { return "BlockAccessLists" } +func (*BlockAccessListResponse) Kind() byte { return BlockAccessListsMsg }