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"` 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. // Client identifiers to support ClientVersionV1.
const ( const (
ClientCode = "GE" ClientCode = "GE"

View file

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

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"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/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
@ -1366,7 +1367,7 @@ func TestGetBlockBodiesByHash(t *testing.T) {
for k, test := range tests { for k, test := range tests {
result := api.GetPayloadBodiesByHashV2(test.hashes) result := api.GetPayloadBodiesByHashV2(test.hashes)
for i, r := range result { 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) 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) { if len(result) == len(test.results) {
for i, r := range result { 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) 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 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) { func TestBlockToPayloadWithBlobs(t *testing.T) {
header := types.Header{} header := types.Header{}
var txs []*types.Transaction var txs []*types.Transaction