beacon/engine, eth/catalyst: return block access lists in payload bodies v2 (#35347)

This commit is contained in:
Stefan 2026-07-13 09:55:39 +02:00 committed by GitHub
parent e7314c8a13
commit b34a925e9e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 99 additions and 12 deletions

View file

@ -415,6 +415,12 @@ type ExecutionPayloadBody struct {
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}
// ExecutionPayloadBodyV2 extends ExecutionPayloadBody with the block access list.
type ExecutionPayloadBodyV2 struct {
ExecutionPayloadBody
BlockAccessList *bal.BlockAccessList `json:"blockAccessList"`
}
// Client identifiers to support ClientVersionV1.
const (
ClientCode = "GE"

View file

@ -1176,13 +1176,13 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engin
return bodies
}
// GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list
// GetPayloadBodiesByHashV2 implements engine_getPayloadBodiesByHashV2 which allows for retrieval of a list
// of block bodies by the engine api.
func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBody {
bodies := make([]*engine.ExecutionPayloadBody, len(hashes))
func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engine.ExecutionPayloadBodyV2 {
bodies := make([]*engine.ExecutionPayloadBodyV2, len(hashes))
for i, hash := range hashes {
block := api.eth.BlockChain().GetBlockByHash(hash)
bodies[i] = getBody(block)
bodies[i] = getBodyV2(block)
}
return bodies
}
@ -1190,16 +1190,16 @@ func (api *ConsensusAPI) GetPayloadBodiesByHashV2(hashes []common.Hash) []*engin
// GetPayloadBodiesByRangeV1 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
// of block bodies by the engine api.
func (api *ConsensusAPI) GetPayloadBodiesByRangeV1(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
return api.getBodiesByRange(start, count)
return getBodiesByRange(api, start, count, getBody)
}
// GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV1 which allows for retrieval of a range
// GetPayloadBodiesByRangeV2 implements engine_getPayloadBodiesByRangeV2 which allows for retrieval of a range
// of block bodies by the engine api.
func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
return api.getBodiesByRange(start, count)
func (api *ConsensusAPI) GetPayloadBodiesByRangeV2(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBodyV2, error) {
return getBodiesByRange(api, start, count, getBodyV2)
}
func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engine.ExecutionPayloadBody, error) {
func getBodiesByRange[T any](api *ConsensusAPI, start, count hexutil.Uint64, getBody func(*types.Block) *T) ([]*T, error) {
if start == 0 || count == 0 {
return nil, engine.InvalidParams.With(fmt.Errorf("invalid start or count, start: %v count: %v", start, count))
}
@ -1212,7 +1212,7 @@ func (api *ConsensusAPI) getBodiesByRange(start, count hexutil.Uint64) ([]*engin
if last > current {
last = current
}
bodies := make([]*engine.ExecutionPayloadBody, 0, uint64(count))
bodies := make([]*T, 0, uint64(count))
for i := uint64(start); i <= last; i++ {
block := api.eth.BlockChain().GetBlockByNumber(i)
bodies = append(bodies, getBody(block))
@ -1241,6 +1241,17 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody {
return &result
}
func getBodyV2(block *types.Block) *engine.ExecutionPayloadBodyV2 {
body := getBody(block)
if body == nil {
return nil
}
return &engine.ExecutionPayloadBodyV2{
ExecutionPayloadBody: *body,
BlockAccessList: block.AccessList(),
}
}
// convertRequests converts a hex requests slice to plain [][]byte.
func convertRequests(hex []hexutil.Bytes) [][]byte {
if hex == nil {

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"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/eth"
@ -1366,7 +1367,7 @@ func TestGetBlockBodiesByHash(t *testing.T) {
for k, test := range tests {
result := api.GetPayloadBodiesByHashV2(test.hashes)
for i, r := range result {
if err := checkEqualBody(test.results[i], r); err != nil {
if err := checkEqualBodyV2(test.results[i], r); err != nil {
t.Fatalf("test %v: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r)
}
}
@ -1444,7 +1445,7 @@ func TestGetBlockBodiesByRange(t *testing.T) {
}
if len(result) == len(test.results) {
for i, r := range result {
if err := checkEqualBody(test.results[i], r); err != nil {
if err := checkEqualBodyV2(test.results[i], r); err != nil {
t.Fatalf("test %d: invalid response: %v\nexpected %+v\ngot %+v", k, err, test.results[i], r)
}
}
@ -1520,6 +1521,75 @@ func checkEqualBody(a *types.Body, b *engine.ExecutionPayloadBody) error {
return nil
}
func checkEqualBodyV2(a *types.Body, b *engine.ExecutionPayloadBodyV2) error {
if b == nil {
return checkEqualBody(a, nil)
}
return checkEqualBody(a, &b.ExecutionPayloadBody)
}
func TestGetPayloadBodyV2BlockAccessList(t *testing.T) {
empty := bal.BlockAccessList{}
emptyHash := empty.Hash()
tests := []struct {
name string
header *types.Header
accessList *bal.BlockAccessList
want string
}{
{
name: "retained empty BAL",
header: &types.Header{BlockAccessListHash: &emptyHash},
accessList: &empty,
want: "[]",
},
{
name: "pruned BAL",
header: &types.Header{BlockAccessListHash: &emptyHash},
want: "null",
},
{
name: "pre-Amsterdam block",
header: new(types.Header),
want: "null",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
block := types.NewBlockWithHeader(test.header).WithAccessListUnsafe(test.accessList)
body := getBodyV2(block)
encoded, err := json.Marshal(body)
if err != nil {
t.Fatal(err)
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(encoded, &fields); err != nil {
t.Fatal(err)
}
if got := string(fields["blockAccessList"]); got != test.want {
t.Fatalf("unexpected blockAccessList: got %s, want %s", got, test.want)
}
})
}
}
func TestGetPayloadBodyV1OmitsBlockAccessList(t *testing.T) {
empty := bal.BlockAccessList{}
emptyHash := empty.Hash()
block := types.NewBlockWithHeader(&types.Header{BlockAccessListHash: &emptyHash}).WithAccessListUnsafe(&empty)
encoded, err := json.Marshal(getBody(block))
if err != nil {
t.Fatal(err)
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(encoded, &fields); err != nil {
t.Fatal(err)
}
if _, ok := fields["blockAccessList"]; ok {
t.Fatal("V1 payload body contains blockAccessList")
}
}
func TestBlockToPayloadWithBlobs(t *testing.T) {
header := types.Header{}
var txs []*types.Transaction