mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
beacon/engine/ssz: update types
This commit is contained in:
parent
b694357889
commit
8685b80f23
19 changed files with 921 additions and 702 deletions
|
|
@ -1,136 +0,0 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// PayloadAttributesCancun is the Cancun/Prague/Osaka build attribute shape.
|
||||
type PayloadAttributesCancun struct {
|
||||
Timestamp uint64
|
||||
PrevRandao common.Hash
|
||||
SuggestedFeeRecipient common.Address
|
||||
Withdrawals []*Withdrawal
|
||||
ParentBeaconBlockRoot common.Hash
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesCancun) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 8 + 32 + 20 + 4(offset) + 32 = 96
|
||||
size := uint32(96)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, a.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesCancun) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineUint64(c, &a.Timestamp)
|
||||
ssz.DefineStaticBytes(c, &a.PrevRandao)
|
||||
ssz.DefineStaticBytes(c, &a.SuggestedFeeRecipient)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineStaticBytes(c, &a.ParentBeaconBlockRoot)
|
||||
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// PayloadAttributesShanghai = Cancun minus parent_beacon_block_root.
|
||||
type PayloadAttributesShanghai struct {
|
||||
Timestamp uint64
|
||||
PrevRandao common.Hash
|
||||
SuggestedFeeRecipient common.Address
|
||||
Withdrawals []*Withdrawal
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesShanghai) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(8 + 32 + 20 + 4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, a.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesShanghai) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineUint64(c, &a.Timestamp)
|
||||
ssz.DefineStaticBytes(c, &a.PrevRandao)
|
||||
ssz.DefineStaticBytes(c, &a.SuggestedFeeRecipient)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// PayloadAttributesParis is the original (no withdrawals) shape.
|
||||
type PayloadAttributesParis struct {
|
||||
Timestamp uint64
|
||||
PrevRandao common.Hash
|
||||
SuggestedFeeRecipient common.Address
|
||||
}
|
||||
|
||||
func (*PayloadAttributesParis) SizeSSZ(*ssz.Sizer) uint32 { return 60 }
|
||||
|
||||
func (a *PayloadAttributesParis) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineUint64(c, &a.Timestamp)
|
||||
ssz.DefineStaticBytes(c, &a.PrevRandao)
|
||||
ssz.DefineStaticBytes(c, &a.SuggestedFeeRecipient)
|
||||
}
|
||||
|
||||
// ExecutionPayloadBodyCancun = Shanghai-shaped body (no BAL).
|
||||
type ExecutionPayloadBodyCancun struct {
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyCancun) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(8)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, b.Transactions)
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, b.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyCancun) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &b.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &b.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// ExecutionPayloadBodyParis is the original transactions-only body.
|
||||
type ExecutionPayloadBodyParis struct {
|
||||
Transactions [][]byte
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyParis) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, b.Transactions)
|
||||
return size
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyParis) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
package ssz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
|
@ -40,13 +42,70 @@ func (r *BodiesByHashRequest) DefineSSZ(c *ssz.Codec) {
|
|||
ssz.DefineSliceOfStaticBytesContent(c, &r.BlockHashes, MaxBodiesRequest)
|
||||
}
|
||||
|
||||
// BodyEntryAmsterdam is the per-block entry returned by /amsterdam/bodies/...
|
||||
type BodyEntryAmsterdam struct {
|
||||
Available bool
|
||||
Body *ExecutionPayloadBodyAmsterdam
|
||||
// ExecutionPayloadBody is the monolithic body shape returned by /{fork}/bodies.
|
||||
//
|
||||
// Field groups:
|
||||
// - base (Paris): Transactions
|
||||
// - withdrawals (Shanghai): Withdrawals
|
||||
// - bal (Amsterdam): BlockAccessList
|
||||
type ExecutionPayloadBody struct {
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal // Shanghai+
|
||||
BlockAccessList []byte // Amsterdam+
|
||||
}
|
||||
|
||||
func (e *BodyEntryAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
func (b *ExecutionPayloadBody) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
var size uint32 = 4 // transactions offset
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += 4 // withdrawals offset
|
||||
}
|
||||
if siz.Fork() >= forkAmsterdam {
|
||||
size += 4 // block_access_list offset
|
||||
}
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, b.Transactions)
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, b.Withdrawals)
|
||||
}
|
||||
if siz.Fork() >= forkAmsterdam {
|
||||
size += ssz.SizeDynamicBytes(siz, b.BlockAccessList)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBody) DefineSSZ(c *ssz.Codec) {
|
||||
// offset phase
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffsetOnFork(c, &b.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
ssz.DefineDynamicBytesOffsetOnFork(c, &b.BlockAccessList, MaxBalBytes, fromAmsterdam)
|
||||
|
||||
// content phase
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContentOnFork(c, &b.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
ssz.DefineDynamicBytesContentOnFork(c, &b.BlockAccessList, MaxBalBytes, fromAmsterdam)
|
||||
}
|
||||
|
||||
// Validate enforces that fields absent for the given fork are empty.
|
||||
func (b *ExecutionPayloadBody) Validate(fork ssz.Fork) error {
|
||||
if fork < ssz.ForkShapella && len(b.Withdrawals) > 0 {
|
||||
return fmt.Errorf("withdrawals set but fork %d predates Shanghai", fork)
|
||||
}
|
||||
if fork < forkAmsterdam && len(b.BlockAccessList) > 0 {
|
||||
return fmt.Errorf("block_access_list set but fork %d predates Amsterdam", fork)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BodyEntry is the per-block entry returned by /{fork}/bodies/... The Body's
|
||||
// wire shape follows the codec fork, so this single type spans all forks.
|
||||
type BodyEntry struct {
|
||||
Available bool
|
||||
Body *ExecutionPayloadBody
|
||||
}
|
||||
|
||||
func (e *BodyEntry) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(1 + 4) // bool + offset
|
||||
if fixed {
|
||||
return size
|
||||
|
|
@ -55,19 +114,19 @@ func (e *BodyEntryAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
|||
return size
|
||||
}
|
||||
|
||||
func (e *BodyEntryAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||
func (e *BodyEntry) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineBool(c, &e.Available)
|
||||
ssz.DefineDynamicObjectOffset(c, &e.Body)
|
||||
|
||||
ssz.DefineDynamicObjectContent(c, &e.Body)
|
||||
}
|
||||
|
||||
// BodiesResponseAmsterdam is the SSZ response of /amsterdam/bodies/...
|
||||
type BodiesResponseAmsterdam struct {
|
||||
Entries []*BodyEntryAmsterdam
|
||||
// BodiesResponse is the SSZ response of /{fork}/bodies/...
|
||||
type BodiesResponse struct {
|
||||
Entries []*BodyEntry
|
||||
}
|
||||
|
||||
func (r *BodiesResponseAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
func (r *BodiesResponse) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(4)
|
||||
if fixed {
|
||||
return size
|
||||
|
|
@ -76,87 +135,7 @@ func (r *BodiesResponseAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
|||
return size
|
||||
}
|
||||
|
||||
func (r *BodiesResponseAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicObjectsOffset(c, &r.Entries, MaxBodiesRequest)
|
||||
ssz.DefineSliceOfDynamicObjectsContent(c, &r.Entries, MaxBodiesRequest)
|
||||
}
|
||||
|
||||
// BodyEntryCancun is the /cancun/bodies/... entry (Shanghai-shape body).
|
||||
type BodyEntryCancun struct {
|
||||
Available bool
|
||||
Body *ExecutionPayloadBodyCancun
|
||||
}
|
||||
|
||||
func (e *BodyEntryCancun) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(1 + 4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicObject(siz, e.Body)
|
||||
return size
|
||||
}
|
||||
|
||||
func (e *BodyEntryCancun) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineBool(c, &e.Available)
|
||||
ssz.DefineDynamicObjectOffset(c, &e.Body)
|
||||
|
||||
ssz.DefineDynamicObjectContent(c, &e.Body)
|
||||
}
|
||||
|
||||
type BodiesResponseCancun struct {
|
||||
Entries []*BodyEntryCancun
|
||||
}
|
||||
|
||||
func (r *BodiesResponseCancun) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicObjects(siz, r.Entries)
|
||||
return size
|
||||
}
|
||||
|
||||
func (r *BodiesResponseCancun) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicObjectsOffset(c, &r.Entries, MaxBodiesRequest)
|
||||
ssz.DefineSliceOfDynamicObjectsContent(c, &r.Entries, MaxBodiesRequest)
|
||||
}
|
||||
|
||||
// BodyEntryParis is the /paris/bodies/... entry (transactions only).
|
||||
type BodyEntryParis struct {
|
||||
Available bool
|
||||
Body *ExecutionPayloadBodyParis
|
||||
}
|
||||
|
||||
func (e *BodyEntryParis) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(1 + 4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicObject(siz, e.Body)
|
||||
return size
|
||||
}
|
||||
|
||||
func (e *BodyEntryParis) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineBool(c, &e.Available)
|
||||
ssz.DefineDynamicObjectOffset(c, &e.Body)
|
||||
|
||||
ssz.DefineDynamicObjectContent(c, &e.Body)
|
||||
}
|
||||
|
||||
type BodiesResponseParis struct {
|
||||
Entries []*BodyEntryParis
|
||||
}
|
||||
|
||||
func (r *BodiesResponseParis) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
size := uint32(4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicObjects(siz, r.Entries)
|
||||
return size
|
||||
}
|
||||
|
||||
func (r *BodiesResponseParis) DefineSSZ(c *ssz.Codec) {
|
||||
func (r *BodiesResponse) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicObjectsOffset(c, &r.Entries, MaxBodiesRequest)
|
||||
ssz.DefineSliceOfDynamicObjectsContent(c, &r.Entries, MaxBodiesRequest)
|
||||
}
|
||||
|
|
|
|||
146
beacon/engine/ssz/bytecompat_test.go
Normal file
146
beacon/engine/ssz/bytecompat_test.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// Byte-compatibility guard for the monolith refactor. The golden hex strings
|
||||
// below were captured from the original per-fork structs (ExecutionPayload
|
||||
// {Paris,Shanghai,Cancun,Amsterdam} etc.) before they were collapsed into the
|
||||
// monolith types. Encoding the monolith with the same sample data on the
|
||||
// corresponding fork MUST reproduce these bytes exactly — that is the proof
|
||||
// the refactor is wire-compatible.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
func encodeHexOnFork(t *testing.T, obj ssz.Object, fork ssz.Fork) string {
|
||||
t.Helper()
|
||||
buf := make([]byte, ssz.SizeOnFork(obj, fork))
|
||||
if err := ssz.EncodeToBytesOnFork(buf, obj, fork); err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}
|
||||
|
||||
// golden bytes captured from the pre-refactor per-fork structs.
|
||||
const (
|
||||
goldenPayloadParis = "1100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005500000000000000000000000000000000000000000000000000000000000000640000000000000080c3c90100000000085200000000000000f1536500000000fc01000000863ba1010000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000ff010000657874080000000a000000020304"
|
||||
goldenPayloadShanghai = "1100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005500000000000000000000000000000000000000000000000000000000000000640000000000000080c3c90100000000085200000000000000f15365000000000002000000863ba1010000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000030200000e020000657874080000000a0000000203040100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
goldenPayloadCancun = "1100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005500000000000000000000000000000000000000000000000000000000000000640000000000000080c3c90100000000085200000000000000f15365000000001002000000863ba1010000000000000000000000000000000000000000000000000000006600000000000000000000000000000000000000000000000000000000000000130200001e02000000000200000000000000000000000000657874080000000a0000000203040100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
goldenPayloadAmsterdam = "1100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000033000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005500000000000000000000000000000000000000000000000000000000000000640000000000000080c3c90100000000085200000000000000f15365000000001c02000000863ba10100000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000001f0200002a0200000000020000000000000000000000000056020000c800000000000000657874080000000a0000000203040100000000000000020000000000000009000000000000000000000000000000000000000300000000000000deadbeef"
|
||||
|
||||
goldenAttrsParis = "00f153650000000055000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000"
|
||||
goldenAttrsShanghai = "00f153650000000055000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000400000000100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
goldenAttrsCancun = "00f1536500000000550000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000006000000077000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
goldenAttrsAmsterdam = "00f1536500000000550000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000007000000077000000000000000000000000000000000000000000000000000000000000002a0000000000000080c3c901000000000100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
|
||||
goldenBodyParis = "04000000080000000a000000020304"
|
||||
goldenBodyCancun = "0800000013000000080000000a0000000203040100000000000000020000000000000009000000000000000000000000000000000000000300000000000000"
|
||||
goldenBodyAmsterdam = "0c0000001700000043000000080000000a0000000203040100000000000000020000000000000009000000000000000000000000000000000000000300000000000000deadbeef"
|
||||
)
|
||||
|
||||
// sample field values, shared so only fork-specific fields move the bytes.
|
||||
var (
|
||||
gParentHash = common.Hash{0x11}
|
||||
gFeeRecipient = common.Address{0x22}
|
||||
gStateRoot = common.Hash{0x33}
|
||||
gReceiptsRoot = common.Hash{0x44}
|
||||
gPrevRandao = common.Hash{0x55}
|
||||
gBlockHash = common.Hash{0x66}
|
||||
gExtra = []byte("ext")
|
||||
gTxs = [][]byte{{0x02, 0x03}, {0x04}}
|
||||
gWds = []*Withdrawal{{Index: 1, ValidatorIndex: 2, Address: common.Address{0x09}, Amount: 3}}
|
||||
gBaseFee = uint256.NewInt(7e9)
|
||||
gBeaconRoot = common.Hash{0x77}
|
||||
gBAL = []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
)
|
||||
|
||||
// basePayload returns a monolith populated for the given fork (only the fork's
|
||||
// active fields are set, matching what the converters do).
|
||||
func basePayload(fork ssz.Fork) *ExecutionPayload {
|
||||
p := &ExecutionPayload{
|
||||
ParentHash: gParentHash, FeeRecipient: gFeeRecipient, StateRoot: gStateRoot,
|
||||
ReceiptsRoot: gReceiptsRoot, PrevRandao: gPrevRandao, BlockNumber: 100,
|
||||
GasLimit: 30000000, GasUsed: 21000, Timestamp: 1700000000, ExtraData: gExtra,
|
||||
BaseFeePerGas: gBaseFee, BlockHash: gBlockHash, Transactions: gTxs,
|
||||
}
|
||||
if fork >= ssz.ForkShapella {
|
||||
p.Withdrawals = gWds
|
||||
}
|
||||
if fork >= ssz.ForkDencun {
|
||||
blob, excess := uint64(131072), uint64(0)
|
||||
p.BlobGasUsed, p.ExcessBlobGas = &blob, &excess
|
||||
}
|
||||
if fork >= forkAmsterdam {
|
||||
slot := uint64(200)
|
||||
p.BlockAccessList = gBAL
|
||||
p.SlotNumber = &slot
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func baseAttrs(fork ssz.Fork) *PayloadAttributes {
|
||||
a := &PayloadAttributes{
|
||||
Timestamp: 1700000000, PrevRandao: gPrevRandao, SuggestedFeeRecipient: gFeeRecipient,
|
||||
}
|
||||
if fork >= ssz.ForkShapella {
|
||||
a.Withdrawals = gWds
|
||||
}
|
||||
if fork >= ssz.ForkDencun {
|
||||
root := gBeaconRoot
|
||||
a.ParentBeaconBlockRoot = &root
|
||||
}
|
||||
if fork >= forkAmsterdam {
|
||||
slot, tgl := uint64(42), uint64(30000000)
|
||||
a.SlotNumber, a.TargetGasLimit = &slot, &tgl
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func baseBody(fork ssz.Fork) *ExecutionPayloadBody {
|
||||
b := &ExecutionPayloadBody{Transactions: gTxs}
|
||||
if fork >= ssz.ForkShapella {
|
||||
b.Withdrawals = gWds
|
||||
}
|
||||
if fork >= forkAmsterdam {
|
||||
b.BlockAccessList = gBAL
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestMonolithByteCompat(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fork ssz.Fork
|
||||
obj ssz.Object
|
||||
want string
|
||||
}{
|
||||
{"payload.paris", ssz.ForkParis, basePayload(ssz.ForkParis), goldenPayloadParis},
|
||||
{"payload.shanghai", ssz.ForkShapella, basePayload(ssz.ForkShapella), goldenPayloadShanghai},
|
||||
{"payload.cancun", ssz.ForkDencun, basePayload(ssz.ForkDencun), goldenPayloadCancun},
|
||||
{"payload.amsterdam", forkAmsterdam, basePayload(forkAmsterdam), goldenPayloadAmsterdam},
|
||||
|
||||
{"attrs.paris", ssz.ForkParis, baseAttrs(ssz.ForkParis), goldenAttrsParis},
|
||||
{"attrs.shanghai", ssz.ForkShapella, baseAttrs(ssz.ForkShapella), goldenAttrsShanghai},
|
||||
{"attrs.cancun", ssz.ForkDencun, baseAttrs(ssz.ForkDencun), goldenAttrsCancun},
|
||||
{"attrs.amsterdam", forkAmsterdam, baseAttrs(forkAmsterdam), goldenAttrsAmsterdam},
|
||||
|
||||
{"body.paris", ssz.ForkParis, baseBody(ssz.ForkParis), goldenBodyParis},
|
||||
{"body.cancun", ssz.ForkDencun, baseBody(ssz.ForkDencun), goldenBodyCancun},
|
||||
{"body.amsterdam", forkAmsterdam, baseBody(forkAmsterdam), goldenBodyAmsterdam},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := encodeHexOnFork(t, tc.obj, tc.fork)
|
||||
if got != tc.want {
|
||||
t.Errorf("byte mismatch for %s\nwant %s\n got %s", tc.name, tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// statusToEnum maps the JSON-RPC PayloadStatusV1.Status string to the SSZ
|
||||
|
|
@ -127,46 +128,68 @@ func withdrawalsToTypes(ws []*Withdrawal) []*types.Withdrawal {
|
|||
return out
|
||||
}
|
||||
|
||||
// PayloadAttributesAmsterdamFromEngine converts a JSON-RPC engine.PayloadAttributes
|
||||
// into its Amsterdam SSZ form. The caller is responsible for ensuring the
|
||||
// attributes carry every Amsterdam-required field.
|
||||
func PayloadAttributesAmsterdamFromEngine(a *engine.PayloadAttributes) *PayloadAttributesAmsterdam {
|
||||
out := &PayloadAttributesAmsterdam{
|
||||
// PayloadAttributesFromEngine converts a JSON-RPC engine.PayloadAttributes into
|
||||
// the monolithic SSZ form for the given fork. Only the fields the fork's wire
|
||||
// shape carries are populated; the caller should pass the same fork to the
|
||||
// codec. target_gas_limit has no representation in engine.PayloadAttributes
|
||||
// today, so it is left nil for Amsterdam unless explicitly populated by the
|
||||
// caller afterwards.
|
||||
func PayloadAttributesFromEngine(a *engine.PayloadAttributes, fork ssz.Fork) *PayloadAttributes {
|
||||
out := &PayloadAttributes{
|
||||
Timestamp: a.Timestamp,
|
||||
PrevRandao: a.Random,
|
||||
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
||||
Withdrawals: withdrawalsFromTypes(a.Withdrawals),
|
||||
}
|
||||
if a.BeaconRoot != nil {
|
||||
out.ParentBeaconBlockRoot = *a.BeaconRoot
|
||||
if fork >= ssz.ForkShapella {
|
||||
out.Withdrawals = withdrawalsFromTypes(a.Withdrawals)
|
||||
}
|
||||
if a.SlotNumber != nil {
|
||||
out.SlotNumber = *a.SlotNumber
|
||||
if fork >= ssz.ForkDencun {
|
||||
root := common.Hash{}
|
||||
if a.BeaconRoot != nil {
|
||||
root = *a.BeaconRoot
|
||||
}
|
||||
out.ParentBeaconBlockRoot = &root
|
||||
}
|
||||
if fork >= forkAmsterdam {
|
||||
slot := uint64(0)
|
||||
if a.SlotNumber != nil {
|
||||
slot = *a.SlotNumber
|
||||
}
|
||||
out.SlotNumber = &slot
|
||||
// target_gas_limit not carried by engine.PayloadAttributes; default 0.
|
||||
tgl := uint64(0)
|
||||
out.TargetGasLimit = &tgl
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// PayloadAttributesAmsterdamToEngine is the inverse of the From helper. The
|
||||
// target_gas_limit field has no representation in engine.PayloadAttributes
|
||||
// today and is dropped on the way down; the caller can fish it out of the SSZ
|
||||
// struct directly.
|
||||
func PayloadAttributesAmsterdamToEngine(a *PayloadAttributesAmsterdam) *engine.PayloadAttributes {
|
||||
root := a.ParentBeaconBlockRoot
|
||||
slot := a.SlotNumber
|
||||
return &engine.PayloadAttributes{
|
||||
// PayloadAttributesToEngine is the inverse of PayloadAttributesFromEngine. The
|
||||
// target_gas_limit field has no representation in engine.PayloadAttributes and
|
||||
// is dropped; the caller can read it from the SSZ struct directly.
|
||||
func PayloadAttributesToEngine(a *PayloadAttributes) *engine.PayloadAttributes {
|
||||
out := &engine.PayloadAttributes{
|
||||
Timestamp: a.Timestamp,
|
||||
Random: a.PrevRandao,
|
||||
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
||||
Withdrawals: withdrawalsToTypes(a.Withdrawals),
|
||||
BeaconRoot: &root,
|
||||
SlotNumber: &slot,
|
||||
}
|
||||
if a.ParentBeaconBlockRoot != nil {
|
||||
root := *a.ParentBeaconBlockRoot
|
||||
out.BeaconRoot = &root
|
||||
}
|
||||
if a.SlotNumber != nil {
|
||||
slot := *a.SlotNumber
|
||||
out.SlotNumber = &slot
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ExecutionPayloadAmsterdamFromEngine converts an ExecutableData into the SSZ
|
||||
// Amsterdam payload shape. The block_access_list and slot_number fields are
|
||||
// expected to be present; missing values yield empty/zero defaults.
|
||||
func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPayloadAmsterdam {
|
||||
// ExecutionPayloadFromEngine converts an ExecutableData into the monolithic SSZ
|
||||
// payload for the given fork. Only the fork's active fields are populated so
|
||||
// the codec (driven by the same fork) and Validate(fork) stay consistent.
|
||||
// BlockAccessList is not yet wired through ExecutableData; the caller sets it
|
||||
// separately for Amsterdam.
|
||||
func ExecutionPayloadFromEngine(d *engine.ExecutableData, fork ssz.Fork) *ExecutionPayload {
|
||||
var bloom [256]byte
|
||||
copy(bloom[:], d.LogsBloom)
|
||||
var fee *uint256.Int
|
||||
|
|
@ -174,7 +197,7 @@ func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPay
|
|||
fee = new(uint256.Int)
|
||||
fee.SetFromBig(d.BaseFeePerGas)
|
||||
}
|
||||
out := &ExecutionPayloadAmsterdam{
|
||||
out := &ExecutionPayload{
|
||||
ParentHash: d.ParentHash,
|
||||
FeeRecipient: d.FeeRecipient,
|
||||
StateRoot: d.StateRoot,
|
||||
|
|
@ -189,48 +212,66 @@ func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPay
|
|||
BaseFeePerGas: fee,
|
||||
BlockHash: d.BlockHash,
|
||||
Transactions: d.Transactions,
|
||||
Withdrawals: withdrawalsFromTypes(d.Withdrawals),
|
||||
}
|
||||
if d.BlobGasUsed != nil {
|
||||
out.BlobGasUsed = *d.BlobGasUsed
|
||||
if fork >= ssz.ForkShapella {
|
||||
out.Withdrawals = withdrawalsFromTypes(d.Withdrawals)
|
||||
}
|
||||
if d.ExcessBlobGas != nil {
|
||||
out.ExcessBlobGas = *d.ExcessBlobGas
|
||||
if fork >= ssz.ForkDencun {
|
||||
blobGas := uint64(0)
|
||||
if d.BlobGasUsed != nil {
|
||||
blobGas = *d.BlobGasUsed
|
||||
}
|
||||
excess := uint64(0)
|
||||
if d.ExcessBlobGas != nil {
|
||||
excess = *d.ExcessBlobGas
|
||||
}
|
||||
out.BlobGasUsed = &blobGas
|
||||
out.ExcessBlobGas = &excess
|
||||
}
|
||||
if d.SlotNumber != nil {
|
||||
out.SlotNumber = *d.SlotNumber
|
||||
if fork >= forkAmsterdam {
|
||||
slot := uint64(0)
|
||||
if d.SlotNumber != nil {
|
||||
slot = *d.SlotNumber
|
||||
}
|
||||
out.SlotNumber = &slot
|
||||
}
|
||||
// BlockAccessList is not yet wired through ExecutableData; the caller
|
||||
// passes it as a separate field on the envelope when constructing the
|
||||
// final wire form.
|
||||
return out
|
||||
}
|
||||
|
||||
// ExecutionPayloadAmsterdamToEngine is the inverse helper. The caller is
|
||||
// responsible for the BAL payload that ExecutableData doesn't yet carry.
|
||||
func ExecutionPayloadAmsterdamToEngine(p *ExecutionPayloadAmsterdam) *engine.ExecutableData {
|
||||
// ExecutionPayloadToEngine is the inverse helper. The caller is responsible for
|
||||
// the BAL payload that ExecutableData doesn't yet carry.
|
||||
func ExecutionPayloadToEngine(p *ExecutionPayload) *engine.ExecutableData {
|
||||
bloom := append([]byte(nil), p.LogsBloom[:]...)
|
||||
out := &engine.ExecutableData{
|
||||
ParentHash: p.ParentHash,
|
||||
FeeRecipient: p.FeeRecipient,
|
||||
StateRoot: p.StateRoot,
|
||||
ReceiptsRoot: p.ReceiptsRoot,
|
||||
LogsBloom: bloom,
|
||||
Random: p.PrevRandao,
|
||||
Number: p.BlockNumber,
|
||||
GasLimit: p.GasLimit,
|
||||
GasUsed: p.GasUsed,
|
||||
Timestamp: p.Timestamp,
|
||||
ExtraData: append([]byte(nil), p.ExtraData...),
|
||||
BlockHash: p.BlockHash,
|
||||
Transactions: p.Transactions,
|
||||
Withdrawals: withdrawalsToTypes(p.Withdrawals),
|
||||
BlobGasUsed: &p.BlobGasUsed,
|
||||
ExcessBlobGas: &p.ExcessBlobGas,
|
||||
SlotNumber: &p.SlotNumber,
|
||||
ParentHash: p.ParentHash,
|
||||
FeeRecipient: p.FeeRecipient,
|
||||
StateRoot: p.StateRoot,
|
||||
ReceiptsRoot: p.ReceiptsRoot,
|
||||
LogsBloom: bloom,
|
||||
Random: p.PrevRandao,
|
||||
Number: p.BlockNumber,
|
||||
GasLimit: p.GasLimit,
|
||||
GasUsed: p.GasUsed,
|
||||
Timestamp: p.Timestamp,
|
||||
ExtraData: append([]byte(nil), p.ExtraData...),
|
||||
BlockHash: p.BlockHash,
|
||||
Transactions: p.Transactions,
|
||||
Withdrawals: withdrawalsToTypes(p.Withdrawals),
|
||||
}
|
||||
if p.BaseFeePerGas != nil {
|
||||
out.BaseFeePerGas = p.BaseFeePerGas.ToBig()
|
||||
}
|
||||
if p.BlobGasUsed != nil {
|
||||
blobGas := *p.BlobGasUsed
|
||||
out.BlobGasUsed = &blobGas
|
||||
}
|
||||
if p.ExcessBlobGas != nil {
|
||||
excess := *p.ExcessBlobGas
|
||||
out.ExcessBlobGas = &excess
|
||||
}
|
||||
if p.SlotNumber != nil {
|
||||
slot := *p.SlotNumber
|
||||
out.SlotNumber = &slot
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
// ExecutionPayloadEnvelopeAmsterdam is the request body of
|
||||
// POST /amsterdam/payloads.
|
||||
type ExecutionPayloadEnvelopeAmsterdam struct {
|
||||
Payload *ExecutionPayloadAmsterdam
|
||||
Payload *ExecutionPayload
|
||||
ParentBeaconBlockRoot common.Hash
|
||||
ExecutionRequests [][]byte
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ func (b *BlobsBundleV2) DefineSSZ(c *ssz.Codec) {
|
|||
|
||||
// BuiltPayloadAmsterdam is the response of GET /amsterdam/payloads/{id}.
|
||||
type BuiltPayloadAmsterdam struct {
|
||||
Payload *ExecutionPayloadAmsterdam
|
||||
Payload *ExecutionPayload
|
||||
BlockValue *uint256.Int
|
||||
BlobsBundle *BlobsBundleV2
|
||||
ExecutionRequests [][]byte
|
||||
|
|
@ -120,7 +120,7 @@ func (p *BuiltPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
|
|||
// length 0 or 1 of the 16-byte Bitvector wrapper.
|
||||
type ForkchoiceUpdateAmsterdam struct {
|
||||
ForkchoiceState *ForkchoiceState
|
||||
PayloadAttributes []*PayloadAttributesAmsterdam
|
||||
PayloadAttributes []*PayloadAttributes
|
||||
CustodyColumns []*Bitvector128
|
||||
}
|
||||
|
||||
|
|
|
|||
60
beacon/engine/ssz/forkmap.go
Normal file
60
beacon/engine/ssz/forkmap.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// karalabe/ssz's Fork enum currently stops at Pectra (Prague). The monolith
|
||||
// types below gate fields with ForkFilter{Added,Removed}, and the codec only
|
||||
// performs ordered comparisons (`fork < Added`, `fork >= Removed`) against
|
||||
// Codec.fork. Any strictly-monotonic values placed after ForkPectra therefore
|
||||
// behave correctly. We define local aliases for the forks the library doesn't
|
||||
// name yet.
|
||||
//
|
||||
// IMPORTANT: these are only meaningful relative to one another and to the
|
||||
// library's own enum; never persist them. If a future karalabe/ssz release
|
||||
// adds Osaka/Amsterdam (or new forks between Pectra and these), revisit this
|
||||
// table so the ordering stays gap-consistent with the upstream enum.
|
||||
const (
|
||||
forkOsaka = ssz.ForkPectra + 1
|
||||
forkAmsterdam = ssz.ForkPectra + 2
|
||||
)
|
||||
|
||||
// ForkFor maps a geth params/forks.Fork onto the karalabe/ssz Fork value
|
||||
// the codec multiplexes on. The bool is false for forks that predate the
|
||||
// Engine API (and thus have no SSZ wire representation here).
|
||||
func ForkFor(f forks.Fork) (ssz.Fork, bool) {
|
||||
switch f {
|
||||
case forks.Paris:
|
||||
return ssz.ForkParis, true
|
||||
case forks.Shanghai:
|
||||
return ssz.ForkShapella, true // EL Shanghai == CL Shapella
|
||||
case forks.Cancun:
|
||||
return ssz.ForkDencun, true // EL Cancun == CL Dencun
|
||||
case forks.Prague:
|
||||
return ssz.ForkPectra, true // EL Prague == CL Pectra
|
||||
case forks.Osaka:
|
||||
return forkOsaka, true
|
||||
case forks.Amsterdam:
|
||||
return forkAmsterdam, true
|
||||
default:
|
||||
return ssz.ForkUnknown, false
|
||||
}
|
||||
}
|
||||
251
beacon/engine/ssz/payload.go
Normal file
251
beacon/engine/ssz/payload.go
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// Fork-boundary filters. Fields gated by these only participate in size,
|
||||
// encode, decode and hash when Codec.fork is in range — see forkmap.go for how
|
||||
// the geth fork maps onto ssz.Fork. Grouping the filters here (rather than one
|
||||
// flag per field) keeps the "field-group" structure visible: each filter is a
|
||||
// group boundary, and DefineSSZ applies it to every field the group owns.
|
||||
var (
|
||||
// withdrawals group, introduced in Shanghai (Shapella).
|
||||
fromShanghai = ssz.ForkFilter{Added: ssz.ForkShapella}
|
||||
// blob-gas group, introduced in Cancun (Dencun).
|
||||
fromCancun = ssz.ForkFilter{Added: ssz.ForkDencun}
|
||||
// beacon-root attribute, introduced in Cancun (Dencun).
|
||||
// (shares the Cancun boundary but kept named for clarity at call sites)
|
||||
// bal + slot group, introduced in Amsterdam.
|
||||
fromAmsterdam = ssz.ForkFilter{Added: forkAmsterdam}
|
||||
)
|
||||
|
||||
// ExecutionPayload is the monolithic execution payload spanning every fork from
|
||||
// Paris onward. Fork-specific fields are gated with ...OnFork; the in-memory
|
||||
// struct is a superset and the fork passed to the codec selects the wire shape.
|
||||
//
|
||||
// Field groups (see the filter vars above):
|
||||
// - base (Paris): ParentHash … Transactions
|
||||
// - withdrawals (Shanghai): Withdrawals
|
||||
// - blobGas (Cancun): BlobGasUsed, ExcessBlobGas
|
||||
// - bal (Amsterdam): BlockAccessList, SlotNumber
|
||||
//
|
||||
// Gated scalars are pointers so the codec can distinguish "absent for this
|
||||
// fork" (nil) from "present and zero"; Validate(fork) enforces that invariant.
|
||||
type ExecutionPayload struct {
|
||||
// base group (Paris)
|
||||
ParentHash common.Hash
|
||||
FeeRecipient common.Address
|
||||
StateRoot common.Hash
|
||||
ReceiptsRoot common.Hash
|
||||
LogsBloom [256]byte
|
||||
PrevRandao common.Hash
|
||||
BlockNumber uint64
|
||||
GasLimit uint64
|
||||
GasUsed uint64
|
||||
Timestamp uint64
|
||||
ExtraData []byte
|
||||
BaseFeePerGas *uint256.Int
|
||||
BlockHash common.Hash
|
||||
Transactions [][]byte
|
||||
|
||||
// withdrawals group (Shanghai)
|
||||
Withdrawals []*Withdrawal
|
||||
|
||||
// blobGas group (Cancun)
|
||||
BlobGasUsed *uint64
|
||||
ExcessBlobGas *uint64
|
||||
|
||||
// bal group (Amsterdam)
|
||||
BlockAccessList []byte
|
||||
SlotNumber *uint64
|
||||
}
|
||||
|
||||
func (p *ExecutionPayload) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// Fixed portion is fork-dependent; let the codec primitives account for it
|
||||
// via the active fork rather than hand-summing magic byte constants.
|
||||
var size uint32
|
||||
// base fixed: 5 hashes(32) + addr(20) + bloom(256) + 4 u64s + uint256(32)
|
||||
// + 2 offsets(extra, txs)(4)
|
||||
size += 5*32 + 20 + 256 + 4*8 + 32 + 2*4
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += 4 // withdrawals offset
|
||||
}
|
||||
if siz.Fork() >= ssz.ForkDencun {
|
||||
size += 2 * 8 // blob_gas_used, excess_blob_gas
|
||||
}
|
||||
if siz.Fork() >= forkAmsterdam {
|
||||
size += 4 + 8 // block_access_list offset, slot_number
|
||||
}
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicBytes(siz, p.ExtraData)
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.Transactions)
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, p.Withdrawals)
|
||||
}
|
||||
if siz.Fork() >= forkAmsterdam {
|
||||
size += ssz.SizeDynamicBytes(siz, p.BlockAccessList)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (p *ExecutionPayload) DefineSSZ(c *ssz.Codec) {
|
||||
// --- offset phase ---
|
||||
ssz.DefineStaticBytes(c, &p.ParentHash)
|
||||
ssz.DefineStaticBytes(c, &p.FeeRecipient)
|
||||
ssz.DefineStaticBytes(c, &p.StateRoot)
|
||||
ssz.DefineStaticBytes(c, &p.ReceiptsRoot)
|
||||
ssz.DefineStaticBytes(c, &p.LogsBloom)
|
||||
ssz.DefineStaticBytes(c, &p.PrevRandao)
|
||||
ssz.DefineUint64(c, &p.BlockNumber)
|
||||
ssz.DefineUint64(c, &p.GasLimit)
|
||||
ssz.DefineUint64(c, &p.GasUsed)
|
||||
ssz.DefineUint64(c, &p.Timestamp)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineUint256(c, &p.BaseFeePerGas)
|
||||
ssz.DefineStaticBytes(c, &p.BlockHash)
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffsetOnFork(c, &p.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
ssz.DefineUint64PointerOnFork(c, &p.BlobGasUsed, fromCancun)
|
||||
ssz.DefineUint64PointerOnFork(c, &p.ExcessBlobGas, fromCancun)
|
||||
ssz.DefineDynamicBytesOffsetOnFork(c, &p.BlockAccessList, MaxBalBytes, fromAmsterdam)
|
||||
ssz.DefineUint64PointerOnFork(c, &p.SlotNumber, fromAmsterdam)
|
||||
|
||||
// --- content phase ---
|
||||
ssz.DefineDynamicBytesContent(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContentOnFork(c, &p.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
ssz.DefineDynamicBytesContentOnFork(c, &p.BlockAccessList, MaxBalBytes, fromAmsterdam)
|
||||
}
|
||||
|
||||
// Validate checks that the in-memory field set matches what the given fork's
|
||||
// wire shape carries: gated scalars must be present iff active, and absent
|
||||
// fields must be empty so the codec doesn't silently drop populated data.
|
||||
func (p *ExecutionPayload) Validate(fork ssz.Fork) error {
|
||||
// withdrawals (Shanghai+)
|
||||
if fork < ssz.ForkShapella && len(p.Withdrawals) > 0 {
|
||||
return fmt.Errorf("withdrawals set but fork %d predates Shanghai", fork)
|
||||
}
|
||||
// blobGas (Cancun+)
|
||||
if fork >= ssz.ForkDencun {
|
||||
if p.BlobGasUsed == nil || p.ExcessBlobGas == nil {
|
||||
return fmt.Errorf("blob gas fields required at fork %d", fork)
|
||||
}
|
||||
} else if p.BlobGasUsed != nil || p.ExcessBlobGas != nil {
|
||||
return fmt.Errorf("blob gas fields set but fork %d predates Cancun", fork)
|
||||
}
|
||||
// bal + slot (Amsterdam+)
|
||||
if fork >= forkAmsterdam {
|
||||
if p.SlotNumber == nil {
|
||||
return fmt.Errorf("slot_number required at fork %d", fork)
|
||||
}
|
||||
} else {
|
||||
if len(p.BlockAccessList) > 0 {
|
||||
return fmt.Errorf("block_access_list set but fork %d predates Amsterdam", fork)
|
||||
}
|
||||
if p.SlotNumber != nil {
|
||||
return fmt.Errorf("slot_number set but fork %d predates Amsterdam", fork)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PayloadAttributes is the monolithic build-attributes container spanning every
|
||||
// fork from Paris onward.
|
||||
//
|
||||
// Field groups:
|
||||
// - base (Paris): Timestamp, PrevRandao, SuggestedFeeRecipient
|
||||
// - withdrawals (Shanghai): Withdrawals
|
||||
// - beaconRoot (Cancun): ParentBeaconBlockRoot
|
||||
// - amsterdam (Amsterdam): SlotNumber, TargetGasLimit
|
||||
type PayloadAttributes struct {
|
||||
Timestamp uint64
|
||||
PrevRandao common.Hash
|
||||
SuggestedFeeRecipient common.Address
|
||||
|
||||
Withdrawals []*Withdrawal // Shanghai+
|
||||
|
||||
ParentBeaconBlockRoot *common.Hash // Cancun+
|
||||
|
||||
SlotNumber *uint64 // Amsterdam+
|
||||
TargetGasLimit *uint64 // Amsterdam+
|
||||
}
|
||||
|
||||
func (a *PayloadAttributes) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// base fixed: timestamp(8) + randao(32) + fee_recipient(20)
|
||||
var size uint32 = 8 + 32 + 20
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += 4 // withdrawals offset
|
||||
}
|
||||
if siz.Fork() >= ssz.ForkDencun {
|
||||
size += 32 // parent_beacon_block_root
|
||||
}
|
||||
if siz.Fork() >= forkAmsterdam {
|
||||
size += 8 + 8 // slot_number, target_gas_limit
|
||||
}
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
if siz.Fork() >= ssz.ForkShapella {
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, a.Withdrawals)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (a *PayloadAttributes) DefineSSZ(c *ssz.Codec) {
|
||||
// offset phase
|
||||
ssz.DefineUint64(c, &a.Timestamp)
|
||||
ssz.DefineStaticBytes(c, &a.PrevRandao)
|
||||
ssz.DefineStaticBytes(c, &a.SuggestedFeeRecipient)
|
||||
ssz.DefineSliceOfStaticObjectsOffsetOnFork(c, &a.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
ssz.DefineStaticBytesPointerOnFork(c, &a.ParentBeaconBlockRoot, fromCancun)
|
||||
ssz.DefineUint64PointerOnFork(c, &a.SlotNumber, fromAmsterdam)
|
||||
ssz.DefineUint64PointerOnFork(c, &a.TargetGasLimit, fromAmsterdam)
|
||||
|
||||
// content phase
|
||||
ssz.DefineSliceOfStaticObjectsContentOnFork(c, &a.Withdrawals, MaxWithdrawalsPerPayload, fromShanghai)
|
||||
}
|
||||
|
||||
// Validate enforces presence/absence of gated fields for the given fork.
|
||||
func (a *PayloadAttributes) Validate(fork ssz.Fork) error {
|
||||
if fork < ssz.ForkShapella && len(a.Withdrawals) > 0 {
|
||||
return fmt.Errorf("withdrawals set but fork %d predates Shanghai", fork)
|
||||
}
|
||||
if fork >= ssz.ForkDencun {
|
||||
if a.ParentBeaconBlockRoot == nil {
|
||||
return fmt.Errorf("parent_beacon_block_root required at fork %d", fork)
|
||||
}
|
||||
} else if a.ParentBeaconBlockRoot != nil {
|
||||
return fmt.Errorf("parent_beacon_block_root set but fork %d predates Cancun", fork)
|
||||
}
|
||||
if fork >= forkAmsterdam {
|
||||
if a.SlotNumber == nil || a.TargetGasLimit == nil {
|
||||
return fmt.Errorf("slot_number and target_gas_limit required at fork %d", fork)
|
||||
}
|
||||
} else if a.SlotNumber != nil || a.TargetGasLimit != nil {
|
||||
return fmt.Errorf("amsterdam attributes set but fork %d predates Amsterdam", fork)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// ExecutionPayloadAmsterdam carries the Amsterdam-shaped payload over the wire.
|
||||
type ExecutionPayloadAmsterdam struct {
|
||||
ParentHash common.Hash
|
||||
FeeRecipient common.Address
|
||||
StateRoot common.Hash
|
||||
ReceiptsRoot common.Hash
|
||||
LogsBloom [256]byte
|
||||
PrevRandao common.Hash
|
||||
BlockNumber uint64
|
||||
GasLimit uint64
|
||||
GasUsed uint64
|
||||
Timestamp uint64
|
||||
ExtraData []byte
|
||||
BaseFeePerGas *uint256.Int
|
||||
BlockHash common.Hash
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal
|
||||
BlobGasUsed uint64
|
||||
ExcessBlobGas uint64
|
||||
BlockAccessList []byte
|
||||
SlotNumber uint64
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 5 hashes(32) + addr(20) + bloom(256) + 4 u64s + uint256(32)
|
||||
// + 4 offsets(4) + 2 u64s (blob gas) + u64 (slot) = 540
|
||||
size := uint32(5*32 + 20 + 256 + 4*8 + 32 + 4*4 + 2*8 + 8)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicBytes(siz, p.ExtraData)
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.Transactions)
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, p.Withdrawals)
|
||||
size += ssz.SizeDynamicBytes(siz, p.BlockAccessList)
|
||||
return size
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineStaticBytes(c, &p.ParentHash)
|
||||
ssz.DefineStaticBytes(c, &p.FeeRecipient)
|
||||
ssz.DefineStaticBytes(c, &p.StateRoot)
|
||||
ssz.DefineStaticBytes(c, &p.ReceiptsRoot)
|
||||
ssz.DefineStaticBytes(c, &p.LogsBloom)
|
||||
ssz.DefineStaticBytes(c, &p.PrevRandao)
|
||||
ssz.DefineUint64(c, &p.BlockNumber)
|
||||
ssz.DefineUint64(c, &p.GasLimit)
|
||||
ssz.DefineUint64(c, &p.GasUsed)
|
||||
ssz.DefineUint64(c, &p.Timestamp)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineUint256(c, &p.BaseFeePerGas)
|
||||
ssz.DefineStaticBytes(c, &p.BlockHash)
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineUint64(c, &p.BlobGasUsed)
|
||||
ssz.DefineUint64(c, &p.ExcessBlobGas)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.BlockAccessList, MaxBalBytes)
|
||||
ssz.DefineUint64(c, &p.SlotNumber)
|
||||
|
||||
ssz.DefineDynamicBytesContent(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineDynamicBytesContent(c, &p.BlockAccessList, MaxBalBytes)
|
||||
}
|
||||
|
||||
// PayloadAttributesAmsterdam carries Amsterdam build attributes.
|
||||
type PayloadAttributesAmsterdam struct {
|
||||
Timestamp uint64
|
||||
PrevRandao common.Hash
|
||||
SuggestedFeeRecipient common.Address
|
||||
Withdrawals []*Withdrawal
|
||||
ParentBeaconBlockRoot common.Hash
|
||||
SlotNumber uint64
|
||||
TargetGasLimit uint64
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// timestamp(8) + randao(32) + fee_recipient(20) + offset(4)
|
||||
// + parent_beacon_block_root(32) + slot(8) + target_gas(8) = 112
|
||||
size := uint32(112)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, a.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (a *PayloadAttributesAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineUint64(c, &a.Timestamp)
|
||||
ssz.DefineStaticBytes(c, &a.PrevRandao)
|
||||
ssz.DefineStaticBytes(c, &a.SuggestedFeeRecipient)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineStaticBytes(c, &a.ParentBeaconBlockRoot)
|
||||
ssz.DefineUint64(c, &a.SlotNumber)
|
||||
ssz.DefineUint64(c, &a.TargetGasLimit)
|
||||
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &a.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// ExecutionPayloadBodyAmsterdam mirrors the /amsterdam/bodies response shape.
|
||||
type ExecutionPayloadBodyAmsterdam struct {
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal
|
||||
BlockAccessList []byte
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 3 offsets
|
||||
size := uint32(12)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, b.Transactions)
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, b.Withdrawals)
|
||||
size += ssz.SizeDynamicBytes(siz, b.BlockAccessList)
|
||||
return size
|
||||
}
|
||||
|
||||
func (b *ExecutionPayloadBodyAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &b.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineDynamicBytesOffset(c, &b.BlockAccessList, MaxBalBytes)
|
||||
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &b.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &b.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineDynamicBytesContent(c, &b.BlockAccessList, MaxBalBytes)
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// ExecutionPayloadCancun is the Cancun/Prague/Osaka payload shape.
|
||||
// Prague and Osaka don't change the inner payload — execution_requests
|
||||
// and the BlobsBundle revision change at the envelope level, not here.
|
||||
type ExecutionPayloadCancun struct {
|
||||
ParentHash common.Hash
|
||||
FeeRecipient common.Address
|
||||
StateRoot common.Hash
|
||||
ReceiptsRoot common.Hash
|
||||
LogsBloom [256]byte
|
||||
PrevRandao common.Hash
|
||||
BlockNumber uint64
|
||||
GasLimit uint64
|
||||
GasUsed uint64
|
||||
Timestamp uint64
|
||||
ExtraData []byte
|
||||
BaseFeePerGas *uint256.Int
|
||||
BlockHash common.Hash
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal
|
||||
BlobGasUsed uint64
|
||||
ExcessBlobGas uint64
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadCancun) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 5 hashes(32) + addr(20) + bloom(256) + 4 u64s + uint256(32)
|
||||
// + 3 offsets(4) + 2 u64s (blob gas) = 528
|
||||
size := uint32(5*32 + 20 + 256 + 4*8 + 32 + 3*4 + 2*8)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicBytes(siz, p.ExtraData)
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.Transactions)
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, p.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadCancun) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineStaticBytes(c, &p.ParentHash)
|
||||
ssz.DefineStaticBytes(c, &p.FeeRecipient)
|
||||
ssz.DefineStaticBytes(c, &p.StateRoot)
|
||||
ssz.DefineStaticBytes(c, &p.ReceiptsRoot)
|
||||
ssz.DefineStaticBytes(c, &p.LogsBloom)
|
||||
ssz.DefineStaticBytes(c, &p.PrevRandao)
|
||||
ssz.DefineUint64(c, &p.BlockNumber)
|
||||
ssz.DefineUint64(c, &p.GasLimit)
|
||||
ssz.DefineUint64(c, &p.GasUsed)
|
||||
ssz.DefineUint64(c, &p.Timestamp)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineUint256(c, &p.BaseFeePerGas)
|
||||
ssz.DefineStaticBytes(c, &p.BlockHash)
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
ssz.DefineUint64(c, &p.BlobGasUsed)
|
||||
ssz.DefineUint64(c, &p.ExcessBlobGas)
|
||||
|
||||
ssz.DefineDynamicBytesContent(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// ExecutionPayloadShanghai is the Shanghai payload shape (Cancun minus blob gas).
|
||||
type ExecutionPayloadShanghai struct {
|
||||
ParentHash common.Hash
|
||||
FeeRecipient common.Address
|
||||
StateRoot common.Hash
|
||||
ReceiptsRoot common.Hash
|
||||
LogsBloom [256]byte
|
||||
PrevRandao common.Hash
|
||||
BlockNumber uint64
|
||||
GasLimit uint64
|
||||
GasUsed uint64
|
||||
Timestamp uint64
|
||||
ExtraData []byte
|
||||
BaseFeePerGas *uint256.Int
|
||||
BlockHash common.Hash
|
||||
Transactions [][]byte
|
||||
Withdrawals []*Withdrawal
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadShanghai) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 5 hashes(32) + addr(20) + bloom(256) + 4 u64s + uint256(32) + 3 offsets(4) = 512
|
||||
size := uint32(5*32 + 20 + 256 + 4*8 + 32 + 3*4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicBytes(siz, p.ExtraData)
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.Transactions)
|
||||
size += ssz.SizeSliceOfStaticObjects(siz, p.Withdrawals)
|
||||
return size
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadShanghai) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineStaticBytes(c, &p.ParentHash)
|
||||
ssz.DefineStaticBytes(c, &p.FeeRecipient)
|
||||
ssz.DefineStaticBytes(c, &p.StateRoot)
|
||||
ssz.DefineStaticBytes(c, &p.ReceiptsRoot)
|
||||
ssz.DefineStaticBytes(c, &p.LogsBloom)
|
||||
ssz.DefineStaticBytes(c, &p.PrevRandao)
|
||||
ssz.DefineUint64(c, &p.BlockNumber)
|
||||
ssz.DefineUint64(c, &p.GasLimit)
|
||||
ssz.DefineUint64(c, &p.GasUsed)
|
||||
ssz.DefineUint64(c, &p.Timestamp)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineUint256(c, &p.BaseFeePerGas)
|
||||
ssz.DefineStaticBytes(c, &p.BlockHash)
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsOffset(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
|
||||
ssz.DefineDynamicBytesContent(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
ssz.DefineSliceOfStaticObjectsContent(c, &p.Withdrawals, MaxWithdrawalsPerPayload)
|
||||
}
|
||||
|
||||
// ExecutionPayloadParis is the original Bellatrix/Paris payload shape.
|
||||
type ExecutionPayloadParis struct {
|
||||
ParentHash common.Hash
|
||||
FeeRecipient common.Address
|
||||
StateRoot common.Hash
|
||||
ReceiptsRoot common.Hash
|
||||
LogsBloom [256]byte
|
||||
PrevRandao common.Hash
|
||||
BlockNumber uint64
|
||||
GasLimit uint64
|
||||
GasUsed uint64
|
||||
Timestamp uint64
|
||||
ExtraData []byte
|
||||
BaseFeePerGas *uint256.Int
|
||||
BlockHash common.Hash
|
||||
Transactions [][]byte
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadParis) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||
// 5 hashes(32) + addr(20) + bloom(256) + 4 u64s + uint256(32) + 2 offsets(4) = 508
|
||||
size := uint32(5*32 + 20 + 256 + 4*8 + 32 + 2*4)
|
||||
if fixed {
|
||||
return size
|
||||
}
|
||||
size += ssz.SizeDynamicBytes(siz, p.ExtraData)
|
||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.Transactions)
|
||||
return size
|
||||
}
|
||||
|
||||
func (p *ExecutionPayloadParis) DefineSSZ(c *ssz.Codec) {
|
||||
ssz.DefineStaticBytes(c, &p.ParentHash)
|
||||
ssz.DefineStaticBytes(c, &p.FeeRecipient)
|
||||
ssz.DefineStaticBytes(c, &p.StateRoot)
|
||||
ssz.DefineStaticBytes(c, &p.ReceiptsRoot)
|
||||
ssz.DefineStaticBytes(c, &p.LogsBloom)
|
||||
ssz.DefineStaticBytes(c, &p.PrevRandao)
|
||||
ssz.DefineUint64(c, &p.BlockNumber)
|
||||
ssz.DefineUint64(c, &p.GasLimit)
|
||||
ssz.DefineUint64(c, &p.GasUsed)
|
||||
ssz.DefineUint64(c, &p.Timestamp)
|
||||
ssz.DefineDynamicBytesOffset(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineUint256(c, &p.BaseFeePerGas)
|
||||
ssz.DefineStaticBytes(c, &p.BlockHash)
|
||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
|
||||
ssz.DefineDynamicBytesContent(c, &p.ExtraData, MaxExtraDataBytes)
|
||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.Transactions, MaxTxsPerPayload, MaxBytesPerTx)
|
||||
}
|
||||
|
|
@ -77,8 +77,30 @@ func TestPayloadStatusRoundtrip(t *testing.T) {
|
|||
encDec(t, &PayloadStatus{Status: StatusSyncing}, func() *PayloadStatus { return new(PayloadStatus) })
|
||||
}
|
||||
|
||||
// encDecOnFork is the fork-aware sibling of encDec for monolith types.
|
||||
func encDecOnFork[T ssz.Object](t *testing.T, obj T, fork ssz.Fork, newEmpty func() T) {
|
||||
t.Helper()
|
||||
size := ssz.SizeOnFork(obj, fork)
|
||||
buf := make([]byte, size)
|
||||
if err := ssz.EncodeToBytesOnFork(buf, obj, fork); err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
got := newEmpty()
|
||||
if err := ssz.DecodeFromBytesOnFork(buf, got, fork); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
rebuf := make([]byte, ssz.SizeOnFork(got, fork))
|
||||
if err := ssz.EncodeToBytesOnFork(rebuf, got, fork); err != nil {
|
||||
t.Fatalf("re-encode: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(buf, rebuf) {
|
||||
t.Fatalf("round-trip mismatch\nwant bytes: %x\n got bytes: %x", buf, rebuf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionPayloadAmsterdamRoundtrip(t *testing.T) {
|
||||
p := &ExecutionPayloadAmsterdam{
|
||||
blob, excess, slot := uint64(131072), uint64(0), uint64(200)
|
||||
p := &ExecutionPayload{
|
||||
ParentHash: common.Hash{0x11},
|
||||
FeeRecipient: common.Address{0x22},
|
||||
StateRoot: common.Hash{0x33},
|
||||
|
|
@ -93,26 +115,28 @@ func TestExecutionPayloadAmsterdamRoundtrip(t *testing.T) {
|
|||
BlockHash: common.Hash{0x66},
|
||||
Transactions: [][]byte{{0x02, 0x03}, {0x04}},
|
||||
Withdrawals: []*Withdrawal{{Index: 1, ValidatorIndex: 2, Amount: 3}},
|
||||
BlobGasUsed: 131072,
|
||||
ExcessBlobGas: 0,
|
||||
BlobGasUsed: &blob,
|
||||
ExcessBlobGas: &excess,
|
||||
BlockAccessList: []byte{0xde, 0xad, 0xbe, 0xef},
|
||||
SlotNumber: 200,
|
||||
SlotNumber: &slot,
|
||||
}
|
||||
encDec(t, p, func() *ExecutionPayloadAmsterdam { return new(ExecutionPayloadAmsterdam) })
|
||||
encDecOnFork(t, p, forkAmsterdam, func() *ExecutionPayload { return new(ExecutionPayload) })
|
||||
}
|
||||
|
||||
func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
||||
// With payload_attributes and custody_columns present.
|
||||
bits := &Bitvector128{Bytes: make([]byte, CellsPerExtBlob/8)}
|
||||
bits.Bytes[0] = 0x01
|
||||
attrs := &PayloadAttributesAmsterdam{
|
||||
root := common.Hash{0x77}
|
||||
slot, tgl := uint64(42), uint64(30000000)
|
||||
attrs := &PayloadAttributes{
|
||||
Timestamp: 1700000000,
|
||||
PrevRandao: common.Hash{0x55},
|
||||
SuggestedFeeRecipient: common.Address{0x66},
|
||||
Withdrawals: []*Withdrawal{},
|
||||
ParentBeaconBlockRoot: common.Hash{0x77},
|
||||
SlotNumber: 42,
|
||||
TargetGasLimit: 30000000,
|
||||
ParentBeaconBlockRoot: &root,
|
||||
SlotNumber: &slot,
|
||||
TargetGasLimit: &tgl,
|
||||
}
|
||||
fcu := &ForkchoiceUpdateAmsterdam{
|
||||
ForkchoiceState: &ForkchoiceState{
|
||||
|
|
@ -120,10 +144,10 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
|||
SafeBlockHash: common.Hash{0xbb},
|
||||
FinalizedBlockHash: common.Hash{0xcc},
|
||||
},
|
||||
PayloadAttributes: []*PayloadAttributesAmsterdam{attrs},
|
||||
PayloadAttributes: []*PayloadAttributes{attrs},
|
||||
CustodyColumns: []*Bitvector128{bits},
|
||||
}
|
||||
encDec(t, fcu, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
||||
encDecOnFork(t, fcu, forkAmsterdam, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
||||
|
||||
// Without optionals.
|
||||
bare := &ForkchoiceUpdateAmsterdam{
|
||||
|
|
@ -131,5 +155,5 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
|||
HeadBlockHash: common.Hash{0xdd},
|
||||
},
|
||||
}
|
||||
encDec(t, bare, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
||||
encDecOnFork(t, bare, forkAmsterdam, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
||||
}
|
||||
|
|
|
|||
120
beacon/engine/ssz/validate_test.go
Normal file
120
beacon/engine/ssz/validate_test.go
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ssz
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
func u64p(v uint64) *uint64 { return &v }
|
||||
|
||||
func TestExecutionPayloadValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fork ssz.Fork
|
||||
mutate func(*ExecutionPayload)
|
||||
wantErr bool
|
||||
}{
|
||||
{"paris ok", ssz.ForkParis, func(p *ExecutionPayload) {}, false},
|
||||
{"paris with withdrawals", ssz.ForkParis, func(p *ExecutionPayload) {
|
||||
p.Withdrawals = []*Withdrawal{{}}
|
||||
}, true},
|
||||
{"paris with blob gas", ssz.ForkParis, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed = u64p(1)
|
||||
}, true},
|
||||
{"cancun missing blob gas", ssz.ForkDencun, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed, p.ExcessBlobGas = nil, nil
|
||||
}, true},
|
||||
{"cancun ok", ssz.ForkDencun, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed, p.ExcessBlobGas = u64p(0), u64p(0)
|
||||
}, false},
|
||||
{"cancun with bal", ssz.ForkDencun, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed, p.ExcessBlobGas = u64p(0), u64p(0)
|
||||
p.BlockAccessList = []byte{0x01}
|
||||
}, true},
|
||||
{"amsterdam missing slot", forkAmsterdam, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed, p.ExcessBlobGas = u64p(0), u64p(0)
|
||||
p.SlotNumber = nil
|
||||
}, true},
|
||||
{"amsterdam ok", forkAmsterdam, func(p *ExecutionPayload) {
|
||||
p.BlobGasUsed, p.ExcessBlobGas = u64p(0), u64p(0)
|
||||
p.SlotNumber = u64p(1)
|
||||
p.BlockAccessList = []byte{0x01}
|
||||
}, false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := &ExecutionPayload{}
|
||||
tc.mutate(p)
|
||||
err := p.Validate(tc.fork)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("Validate(%d) err=%v wantErr=%v", tc.fork, err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPayloadAttributesValidate(t *testing.T) {
|
||||
root := common.Hash{0x77}
|
||||
tests := []struct {
|
||||
name string
|
||||
fork ssz.Fork
|
||||
mutate func(*PayloadAttributes)
|
||||
wantErr bool
|
||||
}{
|
||||
{"paris ok", ssz.ForkParis, func(a *PayloadAttributes) {}, false},
|
||||
{"cancun missing beacon root", ssz.ForkDencun, func(a *PayloadAttributes) {}, true},
|
||||
{"cancun ok", ssz.ForkDencun, func(a *PayloadAttributes) {
|
||||
a.ParentBeaconBlockRoot = &root
|
||||
}, false},
|
||||
{"paris with beacon root", ssz.ForkParis, func(a *PayloadAttributes) {
|
||||
a.ParentBeaconBlockRoot = &root
|
||||
}, true},
|
||||
{"amsterdam missing slot/tgl", forkAmsterdam, func(a *PayloadAttributes) {
|
||||
a.ParentBeaconBlockRoot = &root
|
||||
}, true},
|
||||
{"amsterdam ok", forkAmsterdam, func(a *PayloadAttributes) {
|
||||
a.ParentBeaconBlockRoot = &root
|
||||
a.SlotNumber, a.TargetGasLimit = u64p(1), u64p(2)
|
||||
}, false},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
a := &PayloadAttributes{}
|
||||
tc.mutate(a)
|
||||
err := a.Validate(tc.fork)
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Fatalf("Validate(%d) err=%v wantErr=%v", tc.fork, err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutionPayloadBodyValidate(t *testing.T) {
|
||||
if err := (&ExecutionPayloadBody{Withdrawals: []*Withdrawal{{}}}).Validate(ssz.ForkParis); err == nil {
|
||||
t.Error("expected error for withdrawals at Paris")
|
||||
}
|
||||
if err := (&ExecutionPayloadBody{BlockAccessList: []byte{1}}).Validate(ssz.ForkDencun); err == nil {
|
||||
t.Error("expected error for bal at Cancun")
|
||||
}
|
||||
if err := (&ExecutionPayloadBody{Withdrawals: []*Withdrawal{{}}}).Validate(forkAmsterdam); err != nil {
|
||||
t.Errorf("unexpected error at Amsterdam: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// handleBlobs dispatches POST /engine/v2/blobs/v{1,2,3,4}.
|
||||
|
|
@ -46,7 +47,7 @@ func (rt *Router) handleBlobs(w http.ResponseWriter, r *http.Request, suffix str
|
|||
|
||||
func (rt *Router) handleBlobsV1(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(sszt.BlobsVersionedHashesRequest)
|
||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
if len(req.VersionedHashes) > sszt.MaxBlobsRequest {
|
||||
|
|
@ -69,14 +70,14 @@ func (rt *Router) handleBlobsV1(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
resp.Entries[i] = e
|
||||
}
|
||||
writeSSZResponse(w, resp)
|
||||
writeSSZResponse(w, resp, ssz.ForkUnknown)
|
||||
}
|
||||
|
||||
// handleBlobsV2 serves both /v2 (all-or-nothing) and /v3 (partial). The
|
||||
// allowPartial flag selects between them.
|
||||
func (rt *Router) handleBlobsV2(w http.ResponseWriter, r *http.Request, allowPartial bool) {
|
||||
req := new(sszt.BlobsVersionedHashesRequest)
|
||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
if len(req.VersionedHashes) > sszt.MaxBlobsRequest {
|
||||
|
|
@ -106,7 +107,7 @@ func (rt *Router) handleBlobsV2(w http.ResponseWriter, r *http.Request, allowPar
|
|||
}
|
||||
resp.Entries[i] = e
|
||||
}
|
||||
writeSSZResponse(w, resp)
|
||||
writeSSZResponse(w, resp, ssz.ForkUnknown)
|
||||
}
|
||||
|
||||
// handleBlobsV4 implements POST /engine/v2/blobs/v4 (cell-range selection).
|
||||
|
|
@ -115,7 +116,7 @@ func (rt *Router) handleBlobsV2(w http.ResponseWriter, r *http.Request, allowPar
|
|||
// request body and the indices_bitarray length to keep the wire contract live.
|
||||
func (rt *Router) handleBlobsV4(w http.ResponseWriter, r *http.Request) {
|
||||
req := new(sszt.BlobsV4Request)
|
||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
if req.IndicesBitarray == nil || len(req.IndicesBitarray.Bytes) != sszt.CellsPerExtBlob/8 {
|
||||
|
|
|
|||
|
|
@ -23,16 +23,17 @@ import (
|
|||
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// handleBodiesByHash implements POST /engine/v2/{fork}/bodies/hash.
|
||||
func (rt *Router) handleBodiesByHash(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||
if fork != forks.Amsterdam {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
sf, ok := resolveFork(w, fork, forks.Paris)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
req := new(sszt.BodiesByHashRequest)
|
||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, req, sf, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
if len(req.BlockHashes) > sszt.MaxBodiesRequest {
|
||||
|
|
@ -40,13 +41,13 @@ func (rt *Router) handleBodiesByHash(w http.ResponseWriter, r *http.Request, for
|
|||
return
|
||||
}
|
||||
bodies, timestamps := rt.backend.BodiesByHash(req.BlockHashes)
|
||||
writeSSZResponse(w, buildBodiesResponseAmsterdam(rt.backend, fork, bodies, timestamps))
|
||||
writeSSZResponse(w, buildBodiesResponse(rt.backend, fork, sf, bodies, timestamps), sf)
|
||||
}
|
||||
|
||||
// handleBodiesByRange implements GET /engine/v2/{fork}/bodies?from=&count=.
|
||||
func (rt *Router) handleBodiesByRange(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||
if fork != forks.Amsterdam {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
sf, ok := resolveFork(w, fork, forks.Paris)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
q := r.URL.Query()
|
||||
|
|
@ -65,43 +66,49 @@ func (rt *Router) handleBodiesByRange(w http.ResponseWriter, r *http.Request, fo
|
|||
return
|
||||
}
|
||||
bodies, timestamps := rt.backend.BodiesByRange(from, count)
|
||||
writeSSZResponse(w, buildBodiesResponseAmsterdam(rt.backend, fork, bodies, timestamps))
|
||||
writeSSZResponse(w, buildBodiesResponse(rt.backend, fork, sf, bodies, timestamps), sf)
|
||||
}
|
||||
|
||||
// buildBodiesResponseAmsterdam assembles a BodiesResponseAmsterdam, marking
|
||||
// out-of-era blocks as available=false per the URL fork window.
|
||||
func buildBodiesResponseAmsterdam(b Backend, fork forks.Fork, bodies []*types.Body, ts []uint64) *sszt.BodiesResponseAmsterdam {
|
||||
out := &sszt.BodiesResponseAmsterdam{
|
||||
Entries: make([]*sszt.BodyEntryAmsterdam, len(bodies)),
|
||||
// buildBodiesResponse assembles a BodiesResponse for the given fork, marking
|
||||
// out-of-era blocks as available=false per the URL fork window. The body shape
|
||||
// is fork-driven by the codec; bodyToSSZ populates the superset and the codec
|
||||
// emits only the fork's active fields.
|
||||
func buildBodiesResponse(b Backend, fork forks.Fork, sf ssz.Fork, bodies []*types.Body, ts []uint64) *sszt.BodiesResponse {
|
||||
out := &sszt.BodiesResponse{
|
||||
Entries: make([]*sszt.BodyEntry, len(bodies)),
|
||||
}
|
||||
for i, body := range bodies {
|
||||
entry := &sszt.BodyEntryAmsterdam{Body: new(sszt.ExecutionPayloadBodyAmsterdam)}
|
||||
entry := &sszt.BodyEntry{Body: new(sszt.ExecutionPayloadBody)}
|
||||
if body != nil && b.ForkFromTimestamp(ts[i]) == fork {
|
||||
entry.Available = true
|
||||
entry.Body = bodyToAmsterdamSSZ(body)
|
||||
entry.Body = bodyToSSZ(body, sf)
|
||||
}
|
||||
out.Entries[i] = entry
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// bodyToAmsterdamSSZ flattens a *types.Body into the Amsterdam body shape.
|
||||
func bodyToAmsterdamSSZ(body *types.Body) *sszt.ExecutionPayloadBodyAmsterdam {
|
||||
out := &sszt.ExecutionPayloadBodyAmsterdam{
|
||||
// bodyToSSZ flattens a *types.Body into the monolithic body shape. Withdrawals
|
||||
// are only attached from Shanghai on, matching the fork's wire shape.
|
||||
func bodyToSSZ(body *types.Body, sf ssz.Fork) *sszt.ExecutionPayloadBody {
|
||||
out := &sszt.ExecutionPayloadBody{
|
||||
Transactions: make([][]byte, len(body.Transactions)),
|
||||
Withdrawals: make([]*sszt.Withdrawal, len(body.Withdrawals)),
|
||||
}
|
||||
for i, tx := range body.Transactions {
|
||||
out.Transactions[i], _ = tx.MarshalBinary()
|
||||
}
|
||||
for i, w := range body.Withdrawals {
|
||||
out.Withdrawals[i] = &sszt.Withdrawal{
|
||||
Index: w.Index,
|
||||
ValidatorIndex: w.Validator,
|
||||
Address: w.Address,
|
||||
Amount: w.Amount,
|
||||
if sf >= ssz.ForkShapella {
|
||||
out.Withdrawals = make([]*sszt.Withdrawal, len(body.Withdrawals))
|
||||
for i, w := range body.Withdrawals {
|
||||
out.Withdrawals[i] = &sszt.Withdrawal{
|
||||
Index: w.Index,
|
||||
ValidatorIndex: w.Validator,
|
||||
Address: w.Address,
|
||||
Amount: w.Amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
// BlockAccessList is not yet on types.Body; once wired through, copy here.
|
||||
// BlockAccessList is not yet on types.Body; once wired through, copy here
|
||||
// (gated by sf >= forkAmsterdam).
|
||||
return out
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,14 @@ import (
|
|||
|
||||
// handleForkchoice implements POST /engine/v2/{fork}/forkchoice.
|
||||
func (rt *Router) handleForkchoice(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||
if fork != forks.Amsterdam {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
// The execution-apis spec currently only defines the forkchoice envelope
|
||||
// for Amsterdam; the inner attributes shape is fork-driven by the codec.
|
||||
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fcu := new(sszt.ForkchoiceUpdateAmsterdam)
|
||||
if !readSSZRequest(w, r, fcu, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, fcu, sf, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
if err := fcu.Validate(); err != nil {
|
||||
|
|
@ -41,17 +43,21 @@ func (rt *Router) handleForkchoice(w http.ResponseWriter, r *http.Request, fork
|
|||
state := sszt.ForkchoiceStateToV1(fcu.ForkchoiceState)
|
||||
var attrs *engine.PayloadAttributes
|
||||
if len(fcu.PayloadAttributes) == 1 {
|
||||
ssz := fcu.PayloadAttributes[0]
|
||||
attr := fcu.PayloadAttributes[0]
|
||||
if err := attr.Validate(sf); err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, ErrInvalidAttributes, err.Error())
|
||||
return
|
||||
}
|
||||
// If PayloadAttributes is present the URL fork MUST match the fork
|
||||
// the new payload would belong to. Today only Amsterdam URL exists
|
||||
// in this implementation so the timestamp check is implicit; we
|
||||
// keep an explicit guard for future fork URLs.
|
||||
if rt.backend.ForkFromTimestamp(ssz.Timestamp) != fork {
|
||||
if rt.backend.ForkFromTimestamp(attr.Timestamp) != fork {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork,
|
||||
"payload_attributes timestamp does not match URL fork")
|
||||
return
|
||||
}
|
||||
attrs = sszt.PayloadAttributesAmsterdamToEngine(ssz)
|
||||
attrs = sszt.PayloadAttributesToEngine(attr)
|
||||
// target_gas_limit and custody_columns are not yet plumbed into
|
||||
// the JSON-RPC engine API. Custody is parsed-but-stubbed per
|
||||
// agreed scope; target_gas_limit will be picked up when the
|
||||
|
|
@ -73,5 +79,5 @@ func (rt *Router) handleForkchoice(w http.ResponseWriter, r *http.Request, fork
|
|||
if resp.PayloadID != nil {
|
||||
out.PayloadID = [][8]byte{[8]byte(*resp.PayloadID)}
|
||||
}
|
||||
writeSSZResponse(w, out)
|
||||
writeSSZResponse(w, out, sf)
|
||||
}
|
||||
|
|
|
|||
41
eth/engineapi/forkresolve.go
Normal file
41
eth/engineapi/forkresolve.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package engineapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// resolveFork maps the URL fork onto the ssz.Fork the codec multiplexes on,
|
||||
// enforcing that it is at least min. On failure it writes the appropriate
|
||||
// problem response and returns ok=false; the caller should return immediately.
|
||||
func resolveFork(w http.ResponseWriter, fork, min forks.Fork) (ssz.Fork, bool) {
|
||||
if fork < min {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
return ssz.ForkUnknown, false
|
||||
}
|
||||
sf, ok := sszt.ForkFor(fork)
|
||||
if !ok {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
return ssz.ForkUnknown, false
|
||||
}
|
||||
return sf, true
|
||||
}
|
||||
|
|
@ -24,12 +24,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/params/forks"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/karalabe/ssz"
|
||||
)
|
||||
|
||||
// handleGetPayload implements GET /engine/v2/{fork}/payloads/{payloadId}.
|
||||
func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork, idHex string) {
|
||||
if fork != forks.Amsterdam {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
raw, err := hexutil.Decode(idHex)
|
||||
|
|
@ -46,17 +47,17 @@ func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork
|
|||
return
|
||||
}
|
||||
|
||||
out := buildBuiltPayloadAmsterdam(env)
|
||||
out := buildBuiltPayloadAmsterdam(env, sf)
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
writeSSZResponse(w, out)
|
||||
writeSSZResponse(w, out, sf)
|
||||
}
|
||||
|
||||
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
|
||||
// the SSZ BuiltPayload shape. BlockValue/BlobsBundle/Requests come straight
|
||||
// across; the inner payload goes through the SSZ converter.
|
||||
func buildBuiltPayloadAmsterdam(env *engine.ExecutionPayloadEnvelope) *sszt.BuiltPayloadAmsterdam {
|
||||
func buildBuiltPayloadAmsterdam(env *engine.ExecutionPayloadEnvelope, sf ssz.Fork) *sszt.BuiltPayloadAmsterdam {
|
||||
out := &sszt.BuiltPayloadAmsterdam{
|
||||
Payload: sszt.ExecutionPayloadAmsterdamFromEngine(env.ExecutionPayload),
|
||||
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
|
||||
BlockValue: new(uint256.Int),
|
||||
ExecutionRequests: env.Requests,
|
||||
ShouldOverrideBuilder: env.Override,
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ const (
|
|||
)
|
||||
|
||||
// readSSZRequest enforces the SSZ content-type and Content-Length cap, then
|
||||
// decodes the request body into obj. Writes the appropriate problem response
|
||||
// on failure and returns false; the caller should return immediately.
|
||||
func readSSZRequest(w http.ResponseWriter, r *http.Request, obj ssz.Object, max int64) bool {
|
||||
// decodes the request body into obj for the given fork. Writes the appropriate
|
||||
// problem response on failure and returns false; the caller should return
|
||||
// immediately.
|
||||
func readSSZRequest(w http.ResponseWriter, r *http.Request, obj ssz.Object, fork ssz.Fork, max int64) bool {
|
||||
if !strings.EqualFold(r.Header.Get("Content-Type"), sszContentType) {
|
||||
writeProblem(w, http.StatusUnsupportedMediaType, ErrUnsupportedMedia, "expected "+sszContentType)
|
||||
return false
|
||||
|
|
@ -54,18 +55,18 @@ func readSSZRequest(w http.ResponseWriter, r *http.Request, obj ssz.Object, max
|
|||
writeProblem(w, http.StatusRequestEntityTooLarge, ErrRequestTooLarge, "")
|
||||
return false
|
||||
}
|
||||
if err := ssz.DecodeFromBytes(body, obj); err != nil {
|
||||
if err := ssz.DecodeFromBytesOnFork(body, obj, fork); err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, ErrSSZDecode, "")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// writeSSZResponse encodes obj into the response body. On encode failure it
|
||||
// writes an internal-error problem and returns false.
|
||||
func writeSSZResponse(w http.ResponseWriter, obj ssz.Object) bool {
|
||||
buf := make([]byte, ssz.Size(obj))
|
||||
if err := ssz.EncodeToBytes(buf, obj); err != nil {
|
||||
// writeSSZResponse encodes obj into the response body for the given fork. On
|
||||
// encode failure it writes an internal-error problem and returns false.
|
||||
func writeSSZResponse(w http.ResponseWriter, obj ssz.Object, fork ssz.Fork) bool {
|
||||
buf := make([]byte, ssz.SizeOnFork(obj, fork))
|
||||
if err := ssz.EncodeToBytesOnFork(buf, obj, fork); err != nil {
|
||||
writeProblem(w, http.StatusInternalServerError, ErrInternal, err.Error())
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,15 +27,21 @@ import (
|
|||
|
||||
// handleNewPayload implements POST /engine/v2/{fork}/payloads.
|
||||
func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||
if fork != forks.Amsterdam {
|
||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
||||
// The execution-apis spec currently only defines the payload envelope for
|
||||
// Amsterdam; the inner payload shape is fork-driven by the codec.
|
||||
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
env := new(sszt.ExecutionPayloadEnvelopeAmsterdam)
|
||||
if !readSSZRequest(w, r, env, maxPayloadBytes) {
|
||||
if !readSSZRequest(w, r, env, sf, maxPayloadBytes) {
|
||||
return
|
||||
}
|
||||
data := sszt.ExecutionPayloadAmsterdamToEngine(env.Payload)
|
||||
if err := env.Payload.Validate(sf); err != nil {
|
||||
writeProblem(w, http.StatusUnprocessableEntity, ErrInvalidBody, err.Error())
|
||||
return
|
||||
}
|
||||
data := sszt.ExecutionPayloadToEngine(env.Payload)
|
||||
|
||||
// The spec drops expectedBlobVersionedHashes; we recompute them from the
|
||||
// payload's transactions before passing to the EL.
|
||||
|
|
@ -50,7 +56,7 @@ func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork
|
|||
mapBackendErr(w, err)
|
||||
return
|
||||
}
|
||||
writeSSZResponse(w, sszt.PayloadStatusFromV1(&status))
|
||||
writeSSZResponse(w, sszt.PayloadStatusFromV1(&status), sf)
|
||||
}
|
||||
|
||||
// versionedHashesFromTxs extracts the blob versioned-hash list from the
|
||||
|
|
|
|||
|
|
@ -103,10 +103,10 @@ func newTestServer(t *testing.T, b Backend) *httptest.Server {
|
|||
return httptest.NewServer(http.StripPrefix(BasePath, NewRouter(b)))
|
||||
}
|
||||
|
||||
func sszPost(t *testing.T, srv *httptest.Server, path string, body ssz.Object) *http.Response {
|
||||
func sszPost(t *testing.T, srv *httptest.Server, path string, body ssz.Object, fork ssz.Fork) *http.Response {
|
||||
t.Helper()
|
||||
buf := make([]byte, ssz.Size(body))
|
||||
if err := ssz.EncodeToBytes(buf, body); err != nil {
|
||||
buf := make([]byte, ssz.SizeOnFork(body, fork))
|
||||
if err := ssz.EncodeToBytesOnFork(buf, body, fork); err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodPost, srv.URL+BasePath+path, bytes.NewReader(buf))
|
||||
|
|
@ -118,14 +118,14 @@ func sszPost(t *testing.T, srv *httptest.Server, path string, body ssz.Object) *
|
|||
return resp
|
||||
}
|
||||
|
||||
func decodeSSZ[T ssz.Object](t *testing.T, resp *http.Response, obj T) {
|
||||
func decodeSSZ[T ssz.Object](t *testing.T, resp *http.Response, obj T, fork ssz.Fork) {
|
||||
t.Helper()
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read body: %v", err)
|
||||
}
|
||||
if err := ssz.DecodeFromBytes(body, obj); err != nil {
|
||||
if err := ssz.DecodeFromBytesOnFork(body, obj, fork); err != nil {
|
||||
t.Fatalf("decode response: %v\nbody: %x", err, body)
|
||||
}
|
||||
}
|
||||
|
|
@ -139,19 +139,24 @@ func TestRouterNewPayload(t *testing.T) {
|
|||
srv := newTestServer(t, b)
|
||||
defer srv.Close()
|
||||
|
||||
amsterdam, _ := sszt.ForkFor(forks.Amsterdam)
|
||||
blob, excess, slot := uint64(0), uint64(0), uint64(0)
|
||||
env := &sszt.ExecutionPayloadEnvelopeAmsterdam{
|
||||
Payload: &sszt.ExecutionPayloadAmsterdam{
|
||||
Payload: &sszt.ExecutionPayload{
|
||||
BaseFeePerGas: uint256.NewInt(7e9),
|
||||
LogsBloom: [256]byte{},
|
||||
BlobGasUsed: &blob,
|
||||
ExcessBlobGas: &excess,
|
||||
SlotNumber: &slot,
|
||||
},
|
||||
ParentBeaconBlockRoot: common.Hash{0x55},
|
||||
}
|
||||
resp := sszPost(t, srv, "/amsterdam/payloads", env)
|
||||
resp := sszPost(t, srv, "/amsterdam/payloads", env, amsterdam)
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||
}
|
||||
got := new(sszt.PayloadStatus)
|
||||
decodeSSZ(t, resp, got)
|
||||
decodeSSZ(t, resp, got, amsterdam)
|
||||
if got.Status != sszt.StatusValid {
|
||||
t.Errorf("status=%d want VALID(%d)", got.Status, sszt.StatusValid)
|
||||
}
|
||||
|
|
@ -169,15 +174,16 @@ func TestRouterForkchoice(t *testing.T) {
|
|||
srv := newTestServer(t, b)
|
||||
defer srv.Close()
|
||||
|
||||
amsterdam, _ := sszt.ForkFor(forks.Amsterdam)
|
||||
fcu := &sszt.ForkchoiceUpdateAmsterdam{
|
||||
ForkchoiceState: &sszt.ForkchoiceState{HeadBlockHash: common.Hash{0xaa}},
|
||||
}
|
||||
resp := sszPost(t, srv, "/amsterdam/forkchoice", fcu)
|
||||
resp := sszPost(t, srv, "/amsterdam/forkchoice", fcu, amsterdam)
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||
}
|
||||
got := new(sszt.ForkchoiceUpdateResponseAmsterdam)
|
||||
decodeSSZ(t, resp, got)
|
||||
decodeSSZ(t, resp, got, amsterdam)
|
||||
if got.PayloadStatus == nil || got.PayloadStatus.Status != sszt.StatusValid {
|
||||
t.Errorf("status: %#v", got.PayloadStatus)
|
||||
}
|
||||
|
|
@ -310,7 +316,7 @@ func TestRouterBlobsV2AllOrNothing(t *testing.T) {
|
|||
srv := newTestServer(t, b)
|
||||
defer srv.Close()
|
||||
req := &sszt.BlobsVersionedHashesRequest{VersionedHashes: []common.Hash{{0x01}}}
|
||||
resp := sszPost(t, srv, "/blobs/v2", req)
|
||||
resp := sszPost(t, srv, "/blobs/v2", req, ssz.ForkUnknown)
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 204 {
|
||||
t.Fatalf("want 204, got %d", resp.StatusCode)
|
||||
|
|
@ -322,12 +328,12 @@ func TestRouterBlobsV3PartialOK(t *testing.T) {
|
|||
srv := newTestServer(t, b)
|
||||
defer srv.Close()
|
||||
req := &sszt.BlobsVersionedHashesRequest{VersionedHashes: []common.Hash{{0x01}, {0x02}}}
|
||||
resp := sszPost(t, srv, "/blobs/v3", req)
|
||||
resp := sszPost(t, srv, "/blobs/v3", req, ssz.ForkUnknown)
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||
}
|
||||
got := new(sszt.BlobsV2Response)
|
||||
decodeSSZ(t, resp, got)
|
||||
decodeSSZ(t, resp, got, ssz.ForkUnknown)
|
||||
if len(got.Entries) != 2 {
|
||||
t.Fatalf("entries=%d", len(got.Entries))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue