mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-24 05:36:46 +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
|
package ssz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/karalabe/ssz"
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
@ -40,13 +42,70 @@ func (r *BodiesByHashRequest) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineSliceOfStaticBytesContent(c, &r.BlockHashes, MaxBodiesRequest)
|
ssz.DefineSliceOfStaticBytesContent(c, &r.BlockHashes, MaxBodiesRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodyEntryAmsterdam is the per-block entry returned by /amsterdam/bodies/...
|
// ExecutionPayloadBody is the monolithic body shape returned by /{fork}/bodies.
|
||||||
type BodyEntryAmsterdam struct {
|
//
|
||||||
Available bool
|
// Field groups:
|
||||||
Body *ExecutionPayloadBodyAmsterdam
|
// - 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
|
size := uint32(1 + 4) // bool + offset
|
||||||
if fixed {
|
if fixed {
|
||||||
return size
|
return size
|
||||||
|
|
@ -55,19 +114,19 @@ func (e *BodyEntryAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *BodyEntryAmsterdam) DefineSSZ(c *ssz.Codec) {
|
func (e *BodyEntry) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineBool(c, &e.Available)
|
ssz.DefineBool(c, &e.Available)
|
||||||
ssz.DefineDynamicObjectOffset(c, &e.Body)
|
ssz.DefineDynamicObjectOffset(c, &e.Body)
|
||||||
|
|
||||||
ssz.DefineDynamicObjectContent(c, &e.Body)
|
ssz.DefineDynamicObjectContent(c, &e.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BodiesResponseAmsterdam is the SSZ response of /amsterdam/bodies/...
|
// BodiesResponse is the SSZ response of /{fork}/bodies/...
|
||||||
type BodiesResponseAmsterdam struct {
|
type BodiesResponse struct {
|
||||||
Entries []*BodyEntryAmsterdam
|
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)
|
size := uint32(4)
|
||||||
if fixed {
|
if fixed {
|
||||||
return size
|
return size
|
||||||
|
|
@ -76,87 +135,7 @@ func (r *BodiesResponseAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BodiesResponseAmsterdam) DefineSSZ(c *ssz.Codec) {
|
func (r *BodiesResponse) 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) {
|
|
||||||
ssz.DefineSliceOfDynamicObjectsOffset(c, &r.Entries, MaxBodiesRequest)
|
ssz.DefineSliceOfDynamicObjectsOffset(c, &r.Entries, MaxBodiesRequest)
|
||||||
ssz.DefineSliceOfDynamicObjectsContent(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/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statusToEnum maps the JSON-RPC PayloadStatusV1.Status string to the SSZ
|
// statusToEnum maps the JSON-RPC PayloadStatusV1.Status string to the SSZ
|
||||||
|
|
@ -127,46 +128,68 @@ func withdrawalsToTypes(ws []*Withdrawal) []*types.Withdrawal {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadAttributesAmsterdamFromEngine converts a JSON-RPC engine.PayloadAttributes
|
// PayloadAttributesFromEngine converts a JSON-RPC engine.PayloadAttributes into
|
||||||
// into its Amsterdam SSZ form. The caller is responsible for ensuring the
|
// the monolithic SSZ form for the given fork. Only the fields the fork's wire
|
||||||
// attributes carry every Amsterdam-required field.
|
// shape carries are populated; the caller should pass the same fork to the
|
||||||
func PayloadAttributesAmsterdamFromEngine(a *engine.PayloadAttributes) *PayloadAttributesAmsterdam {
|
// codec. target_gas_limit has no representation in engine.PayloadAttributes
|
||||||
out := &PayloadAttributesAmsterdam{
|
// 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,
|
Timestamp: a.Timestamp,
|
||||||
PrevRandao: a.Random,
|
PrevRandao: a.Random,
|
||||||
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
||||||
Withdrawals: withdrawalsFromTypes(a.Withdrawals),
|
|
||||||
}
|
}
|
||||||
if a.BeaconRoot != nil {
|
if fork >= ssz.ForkShapella {
|
||||||
out.ParentBeaconBlockRoot = *a.BeaconRoot
|
out.Withdrawals = withdrawalsFromTypes(a.Withdrawals)
|
||||||
}
|
}
|
||||||
if a.SlotNumber != nil {
|
if fork >= ssz.ForkDencun {
|
||||||
out.SlotNumber = *a.SlotNumber
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadAttributesAmsterdamToEngine is the inverse of the From helper. The
|
// PayloadAttributesToEngine is the inverse of PayloadAttributesFromEngine. The
|
||||||
// target_gas_limit field has no representation in engine.PayloadAttributes
|
// target_gas_limit field has no representation in engine.PayloadAttributes and
|
||||||
// today and is dropped on the way down; the caller can fish it out of the SSZ
|
// is dropped; the caller can read it from the SSZ struct directly.
|
||||||
// struct directly.
|
func PayloadAttributesToEngine(a *PayloadAttributes) *engine.PayloadAttributes {
|
||||||
func PayloadAttributesAmsterdamToEngine(a *PayloadAttributesAmsterdam) *engine.PayloadAttributes {
|
out := &engine.PayloadAttributes{
|
||||||
root := a.ParentBeaconBlockRoot
|
|
||||||
slot := a.SlotNumber
|
|
||||||
return &engine.PayloadAttributes{
|
|
||||||
Timestamp: a.Timestamp,
|
Timestamp: a.Timestamp,
|
||||||
Random: a.PrevRandao,
|
Random: a.PrevRandao,
|
||||||
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
SuggestedFeeRecipient: a.SuggestedFeeRecipient,
|
||||||
Withdrawals: withdrawalsToTypes(a.Withdrawals),
|
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
|
// ExecutionPayloadFromEngine converts an ExecutableData into the monolithic SSZ
|
||||||
// Amsterdam payload shape. The block_access_list and slot_number fields are
|
// payload for the given fork. Only the fork's active fields are populated so
|
||||||
// expected to be present; missing values yield empty/zero defaults.
|
// the codec (driven by the same fork) and Validate(fork) stay consistent.
|
||||||
func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPayloadAmsterdam {
|
// 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
|
var bloom [256]byte
|
||||||
copy(bloom[:], d.LogsBloom)
|
copy(bloom[:], d.LogsBloom)
|
||||||
var fee *uint256.Int
|
var fee *uint256.Int
|
||||||
|
|
@ -174,7 +197,7 @@ func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPay
|
||||||
fee = new(uint256.Int)
|
fee = new(uint256.Int)
|
||||||
fee.SetFromBig(d.BaseFeePerGas)
|
fee.SetFromBig(d.BaseFeePerGas)
|
||||||
}
|
}
|
||||||
out := &ExecutionPayloadAmsterdam{
|
out := &ExecutionPayload{
|
||||||
ParentHash: d.ParentHash,
|
ParentHash: d.ParentHash,
|
||||||
FeeRecipient: d.FeeRecipient,
|
FeeRecipient: d.FeeRecipient,
|
||||||
StateRoot: d.StateRoot,
|
StateRoot: d.StateRoot,
|
||||||
|
|
@ -189,48 +212,66 @@ func ExecutionPayloadAmsterdamFromEngine(d *engine.ExecutableData) *ExecutionPay
|
||||||
BaseFeePerGas: fee,
|
BaseFeePerGas: fee,
|
||||||
BlockHash: d.BlockHash,
|
BlockHash: d.BlockHash,
|
||||||
Transactions: d.Transactions,
|
Transactions: d.Transactions,
|
||||||
Withdrawals: withdrawalsFromTypes(d.Withdrawals),
|
|
||||||
}
|
}
|
||||||
if d.BlobGasUsed != nil {
|
if fork >= ssz.ForkShapella {
|
||||||
out.BlobGasUsed = *d.BlobGasUsed
|
out.Withdrawals = withdrawalsFromTypes(d.Withdrawals)
|
||||||
}
|
}
|
||||||
if d.ExcessBlobGas != nil {
|
if fork >= ssz.ForkDencun {
|
||||||
out.ExcessBlobGas = *d.ExcessBlobGas
|
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 {
|
if fork >= forkAmsterdam {
|
||||||
out.SlotNumber = *d.SlotNumber
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionPayloadAmsterdamToEngine is the inverse helper. The caller is
|
// ExecutionPayloadToEngine is the inverse helper. The caller is responsible for
|
||||||
// responsible for the BAL payload that ExecutableData doesn't yet carry.
|
// the BAL payload that ExecutableData doesn't yet carry.
|
||||||
func ExecutionPayloadAmsterdamToEngine(p *ExecutionPayloadAmsterdam) *engine.ExecutableData {
|
func ExecutionPayloadToEngine(p *ExecutionPayload) *engine.ExecutableData {
|
||||||
bloom := append([]byte(nil), p.LogsBloom[:]...)
|
bloom := append([]byte(nil), p.LogsBloom[:]...)
|
||||||
out := &engine.ExecutableData{
|
out := &engine.ExecutableData{
|
||||||
ParentHash: p.ParentHash,
|
ParentHash: p.ParentHash,
|
||||||
FeeRecipient: p.FeeRecipient,
|
FeeRecipient: p.FeeRecipient,
|
||||||
StateRoot: p.StateRoot,
|
StateRoot: p.StateRoot,
|
||||||
ReceiptsRoot: p.ReceiptsRoot,
|
ReceiptsRoot: p.ReceiptsRoot,
|
||||||
LogsBloom: bloom,
|
LogsBloom: bloom,
|
||||||
Random: p.PrevRandao,
|
Random: p.PrevRandao,
|
||||||
Number: p.BlockNumber,
|
Number: p.BlockNumber,
|
||||||
GasLimit: p.GasLimit,
|
GasLimit: p.GasLimit,
|
||||||
GasUsed: p.GasUsed,
|
GasUsed: p.GasUsed,
|
||||||
Timestamp: p.Timestamp,
|
Timestamp: p.Timestamp,
|
||||||
ExtraData: append([]byte(nil), p.ExtraData...),
|
ExtraData: append([]byte(nil), p.ExtraData...),
|
||||||
BlockHash: p.BlockHash,
|
BlockHash: p.BlockHash,
|
||||||
Transactions: p.Transactions,
|
Transactions: p.Transactions,
|
||||||
Withdrawals: withdrawalsToTypes(p.Withdrawals),
|
Withdrawals: withdrawalsToTypes(p.Withdrawals),
|
||||||
BlobGasUsed: &p.BlobGasUsed,
|
|
||||||
ExcessBlobGas: &p.ExcessBlobGas,
|
|
||||||
SlotNumber: &p.SlotNumber,
|
|
||||||
}
|
}
|
||||||
if p.BaseFeePerGas != nil {
|
if p.BaseFeePerGas != nil {
|
||||||
out.BaseFeePerGas = p.BaseFeePerGas.ToBig()
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
// ExecutionPayloadEnvelopeAmsterdam is the request body of
|
// ExecutionPayloadEnvelopeAmsterdam is the request body of
|
||||||
// POST /amsterdam/payloads.
|
// POST /amsterdam/payloads.
|
||||||
type ExecutionPayloadEnvelopeAmsterdam struct {
|
type ExecutionPayloadEnvelopeAmsterdam struct {
|
||||||
Payload *ExecutionPayloadAmsterdam
|
Payload *ExecutionPayload
|
||||||
ParentBeaconBlockRoot common.Hash
|
ParentBeaconBlockRoot common.Hash
|
||||||
ExecutionRequests [][]byte
|
ExecutionRequests [][]byte
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ func (b *BlobsBundleV2) DefineSSZ(c *ssz.Codec) {
|
||||||
|
|
||||||
// BuiltPayloadAmsterdam is the response of GET /amsterdam/payloads/{id}.
|
// BuiltPayloadAmsterdam is the response of GET /amsterdam/payloads/{id}.
|
||||||
type BuiltPayloadAmsterdam struct {
|
type BuiltPayloadAmsterdam struct {
|
||||||
Payload *ExecutionPayloadAmsterdam
|
Payload *ExecutionPayload
|
||||||
BlockValue *uint256.Int
|
BlockValue *uint256.Int
|
||||||
BlobsBundle *BlobsBundleV2
|
BlobsBundle *BlobsBundleV2
|
||||||
ExecutionRequests [][]byte
|
ExecutionRequests [][]byte
|
||||||
|
|
@ -120,7 +120,7 @@ func (p *BuiltPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||||
// length 0 or 1 of the 16-byte Bitvector wrapper.
|
// length 0 or 1 of the 16-byte Bitvector wrapper.
|
||||||
type ForkchoiceUpdateAmsterdam struct {
|
type ForkchoiceUpdateAmsterdam struct {
|
||||||
ForkchoiceState *ForkchoiceState
|
ForkchoiceState *ForkchoiceState
|
||||||
PayloadAttributes []*PayloadAttributesAmsterdam
|
PayloadAttributes []*PayloadAttributes
|
||||||
CustodyColumns []*Bitvector128
|
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) })
|
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) {
|
func TestExecutionPayloadAmsterdamRoundtrip(t *testing.T) {
|
||||||
p := &ExecutionPayloadAmsterdam{
|
blob, excess, slot := uint64(131072), uint64(0), uint64(200)
|
||||||
|
p := &ExecutionPayload{
|
||||||
ParentHash: common.Hash{0x11},
|
ParentHash: common.Hash{0x11},
|
||||||
FeeRecipient: common.Address{0x22},
|
FeeRecipient: common.Address{0x22},
|
||||||
StateRoot: common.Hash{0x33},
|
StateRoot: common.Hash{0x33},
|
||||||
|
|
@ -93,26 +115,28 @@ func TestExecutionPayloadAmsterdamRoundtrip(t *testing.T) {
|
||||||
BlockHash: common.Hash{0x66},
|
BlockHash: common.Hash{0x66},
|
||||||
Transactions: [][]byte{{0x02, 0x03}, {0x04}},
|
Transactions: [][]byte{{0x02, 0x03}, {0x04}},
|
||||||
Withdrawals: []*Withdrawal{{Index: 1, ValidatorIndex: 2, Amount: 3}},
|
Withdrawals: []*Withdrawal{{Index: 1, ValidatorIndex: 2, Amount: 3}},
|
||||||
BlobGasUsed: 131072,
|
BlobGasUsed: &blob,
|
||||||
ExcessBlobGas: 0,
|
ExcessBlobGas: &excess,
|
||||||
BlockAccessList: []byte{0xde, 0xad, 0xbe, 0xef},
|
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) {
|
func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
||||||
// With payload_attributes and custody_columns present.
|
// With payload_attributes and custody_columns present.
|
||||||
bits := &Bitvector128{Bytes: make([]byte, CellsPerExtBlob/8)}
|
bits := &Bitvector128{Bytes: make([]byte, CellsPerExtBlob/8)}
|
||||||
bits.Bytes[0] = 0x01
|
bits.Bytes[0] = 0x01
|
||||||
attrs := &PayloadAttributesAmsterdam{
|
root := common.Hash{0x77}
|
||||||
|
slot, tgl := uint64(42), uint64(30000000)
|
||||||
|
attrs := &PayloadAttributes{
|
||||||
Timestamp: 1700000000,
|
Timestamp: 1700000000,
|
||||||
PrevRandao: common.Hash{0x55},
|
PrevRandao: common.Hash{0x55},
|
||||||
SuggestedFeeRecipient: common.Address{0x66},
|
SuggestedFeeRecipient: common.Address{0x66},
|
||||||
Withdrawals: []*Withdrawal{},
|
Withdrawals: []*Withdrawal{},
|
||||||
ParentBeaconBlockRoot: common.Hash{0x77},
|
ParentBeaconBlockRoot: &root,
|
||||||
SlotNumber: 42,
|
SlotNumber: &slot,
|
||||||
TargetGasLimit: 30000000,
|
TargetGasLimit: &tgl,
|
||||||
}
|
}
|
||||||
fcu := &ForkchoiceUpdateAmsterdam{
|
fcu := &ForkchoiceUpdateAmsterdam{
|
||||||
ForkchoiceState: &ForkchoiceState{
|
ForkchoiceState: &ForkchoiceState{
|
||||||
|
|
@ -120,10 +144,10 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
||||||
SafeBlockHash: common.Hash{0xbb},
|
SafeBlockHash: common.Hash{0xbb},
|
||||||
FinalizedBlockHash: common.Hash{0xcc},
|
FinalizedBlockHash: common.Hash{0xcc},
|
||||||
},
|
},
|
||||||
PayloadAttributes: []*PayloadAttributesAmsterdam{attrs},
|
PayloadAttributes: []*PayloadAttributes{attrs},
|
||||||
CustodyColumns: []*Bitvector128{bits},
|
CustodyColumns: []*Bitvector128{bits},
|
||||||
}
|
}
|
||||||
encDec(t, fcu, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
encDecOnFork(t, fcu, forkAmsterdam, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
||||||
|
|
||||||
// Without optionals.
|
// Without optionals.
|
||||||
bare := &ForkchoiceUpdateAmsterdam{
|
bare := &ForkchoiceUpdateAmsterdam{
|
||||||
|
|
@ -131,5 +155,5 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
||||||
HeadBlockHash: common.Hash{0xdd},
|
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"
|
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||||
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
||||||
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleBlobs dispatches POST /engine/v2/blobs/v{1,2,3,4}.
|
// 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) {
|
func (rt *Router) handleBlobsV1(w http.ResponseWriter, r *http.Request) {
|
||||||
req := new(sszt.BlobsVersionedHashesRequest)
|
req := new(sszt.BlobsVersionedHashesRequest)
|
||||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(req.VersionedHashes) > sszt.MaxBlobsRequest {
|
if len(req.VersionedHashes) > sszt.MaxBlobsRequest {
|
||||||
|
|
@ -69,14 +70,14 @@ func (rt *Router) handleBlobsV1(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
resp.Entries[i] = e
|
resp.Entries[i] = e
|
||||||
}
|
}
|
||||||
writeSSZResponse(w, resp)
|
writeSSZResponse(w, resp, ssz.ForkUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleBlobsV2 serves both /v2 (all-or-nothing) and /v3 (partial). The
|
// handleBlobsV2 serves both /v2 (all-or-nothing) and /v3 (partial). The
|
||||||
// allowPartial flag selects between them.
|
// allowPartial flag selects between them.
|
||||||
func (rt *Router) handleBlobsV2(w http.ResponseWriter, r *http.Request, allowPartial bool) {
|
func (rt *Router) handleBlobsV2(w http.ResponseWriter, r *http.Request, allowPartial bool) {
|
||||||
req := new(sszt.BlobsVersionedHashesRequest)
|
req := new(sszt.BlobsVersionedHashesRequest)
|
||||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(req.VersionedHashes) > sszt.MaxBlobsRequest {
|
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
|
resp.Entries[i] = e
|
||||||
}
|
}
|
||||||
writeSSZResponse(w, resp)
|
writeSSZResponse(w, resp, ssz.ForkUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleBlobsV4 implements POST /engine/v2/blobs/v4 (cell-range selection).
|
// 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.
|
// request body and the indices_bitarray length to keep the wire contract live.
|
||||||
func (rt *Router) handleBlobsV4(w http.ResponseWriter, r *http.Request) {
|
func (rt *Router) handleBlobsV4(w http.ResponseWriter, r *http.Request) {
|
||||||
req := new(sszt.BlobsV4Request)
|
req := new(sszt.BlobsV4Request)
|
||||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
if !readSSZRequest(w, r, req, ssz.ForkUnknown, maxPayloadBytes) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.IndicesBitarray == nil || len(req.IndicesBitarray.Bytes) != sszt.CellsPerExtBlob/8 {
|
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"
|
sszt "github.com/ethereum/go-ethereum/beacon/engine/ssz"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleBodiesByHash implements POST /engine/v2/{fork}/bodies/hash.
|
// handleBodiesByHash implements POST /engine/v2/{fork}/bodies/hash.
|
||||||
func (rt *Router) handleBodiesByHash(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
func (rt *Router) handleBodiesByHash(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||||
if fork != forks.Amsterdam {
|
sf, ok := resolveFork(w, fork, forks.Paris)
|
||||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
req := new(sszt.BodiesByHashRequest)
|
req := new(sszt.BodiesByHashRequest)
|
||||||
if !readSSZRequest(w, r, req, maxPayloadBytes) {
|
if !readSSZRequest(w, r, req, sf, maxPayloadBytes) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(req.BlockHashes) > sszt.MaxBodiesRequest {
|
if len(req.BlockHashes) > sszt.MaxBodiesRequest {
|
||||||
|
|
@ -40,13 +41,13 @@ func (rt *Router) handleBodiesByHash(w http.ResponseWriter, r *http.Request, for
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bodies, timestamps := rt.backend.BodiesByHash(req.BlockHashes)
|
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=.
|
// handleBodiesByRange implements GET /engine/v2/{fork}/bodies?from=&count=.
|
||||||
func (rt *Router) handleBodiesByRange(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
func (rt *Router) handleBodiesByRange(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||||
if fork != forks.Amsterdam {
|
sf, ok := resolveFork(w, fork, forks.Paris)
|
||||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
q := r.URL.Query()
|
q := r.URL.Query()
|
||||||
|
|
@ -65,43 +66,49 @@ func (rt *Router) handleBodiesByRange(w http.ResponseWriter, r *http.Request, fo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bodies, timestamps := rt.backend.BodiesByRange(from, count)
|
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
|
// buildBodiesResponse assembles a BodiesResponse for the given fork, marking
|
||||||
// out-of-era blocks as available=false per the URL fork window.
|
// out-of-era blocks as available=false per the URL fork window. The body shape
|
||||||
func buildBodiesResponseAmsterdam(b Backend, fork forks.Fork, bodies []*types.Body, ts []uint64) *sszt.BodiesResponseAmsterdam {
|
// is fork-driven by the codec; bodyToSSZ populates the superset and the codec
|
||||||
out := &sszt.BodiesResponseAmsterdam{
|
// emits only the fork's active fields.
|
||||||
Entries: make([]*sszt.BodyEntryAmsterdam, len(bodies)),
|
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 {
|
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 {
|
if body != nil && b.ForkFromTimestamp(ts[i]) == fork {
|
||||||
entry.Available = true
|
entry.Available = true
|
||||||
entry.Body = bodyToAmsterdamSSZ(body)
|
entry.Body = bodyToSSZ(body, sf)
|
||||||
}
|
}
|
||||||
out.Entries[i] = entry
|
out.Entries[i] = entry
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// bodyToAmsterdamSSZ flattens a *types.Body into the Amsterdam body shape.
|
// bodyToSSZ flattens a *types.Body into the monolithic body shape. Withdrawals
|
||||||
func bodyToAmsterdamSSZ(body *types.Body) *sszt.ExecutionPayloadBodyAmsterdam {
|
// are only attached from Shanghai on, matching the fork's wire shape.
|
||||||
out := &sszt.ExecutionPayloadBodyAmsterdam{
|
func bodyToSSZ(body *types.Body, sf ssz.Fork) *sszt.ExecutionPayloadBody {
|
||||||
|
out := &sszt.ExecutionPayloadBody{
|
||||||
Transactions: make([][]byte, len(body.Transactions)),
|
Transactions: make([][]byte, len(body.Transactions)),
|
||||||
Withdrawals: make([]*sszt.Withdrawal, len(body.Withdrawals)),
|
|
||||||
}
|
}
|
||||||
for i, tx := range body.Transactions {
|
for i, tx := range body.Transactions {
|
||||||
out.Transactions[i], _ = tx.MarshalBinary()
|
out.Transactions[i], _ = tx.MarshalBinary()
|
||||||
}
|
}
|
||||||
for i, w := range body.Withdrawals {
|
if sf >= ssz.ForkShapella {
|
||||||
out.Withdrawals[i] = &sszt.Withdrawal{
|
out.Withdrawals = make([]*sszt.Withdrawal, len(body.Withdrawals))
|
||||||
Index: w.Index,
|
for i, w := range body.Withdrawals {
|
||||||
ValidatorIndex: w.Validator,
|
out.Withdrawals[i] = &sszt.Withdrawal{
|
||||||
Address: w.Address,
|
Index: w.Index,
|
||||||
Amount: w.Amount,
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,14 @@ import (
|
||||||
|
|
||||||
// handleForkchoice implements POST /engine/v2/{fork}/forkchoice.
|
// handleForkchoice implements POST /engine/v2/{fork}/forkchoice.
|
||||||
func (rt *Router) handleForkchoice(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
func (rt *Router) handleForkchoice(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||||
if fork != forks.Amsterdam {
|
// The execution-apis spec currently only defines the forkchoice envelope
|
||||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
// for Amsterdam; the inner attributes shape is fork-driven by the codec.
|
||||||
|
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fcu := new(sszt.ForkchoiceUpdateAmsterdam)
|
fcu := new(sszt.ForkchoiceUpdateAmsterdam)
|
||||||
if !readSSZRequest(w, r, fcu, maxPayloadBytes) {
|
if !readSSZRequest(w, r, fcu, sf, maxPayloadBytes) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := fcu.Validate(); err != nil {
|
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)
|
state := sszt.ForkchoiceStateToV1(fcu.ForkchoiceState)
|
||||||
var attrs *engine.PayloadAttributes
|
var attrs *engine.PayloadAttributes
|
||||||
if len(fcu.PayloadAttributes) == 1 {
|
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
|
// If PayloadAttributes is present the URL fork MUST match the fork
|
||||||
// the new payload would belong to. Today only Amsterdam URL exists
|
// the new payload would belong to. Today only Amsterdam URL exists
|
||||||
// in this implementation so the timestamp check is implicit; we
|
// in this implementation so the timestamp check is implicit; we
|
||||||
// keep an explicit guard for future fork URLs.
|
// 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,
|
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork,
|
||||||
"payload_attributes timestamp does not match URL fork")
|
"payload_attributes timestamp does not match URL fork")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
attrs = sszt.PayloadAttributesAmsterdamToEngine(ssz)
|
attrs = sszt.PayloadAttributesToEngine(attr)
|
||||||
// target_gas_limit and custody_columns are not yet plumbed into
|
// target_gas_limit and custody_columns are not yet plumbed into
|
||||||
// the JSON-RPC engine API. Custody is parsed-but-stubbed per
|
// the JSON-RPC engine API. Custody is parsed-but-stubbed per
|
||||||
// agreed scope; target_gas_limit will be picked up when the
|
// 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 {
|
if resp.PayloadID != nil {
|
||||||
out.PayloadID = [][8]byte{[8]byte(*resp.PayloadID)}
|
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/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/params/forks"
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// handleGetPayload implements GET /engine/v2/{fork}/payloads/{payloadId}.
|
// handleGetPayload implements GET /engine/v2/{fork}/payloads/{payloadId}.
|
||||||
func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork, idHex string) {
|
func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork, idHex string) {
|
||||||
if fork != forks.Amsterdam {
|
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
raw, err := hexutil.Decode(idHex)
|
raw, err := hexutil.Decode(idHex)
|
||||||
|
|
@ -46,17 +47,17 @@ func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := buildBuiltPayloadAmsterdam(env)
|
out := buildBuiltPayloadAmsterdam(env, sf)
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
writeSSZResponse(w, out)
|
writeSSZResponse(w, out, sf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
|
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
|
||||||
// the SSZ BuiltPayload shape. BlockValue/BlobsBundle/Requests come straight
|
// the SSZ BuiltPayload shape. BlockValue/BlobsBundle/Requests come straight
|
||||||
// across; the inner payload goes through the SSZ converter.
|
// 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{
|
out := &sszt.BuiltPayloadAmsterdam{
|
||||||
Payload: sszt.ExecutionPayloadAmsterdamFromEngine(env.ExecutionPayload),
|
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
|
||||||
BlockValue: new(uint256.Int),
|
BlockValue: new(uint256.Int),
|
||||||
ExecutionRequests: env.Requests,
|
ExecutionRequests: env.Requests,
|
||||||
ShouldOverrideBuilder: env.Override,
|
ShouldOverrideBuilder: env.Override,
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// readSSZRequest enforces the SSZ content-type and Content-Length cap, then
|
// readSSZRequest enforces the SSZ content-type and Content-Length cap, then
|
||||||
// decodes the request body into obj. Writes the appropriate problem response
|
// decodes the request body into obj for the given fork. Writes the appropriate
|
||||||
// on failure and returns false; the caller should return immediately.
|
// problem response on failure and returns false; the caller should return
|
||||||
func readSSZRequest(w http.ResponseWriter, r *http.Request, obj ssz.Object, max int64) bool {
|
// 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) {
|
if !strings.EqualFold(r.Header.Get("Content-Type"), sszContentType) {
|
||||||
writeProblem(w, http.StatusUnsupportedMediaType, ErrUnsupportedMedia, "expected "+sszContentType)
|
writeProblem(w, http.StatusUnsupportedMediaType, ErrUnsupportedMedia, "expected "+sszContentType)
|
||||||
return false
|
return false
|
||||||
|
|
@ -54,18 +55,18 @@ func readSSZRequest(w http.ResponseWriter, r *http.Request, obj ssz.Object, max
|
||||||
writeProblem(w, http.StatusRequestEntityTooLarge, ErrRequestTooLarge, "")
|
writeProblem(w, http.StatusRequestEntityTooLarge, ErrRequestTooLarge, "")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if err := ssz.DecodeFromBytes(body, obj); err != nil {
|
if err := ssz.DecodeFromBytesOnFork(body, obj, fork); err != nil {
|
||||||
writeProblem(w, http.StatusBadRequest, ErrSSZDecode, "")
|
writeProblem(w, http.StatusBadRequest, ErrSSZDecode, "")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeSSZResponse encodes obj into the response body. On encode failure it
|
// writeSSZResponse encodes obj into the response body for the given fork. On
|
||||||
// writes an internal-error problem and returns false.
|
// encode failure it writes an internal-error problem and returns false.
|
||||||
func writeSSZResponse(w http.ResponseWriter, obj ssz.Object) bool {
|
func writeSSZResponse(w http.ResponseWriter, obj ssz.Object, fork ssz.Fork) bool {
|
||||||
buf := make([]byte, ssz.Size(obj))
|
buf := make([]byte, ssz.SizeOnFork(obj, fork))
|
||||||
if err := ssz.EncodeToBytes(buf, obj); err != nil {
|
if err := ssz.EncodeToBytesOnFork(buf, obj, fork); err != nil {
|
||||||
writeProblem(w, http.StatusInternalServerError, ErrInternal, err.Error())
|
writeProblem(w, http.StatusInternalServerError, ErrInternal, err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,21 @@ import (
|
||||||
|
|
||||||
// handleNewPayload implements POST /engine/v2/{fork}/payloads.
|
// handleNewPayload implements POST /engine/v2/{fork}/payloads.
|
||||||
func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork forks.Fork) {
|
||||||
if fork != forks.Amsterdam {
|
// The execution-apis spec currently only defines the payload envelope for
|
||||||
writeProblem(w, http.StatusBadRequest, ErrUnsupportedFork, "")
|
// Amsterdam; the inner payload shape is fork-driven by the codec.
|
||||||
|
sf, ok := resolveFork(w, fork, forks.Amsterdam)
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
env := new(sszt.ExecutionPayloadEnvelopeAmsterdam)
|
env := new(sszt.ExecutionPayloadEnvelopeAmsterdam)
|
||||||
if !readSSZRequest(w, r, env, maxPayloadBytes) {
|
if !readSSZRequest(w, r, env, sf, maxPayloadBytes) {
|
||||||
return
|
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
|
// The spec drops expectedBlobVersionedHashes; we recompute them from the
|
||||||
// payload's transactions before passing to the EL.
|
// 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)
|
mapBackendErr(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeSSZResponse(w, sszt.PayloadStatusFromV1(&status))
|
writeSSZResponse(w, sszt.PayloadStatusFromV1(&status), sf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// versionedHashesFromTxs extracts the blob versioned-hash list from the
|
// 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)))
|
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()
|
t.Helper()
|
||||||
buf := make([]byte, ssz.Size(body))
|
buf := make([]byte, ssz.SizeOnFork(body, fork))
|
||||||
if err := ssz.EncodeToBytes(buf, body); err != nil {
|
if err := ssz.EncodeToBytesOnFork(buf, body, fork); err != nil {
|
||||||
t.Fatalf("encode: %v", err)
|
t.Fatalf("encode: %v", err)
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequest(http.MethodPost, srv.URL+BasePath+path, bytes.NewReader(buf))
|
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
|
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()
|
t.Helper()
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read body: %v", err)
|
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)
|
t.Fatalf("decode response: %v\nbody: %x", err, body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,19 +139,24 @@ func TestRouterNewPayload(t *testing.T) {
|
||||||
srv := newTestServer(t, b)
|
srv := newTestServer(t, b)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
amsterdam, _ := sszt.ForkFor(forks.Amsterdam)
|
||||||
|
blob, excess, slot := uint64(0), uint64(0), uint64(0)
|
||||||
env := &sszt.ExecutionPayloadEnvelopeAmsterdam{
|
env := &sszt.ExecutionPayloadEnvelopeAmsterdam{
|
||||||
Payload: &sszt.ExecutionPayloadAmsterdam{
|
Payload: &sszt.ExecutionPayload{
|
||||||
BaseFeePerGas: uint256.NewInt(7e9),
|
BaseFeePerGas: uint256.NewInt(7e9),
|
||||||
LogsBloom: [256]byte{},
|
LogsBloom: [256]byte{},
|
||||||
|
BlobGasUsed: &blob,
|
||||||
|
ExcessBlobGas: &excess,
|
||||||
|
SlotNumber: &slot,
|
||||||
},
|
},
|
||||||
ParentBeaconBlockRoot: common.Hash{0x55},
|
ParentBeaconBlockRoot: common.Hash{0x55},
|
||||||
}
|
}
|
||||||
resp := sszPost(t, srv, "/amsterdam/payloads", env)
|
resp := sszPost(t, srv, "/amsterdam/payloads", env, amsterdam)
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
got := new(sszt.PayloadStatus)
|
got := new(sszt.PayloadStatus)
|
||||||
decodeSSZ(t, resp, got)
|
decodeSSZ(t, resp, got, amsterdam)
|
||||||
if got.Status != sszt.StatusValid {
|
if got.Status != sszt.StatusValid {
|
||||||
t.Errorf("status=%d want VALID(%d)", 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)
|
srv := newTestServer(t, b)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
amsterdam, _ := sszt.ForkFor(forks.Amsterdam)
|
||||||
fcu := &sszt.ForkchoiceUpdateAmsterdam{
|
fcu := &sszt.ForkchoiceUpdateAmsterdam{
|
||||||
ForkchoiceState: &sszt.ForkchoiceState{HeadBlockHash: common.Hash{0xaa}},
|
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 {
|
if resp.StatusCode != 200 {
|
||||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
got := new(sszt.ForkchoiceUpdateResponseAmsterdam)
|
got := new(sszt.ForkchoiceUpdateResponseAmsterdam)
|
||||||
decodeSSZ(t, resp, got)
|
decodeSSZ(t, resp, got, amsterdam)
|
||||||
if got.PayloadStatus == nil || got.PayloadStatus.Status != sszt.StatusValid {
|
if got.PayloadStatus == nil || got.PayloadStatus.Status != sszt.StatusValid {
|
||||||
t.Errorf("status: %#v", got.PayloadStatus)
|
t.Errorf("status: %#v", got.PayloadStatus)
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +316,7 @@ func TestRouterBlobsV2AllOrNothing(t *testing.T) {
|
||||||
srv := newTestServer(t, b)
|
srv := newTestServer(t, b)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
req := &sszt.BlobsVersionedHashesRequest{VersionedHashes: []common.Hash{{0x01}}}
|
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()
|
defer resp.Body.Close()
|
||||||
if resp.StatusCode != 204 {
|
if resp.StatusCode != 204 {
|
||||||
t.Fatalf("want 204, got %d", resp.StatusCode)
|
t.Fatalf("want 204, got %d", resp.StatusCode)
|
||||||
|
|
@ -322,12 +328,12 @@ func TestRouterBlobsV3PartialOK(t *testing.T) {
|
||||||
srv := newTestServer(t, b)
|
srv := newTestServer(t, b)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
req := &sszt.BlobsVersionedHashesRequest{VersionedHashes: []common.Hash{{0x01}, {0x02}}}
|
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 {
|
if resp.StatusCode != 200 {
|
||||||
t.Fatalf("want 200, got %d", resp.StatusCode)
|
t.Fatalf("want 200, got %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
got := new(sszt.BlobsV2Response)
|
got := new(sszt.BlobsV2Response)
|
||||||
decodeSSZ(t, resp, got)
|
decodeSSZ(t, resp, got, ssz.ForkUnknown)
|
||||||
if len(got.Entries) != 2 {
|
if len(got.Entries) != 2 {
|
||||||
t.Fatalf("entries=%d", len(got.Entries))
|
t.Fatalf("entries=%d", len(got.Entries))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue