mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-20 23:09:27 +00:00
eth/protocols/eth: implement eth71 bal response (#34879)
This PR implements the serving side of the eth71 BAL exchange messages.
Until commit 4cd7092 also contained the requesting side, but since that
part still needs more work, I'm splitting it out into a separate PR.
The test injects BALs directly into rawdb. This can be removed once BAL
generation is integrated into the chain maker.
---------
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
1bdc4a60d9
commit
a484a8506d
6 changed files with 209 additions and 2 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Reference in a new issue