diff --git a/.gitignore b/.gitignore
index 269455db7a..5712e250e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,4 +55,7 @@ cmd/ethkey/ethkey
cmd/evm/evm
cmd/geth/geth
cmd/rlpdump/rlpdump
-cmd/workload/workload
\ No newline at end of file
+cmd/workload/workload
+
+# claude
+.claude
diff --git a/beacon/blsync/engineclient.go b/beacon/blsync/engineclient.go
index f9821fc6f3..7c9d285433 100644
--- a/beacon/blsync/engineclient.go
+++ b/beacon/blsync/engineclient.go
@@ -101,6 +101,17 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
params = []any{execData}
)
switch fork {
+ case "electra1":
+ // TODO(BRIP-4): Add ParentProposerPubkey to the chainHeadEvent to add to the newPayload call.
+ method = "engine_newPayloadV4P11"
+ parentBeaconRoot := event.BeaconHead.ParentRoot
+ blobHashes := collectBlobHashes(event.Block)
+ hexRequests := make([]hexutil.Bytes, len(event.ExecRequests))
+ for i := range event.ExecRequests {
+ hexRequests[i] = hexutil.Bytes(event.ExecRequests[i])
+ }
+ parentProposerPubkey := &common.Pubkey{}
+ params = append(params, blobHashes, parentBeaconRoot, hexRequests, parentProposerPubkey)
case "electra":
method = "engine_newPayloadV4"
parentBeaconRoot := event.BeaconHead.ParentRoot
@@ -145,6 +156,8 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead
var method string
switch fork {
+ case "electra1":
+ method = "engine_forkchoiceUpdatedV3P11"
case "deneb", "electra":
method = "engine_forkchoiceUpdatedV3"
case "capella":
diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go
index b1f01b50ff..a15186e3d5 100644
--- a/beacon/engine/gen_blockparams.go
+++ b/beacon/engine/gen_blockparams.go
@@ -21,6 +21,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
}
var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp)
@@ -28,6 +29,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot
+ enc.ProposerPubkey = p.ProposerPubkey
return json.Marshal(&enc)
}
@@ -39,6 +41,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
}
var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil {
@@ -62,5 +65,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot
}
+ if dec.ProposerPubkey != nil {
+ p.ProposerPubkey = dec.ProposerPubkey
+ }
return nil
}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index 76bfd22a23..1bfbcce506 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -33,9 +33,10 @@ import (
type PayloadVersion byte
var (
- PayloadV1 PayloadVersion = 0x1
- PayloadV2 PayloadVersion = 0x2
- PayloadV3 PayloadVersion = 0x3
+ PayloadV1 PayloadVersion = 0x1
+ PayloadV2 PayloadVersion = 0x2
+ PayloadV3 PayloadVersion = 0x3
+ PayloadV3P11 PayloadVersion = 0x31
)
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go
@@ -48,6 +49,7 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"` // Berachain: only used prague1 and onwards
}
// JSON type overrides for PayloadAttributes.
@@ -218,8 +220,8 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) {
// and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data.
-func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
- block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests)
+func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, proposerPubkey *common.Pubkey) (*types.Block, error) {
+ block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil {
return nil, err
}
@@ -232,7 +234,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
// for stateless execution, so it skips checking if the executable data hashes to
// the requested hash (stateless has to *compute* the root hash, it's not given).
-func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte) (*types.Block, error) {
+func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, proposerPubkey *common.Pubkey) (*types.Block, error) {
txs, err := decodeTransactions(data.Transactions)
if err != nil {
return nil, err
@@ -275,26 +277,27 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
}
header := &types.Header{
- ParentHash: data.ParentHash,
- UncleHash: types.EmptyUncleHash,
- Coinbase: data.FeeRecipient,
- Root: data.StateRoot,
- TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
- ReceiptHash: data.ReceiptsRoot,
- Bloom: types.BytesToBloom(data.LogsBloom),
- Difficulty: common.Big0,
- Number: new(big.Int).SetUint64(data.Number),
- GasLimit: data.GasLimit,
- GasUsed: data.GasUsed,
- Time: data.Timestamp,
- BaseFee: data.BaseFeePerGas,
- Extra: data.ExtraData,
- MixDigest: data.Random,
- WithdrawalsHash: withdrawalsRoot,
- ExcessBlobGas: data.ExcessBlobGas,
- BlobGasUsed: data.BlobGasUsed,
- ParentBeaconRoot: beaconRoot,
- RequestsHash: requestsHash,
+ ParentHash: data.ParentHash,
+ UncleHash: types.EmptyUncleHash,
+ Coinbase: data.FeeRecipient,
+ Root: data.StateRoot,
+ TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
+ ReceiptHash: data.ReceiptsRoot,
+ Bloom: types.BytesToBloom(data.LogsBloom),
+ Difficulty: common.Big0,
+ Number: new(big.Int).SetUint64(data.Number),
+ GasLimit: data.GasLimit,
+ GasUsed: data.GasUsed,
+ Time: data.Timestamp,
+ BaseFee: data.BaseFeePerGas,
+ Extra: data.ExtraData,
+ MixDigest: data.Random,
+ WithdrawalsHash: withdrawalsRoot,
+ ExcessBlobGas: data.ExcessBlobGas,
+ BlobGasUsed: data.BlobGasUsed,
+ ParentBeaconRoot: beaconRoot,
+ RequestsHash: requestsHash,
+ ParentProposerPubkey: proposerPubkey,
}
return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).
diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go
index 37a6db9ffc..c81669ce05 100644
--- a/cmd/evm/internal/t8ntool/block.go
+++ b/cmd/evm/internal/t8ntool/block.go
@@ -56,6 +56,7 @@ type header struct {
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
}
type headerMarshaling struct {
@@ -117,25 +118,26 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error {
// ToBlock converts i into a *types.Block
func (i *bbInput) ToBlock() *types.Block {
header := &types.Header{
- ParentHash: i.Header.ParentHash,
- UncleHash: types.EmptyUncleHash,
- Coinbase: common.Address{},
- Root: i.Header.Root,
- TxHash: types.EmptyTxsHash,
- ReceiptHash: types.EmptyReceiptsHash,
- Bloom: i.Header.Bloom,
- Difficulty: common.Big0,
- Number: i.Header.Number,
- GasLimit: i.Header.GasLimit,
- GasUsed: i.Header.GasUsed,
- Time: i.Header.Time,
- Extra: i.Header.Extra,
- MixDigest: i.Header.MixDigest,
- BaseFee: i.Header.BaseFee,
- WithdrawalsHash: i.Header.WithdrawalsHash,
- BlobGasUsed: i.Header.BlobGasUsed,
- ExcessBlobGas: i.Header.ExcessBlobGas,
- ParentBeaconRoot: i.Header.ParentBeaconBlockRoot,
+ ParentHash: i.Header.ParentHash,
+ UncleHash: types.EmptyUncleHash,
+ Coinbase: common.Address{},
+ Root: i.Header.Root,
+ TxHash: types.EmptyTxsHash,
+ ReceiptHash: types.EmptyReceiptsHash,
+ Bloom: i.Header.Bloom,
+ Difficulty: common.Big0,
+ Number: i.Header.Number,
+ GasLimit: i.Header.GasLimit,
+ GasUsed: i.Header.GasUsed,
+ Time: i.Header.Time,
+ Extra: i.Header.Extra,
+ MixDigest: i.Header.MixDigest,
+ BaseFee: i.Header.BaseFee,
+ WithdrawalsHash: i.Header.WithdrawalsHash,
+ BlobGasUsed: i.Header.BlobGasUsed,
+ ExcessBlobGas: i.Header.ExcessBlobGas,
+ ParentBeaconRoot: i.Header.ParentBeaconBlockRoot,
+ ParentProposerPubkey: i.Header.ParentProposerPubkey,
}
// Fill optional values.
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index da1fb3701f..6891147177 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -101,6 +101,7 @@ type stEnv struct {
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
}
type stEnvMarshaling struct {
@@ -220,6 +221,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
)
core.ProcessParentBlockHash(prevHash, evm)
}
+ // TODO(BRIP-4): Validate Prague1 block rules.
for i := 0; txIt.Next(); i++ {
tx, err := txIt.Tx()
if err != nil {
diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go
index a8c8668978..84a620f97b 100644
--- a/cmd/evm/internal/t8ntool/gen_header.go
+++ b/cmd/evm/internal/t8ntool/gen_header.go
@@ -38,6 +38,7 @@ func (h header) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
}
var enc header
enc.ParentHash = h.ParentHash
@@ -60,6 +61,7 @@ func (h header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas)
enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot
+ enc.ParentProposerPubkey = h.ParentProposerPubkey
return json.Marshal(&enc)
}
@@ -86,6 +88,7 @@ func (h *header) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
}
var dec header
if err := json.Unmarshal(input, &dec); err != nil {
@@ -155,5 +158,8 @@ func (h *header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil {
h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
}
+ if dec.ParentProposerPubkey != nil {
+ h.ParentProposerPubkey = dec.ParentProposerPubkey
+ }
return nil
}
diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go
index d47db4a876..ec16267f31 100644
--- a/cmd/evm/internal/t8ntool/gen_stenv.go
+++ b/cmd/evm/internal/t8ntool/gen_stenv.go
@@ -37,6 +37,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
}
var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@@ -59,6 +60,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot
+ enc.ParentProposerPubkey = s.ParentProposerPubkey
return json.Marshal(&enc)
}
@@ -85,6 +87,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
}
var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil {
@@ -154,5 +157,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil {
s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
}
+ if dec.ParentProposerPubkey != nil {
+ s.ParentProposerPubkey = dec.ParentProposerPubkey
+ }
return nil
}
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index e946ccddd5..342ee595c7 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -150,6 +150,9 @@ func Transition(ctx *cli.Context) error {
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return err
}
+ if err := applyPrague1Checks(&prestate.Env, chainConfig); err != nil {
+ return err
+ }
// Configure tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
@@ -265,6 +268,19 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil
}
+func applyPrague1Checks(env *stEnv, chainConfig *params.ChainConfig) error {
+ if !chainConfig.IsPrague1(big.NewInt(int64(env.Number)), env.Timestamp) {
+ env.ParentProposerPubkey = nil // un-set it if it has been set too early
+ return nil
+ }
+ // Post-prague1
+ // We require BRIP-0004 proposer pubkey to be set in the env
+ if env.ParentProposerPubkey == nil {
+ return NewError(ErrorConfig, errors.New("post-prague1 env requires parentProposerPubkey to be set"))
+ }
+ return nil
+}
+
type Alloc map[common.Address]types.Account
func (g Alloc) OnRoot(common.Hash) {}
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index c08dc3ba37..7d32e3877a 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -286,7 +286,7 @@ func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience.
switch {
case ctx.IsSet(utils.MainnetFlag.Name):
- log.Info("Starting Geth on Berachain mainnet...")
+ log.Info("Starting Geth on Ethereum mainnet...")
case ctx.IsSet(utils.SepoliaFlag.Name):
log.Info("Starting Geth on Sepolia testnet...")
diff --git a/common/types.go b/common/types.go
index fdb25f1b34..d33451cdf9 100644
--- a/common/types.go
+++ b/common/types.go
@@ -39,11 +39,16 @@ const (
HashLength = 32
// AddressLength is the expected length of the address
AddressLength = 20
+
+ // Berachain: PubkeyLength represents the expected byte length of a BLS12-381 public key
+ // as used by the beacon chain.
+ PubkeyLength = 48
)
var (
hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{})
+ pubkeyT = reflect.TypeOf(Pubkey{})
// MaxAddress represents the maximum possible address value.
MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff")
@@ -486,3 +491,60 @@ func (b PrettyBytes) TerminalString() string {
}
return fmt.Sprintf("%#x...%x (%dB)", b[:3], b[len(b)-3:], len(b))
}
+
+/////////// Berachain: Pubkey
+
+// Pubkey represents a fixed-length 48-byte BLS public key.
+// JSON and text serialization use 0x-prefixed hex strings.
+type Pubkey [PubkeyLength]byte
+
+// Bytes returns a copy of the underlying byte slice.
+func (p Pubkey) Bytes() []byte { return p[:] }
+
+// String returns the hex-encoded string representation of the pubkey.
+func (p Pubkey) String() string { return hexutil.Encode(p[:]) }
+
+// Format implements fmt.Formatter.
+// Pubkey supports the %v, %s, %q, %x, %X and %d format verbs.
+func (p Pubkey) Format(s fmt.State, c rune) {
+ hexb := make([]byte, 2+len(p)*2)
+ copy(hexb, "0x")
+ hex.Encode(hexb[2:], p[:])
+
+ switch c {
+ case 'x', 'X':
+ if !s.Flag('#') {
+ hexb = hexb[2:]
+ }
+ if c == 'X' {
+ hexb = bytes.ToUpper(hexb)
+ }
+ fallthrough
+ case 'v', 's':
+ s.Write(hexb)
+ case 'q':
+ q := []byte{'"'}
+ s.Write(q)
+ s.Write(hexb)
+ s.Write(q)
+ case 'd':
+ fmt.Fprint(s, ([len(p)]byte)(p))
+ default:
+ fmt.Fprintf(s, "%%!%c(pubkey=%x)", c, p)
+ }
+}
+
+// MarshalText encodes the pubkey as a 0x-prefixed hex string.
+func (p Pubkey) MarshalText() ([]byte, error) {
+ return hexutil.Bytes(p[:]).MarshalText()
+}
+
+// UnmarshalText decodes a 0x-prefixed hex string into the pubkey.
+func (p *Pubkey) UnmarshalText(input []byte) error {
+ return hexutil.UnmarshalFixedText("Pubkey", input, p[:])
+}
+
+// UnmarshalJSON decodes a JSON string containing the 0x-prefixed hex pubkey.
+func (p *Pubkey) UnmarshalJSON(input []byte) error {
+ return hexutil.UnmarshalFixedJSON(pubkeyT, input, p[:])
+}
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index 196cbc857c..5cdc2a92e8 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -282,6 +282,17 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return err
}
}
+ // Berachain specific: verify the existence of the proposer pubkey.
+ prague1 := chain.Config().IsPrague1(header.Number, header.Time)
+ if !prague1 {
+ if header.ParentProposerPubkey != nil {
+ return fmt.Errorf("invalid proposer pubkey: have %#x, expected nil", header.ParentProposerPubkey)
+ }
+ } else {
+ if header.ParentProposerPubkey == nil {
+ return errors.New("header is missing proposer pubkey")
+ }
+ }
return nil
}
diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go
index 32b34f4e53..faa1869b9a 100644
--- a/consensus/misc/eip4844/eip4844.go
+++ b/consensus/misc/eip4844/eip4844.go
@@ -85,7 +85,7 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
switch config.LatestFork(header.Time) {
case forks.Osaka:
frac = config.BlobScheduleConfig.Osaka.UpdateFraction
- case forks.Prague:
+ case forks.Prague, forks.Prague1:
frac = config.BlobScheduleConfig.Prague.UpdateFraction
case forks.Cancun:
frac = config.BlobScheduleConfig.Cancun.UpdateFraction
diff --git a/core/block_validator.go b/core/block_validator.go
index 591e472bc1..85ecefc85d 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -19,7 +19,9 @@ package core
import (
"errors"
"fmt"
+ "math/big"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@@ -47,7 +49,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *Bloc
// ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already
-// validated at this point.
+// validated at this point. Also it the Prague1 block according to BRIP-0004.
func (v *BlockValidator) ValidateBody(block *types.Block) error {
// Check whether the block is already imported.
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
@@ -81,9 +83,37 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
return errors.New("withdrawals present in block body")
}
+ // Berachain: Pre-compute expected PoL tx hash when in Prague1.
+ isPrague1 := v.config.IsPrague1(block.Number(), block.Time())
+ var expectedPoLHash common.Hash
+ if isPrague1 {
+ polTx, err := types.NewPoLTx(
+ v.config.ChainID,
+ v.config.Berachain.Prague1.PoLDistributorAddress,
+ new(big.Int).Sub(block.Number(), big.NewInt(1)),
+ params.PoLTxGasLimit,
+ block.BaseFee(),
+ block.ProposerPubkey(),
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create expected PoL tx: %w", err)
+ }
+ expectedPoLHash = polTx.Hash()
+ }
+
// Blob transactions may be present after the Cancun fork.
var blobs int
for i, tx := range block.Transactions() {
+ // Berachain: validate the PoL tx is only the first tx in the block.
+ switch {
+ case isPrague1 && i == 0:
+ if tx.Hash() != expectedPoLHash {
+ return fmt.Errorf("PoL tx hash mismatch: have %v, want %v", tx.Hash(), expectedPoLHash)
+ }
+ case tx.Type() == types.PoLTxType:
+ return fmt.Errorf("invalid block: tx at index %d is a PoL tx", i)
+ }
+
// Count the number of blobs to validate against the header's blobGasUsed
blobs += len(tx.BlobHashes())
diff --git a/core/block_validator_test.go b/core/block_validator_test.go
index fcc99effd0..86b0ac76dd 100644
--- a/core/block_validator_test.go
+++ b/core/block_validator_test.go
@@ -30,8 +30,127 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/trie"
)
+// -----------------------------------------------------------------------------
+// Berachain:Prague-1 PoL validation tests
+// -----------------------------------------------------------------------------
+
+// newPrague1Config returns a ChainConfig where Prague (and Prague-1) are active
+// from timestamp 0. The caller can specify the PoL distributor address.
+func newPrague1Config(distributor common.Address) *params.ChainConfig {
+ zero := uint64(0)
+ cfg := *params.AllDevChainProtocolChanges // copy
+ cfg.Berachain.Prague1 = params.Prague1Config{
+ Time: &zero,
+ PoLDistributorAddress: distributor,
+ }
+ return &cfg
+}
+
+// buildTestChain initialises an in-memory blockchain with the provided config
+// and returns the chain and its validator.
+func buildTestChain(t *testing.T, cfg *params.ChainConfig) (*BlockChain, Validator) {
+ t.Helper()
+ db := rawdb.NewMemoryDatabase()
+ genesis := &Genesis{Config: cfg}
+ chain, err := NewBlockChain(db, genesis, ethash.NewFaker(), nil)
+ if err != nil {
+ t.Fatalf("failed to create blockchain: %v", err)
+ }
+ return chain, chain.Validator()
+}
+
+// samplePubkey returns a deterministic 48-byte pubkey for tests.
+func samplePubkey() *common.Pubkey {
+ var pk common.Pubkey
+ for i := 0; i < common.PubkeyLength; i++ {
+ pk[i] = byte(i)
+ }
+ return &pk
+}
+
+// makeBlock builds a child block on top of parent with the supplied txs and timestamp.
+func makeBlock(parent *types.Header, txs types.Transactions, timestamp uint64) *types.Block {
+ header := &types.Header{
+ ParentHash: parent.Hash(),
+ Number: new(big.Int).Add(parent.Number, big.NewInt(1)),
+ Time: timestamp,
+ GasLimit: params.GenesisGasLimit * 10,
+ Difficulty: big.NewInt(1),
+ ParentProposerPubkey: samplePubkey(),
+ BaseFee: big.NewInt(1000000000),
+ }
+ return types.NewBlock(header, &types.Body{Transactions: txs}, nil, trie.NewStackTrie(nil))
+}
+
+func TestValidateBody_Prague1_Valid(t *testing.T) {
+ distributor := common.HexToAddress("0x1111111111111111111111111111111111111111")
+ cfg := newPrague1Config(distributor)
+ chain, validator := buildTestChain(t, cfg)
+
+ // Build PoL tx + dummy tx.
+ polTx, err := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
+ if err != nil {
+ t.Fatalf("failed to create PoL tx: %v", err)
+ }
+ dummyTx := types.NewTx(&types.LegacyTx{Nonce: 1})
+
+ block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx, dummyTx}, 1)
+
+ if err := validator.ValidateBody(block); err != nil {
+ t.Fatalf("ValidateBody returned error for valid Prague1 block: %v", err)
+ }
+}
+
+func TestValidateBody_Prague1_InvalidHash(t *testing.T) {
+ distributor := common.HexToAddress("0x2222222222222222222222222222222222222222")
+ cfg := newPrague1Config(distributor)
+ chain, validator := buildTestChain(t, cfg)
+
+ // PoL tx with WRONG pubkey (different from header.ParentProposerPubkey).
+ wrongPk := &common.Pubkey{}
+ polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), wrongPk)
+ block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx}, 1)
+
+ if err := validator.ValidateBody(block); err == nil {
+ t.Fatalf("expected error due to invalid PoL hash, got nil")
+ }
+}
+
+func TestValidateBody_Prague1_MisplacedPoL(t *testing.T) {
+ distributor := common.HexToAddress("0x3333333333333333333333333333333333333333")
+ cfg := newPrague1Config(distributor)
+ chain, validator := buildTestChain(t, cfg)
+
+ polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
+ dummyTx := types.NewTx(&types.LegacyTx{Nonce: 1})
+ // PoL tx placed second.
+ block := makeBlock(chain.CurrentHeader(), types.Transactions{dummyTx, polTx}, 1)
+
+ if err := validator.ValidateBody(block); err == nil {
+ t.Fatalf("expected error for PoL tx at index >0, got nil")
+ }
+}
+
+func TestValidateBody_PrePrague1_PoLProhibited(t *testing.T) {
+ distributor := common.HexToAddress("0x4444444444444444444444444444444444444444")
+ // Prague time active, but Prague1 *future* at timestamp 1000.
+ future := uint64(1000)
+ cfg := *params.AllDevChainProtocolChanges
+ cfg.Berachain.Prague1.Time = &future
+ cfg.Berachain.Prague1.PoLDistributorAddress = distributor
+ chain, validator := buildTestChain(t, &cfg)
+
+ polTx, _ := types.NewPoLTx(cfg.ChainID, distributor, big.NewInt(0), params.PoLTxGasLimit, big.NewInt(1000000000), samplePubkey())
+ block := makeBlock(chain.CurrentHeader(), types.Transactions{polTx}, 1) // timestamp 1 < future
+
+ if err := validator.ValidateBody(block); err == nil {
+ t.Fatalf("expected error: PoL tx before Prague1 fork should be invalid")
+ }
+}
+
// Tests that simple header verification works, for both good and bad blocks.
func TestHeaderVerification(t *testing.T) {
testHeaderVerification(t, rawdb.HashScheme)
diff --git a/core/chain_makers.go b/core/chain_makers.go
index b2559495a1..ef680f8d27 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -102,6 +102,12 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{}))
}
+// SetParentProposerPubkey sets the parent proposer pubkey field of the generated
+// block.
+func (b *BlockGen) SetParentProposerPubkey(pubkey common.Pubkey) {
+ b.header.ParentProposerPubkey = &pubkey
+}
+
// addTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address.
//
@@ -116,9 +122,16 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
var (
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig)
+ gasPool = b.gasPool
+ gasUsed = &b.header.GasUsed
)
+ // Berachain: PoL txs do not count towards block gas.
+ if tx.Type() == types.PoLTxType {
+ gasPool = new(GasPool).AddGas(params.PoLTxGasLimit)
+ gasUsed = new(uint64)
+ }
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
- receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed)
+ receipt, err := ApplyTransaction(evm, gasPool, b.statedb, b.header, tx, gasUsed)
if err != nil {
panic(err)
}
@@ -617,6 +630,9 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
header.BlobGasUsed = new(uint64)
header.ParentBeaconRoot = new(common.Hash)
}
+ if cm.config.IsPrague1(header.Number, header.Time) {
+ header.ParentProposerPubkey = new(common.Pubkey)
+ }
return header
}
diff --git a/core/genesis.go b/core/genesis.go
index 42dc493522..1ba8bc9b23 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -526,6 +526,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash
}
+ if conf.IsPrague1(num, g.Timestamp) {
+ // BRIP-0004: The parentProposerPubkey of the genesis block is always
+ // the zero pubkey. This is because the genesis block does not have a parent
+ // by definition.
+ head.ParentProposerPubkey = new(common.Pubkey)
+ }
}
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
}
diff --git a/core/state_processor_test.go b/core/state_processor_test.go
index d175b5eac5..b4e7690639 100644
--- a/core/state_processor_test.go
+++ b/core/state_processor_test.go
@@ -406,6 +406,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
nBlobs += len(tx.BlobHashes())
}
header.Root = common.BytesToHash(hasher.Sum(nil))
+ if config.IsPrague1(header.Number, header.Time) {
+ proposerPubkey := common.Pubkey{0x01, 0x02, 0x03}
+ header.ParentProposerPubkey = &proposerPubkey
+ }
if config.IsCancun(header.Number, header.Time) {
excess := eip4844.CalcExcessBlobGas(config, parent.Header(), header.Time)
used := uint64(nBlobs * params.BlobTxBlobGasPerBlob)
diff --git a/core/state_transition.go b/core/state_transition.go
index 3ec718c52c..aa1e20d5f4 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -166,6 +166,9 @@ type Message struct {
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
SkipFromEOACheck bool
+
+ // Berachain:IsPoLTx is true if the message is a PoL tx.
+ IsPoLTx bool
}
// TransactionToMessage converts a transaction into a Message.
@@ -185,6 +188,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipFromEOACheck: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
+ IsPoLTx: tx.Type() == types.PoLTxType,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
@@ -207,9 +211,30 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
// state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
evm.SetTxContext(NewEVMTxContext(msg))
+
+ // Berachain: ApplyPoLMessage is used to apply the PoL tx to the EVM, skipping the
+ // normal state transition checks.
+ if msg.IsPoLTx {
+ return ApplyPoLMessage(msg, evm), nil
+ }
+
return newStateTransition(evm, msg, gp).execute()
}
+// Berachain: ApplyPoLMessage applies the BRIP-0004 PoL tx to the EVM. No gas is consumed.
+func ApplyPoLMessage(msg *Message, evm *vm.EVM) *ExecutionResult {
+ evm.StateDB.AddAddressToAccessList(*msg.To)
+
+ result := &ExecutionResult{}
+ ret, leftOverGas, err := evm.Call(msg.From, *msg.To, msg.Data, msg.GasLimit, common.U2560)
+ if err != nil {
+ result.Err = fmt.Errorf("PoL tx failed to execute: %v", err)
+ }
+ result.ReturnData = ret
+ result.MaxUsedGas = msg.GasLimit - leftOverGas // To inform how much gas was needed to run the msg.
+ return result
+}
+
// stateTransition represents a state transition.
//
// == The State Transitioning Model
diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go
index b5470cd7fc..9810e2249a 100644
--- a/core/txpool/txpool.go
+++ b/core/txpool/txpool.go
@@ -327,6 +327,11 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
// Mark this transaction belonging to no-subpool
splits[i] = -1
+ // Berachain: PoL txs are rejected.
+ if tx.Type() == types.PoLTxType {
+ continue
+ }
+
// Try to find a subpool that accepts the transaction
for j, subpool := range p.subpools {
if subpool.Filter(tx) {
diff --git a/core/types/block.go b/core/types/block.go
index b284fb3b16..f3cff4dd03 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -106,6 +106,9 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
+
+ // ParentProposerPubkey was added by BRIP-0004 and is ignored in legacy headers.
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
}
// field type overrides for gencodec
@@ -329,6 +332,10 @@ func CopyHeader(h *Header) *Header {
cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash
}
+ if h.ParentProposerPubkey != nil {
+ cpy.ParentProposerPubkey = new(common.Pubkey)
+ *cpy.ParentProposerPubkey = *h.ParentProposerPubkey
+ }
return &cpy
}
@@ -408,8 +415,9 @@ func (b *Block) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee)
}
-func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
-func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
+func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
+func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash }
+func (b *Block) ProposerPubkey() *common.Pubkey { return b.header.ParentProposerPubkey }
func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64
diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go
index 0af12500bd..1dd179b704 100644
--- a/core/types/gen_header_json.go
+++ b/core/types/gen_header_json.go
@@ -16,28 +16,29 @@ var _ = (*headerMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (h Header) MarshalJSON() ([]byte, error) {
type Header struct {
- ParentHash common.Hash `json:"parentHash" gencodec:"required"`
- UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
- Coinbase common.Address `json:"miner"`
- Root common.Hash `json:"stateRoot" gencodec:"required"`
- TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
- ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
- Bloom Bloom `json:"logsBloom" gencodec:"required"`
- Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
- Number *hexutil.Big `json:"number" gencodec:"required"`
- GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
- GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
- Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
- Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
- MixDigest common.Hash `json:"mixHash"`
- Nonce BlockNonce `json:"nonce"`
- BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
- WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
- BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
- ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
- ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
- RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
- Hash common.Hash `json:"hash"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
+ Coinbase common.Address `json:"miner"`
+ Root common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
+ ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
+ Bloom Bloom `json:"logsBloom" gencodec:"required"`
+ Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
+ Number *hexutil.Big `json:"number" gencodec:"required"`
+ GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
+ MixDigest common.Hash `json:"mixHash"`
+ Nonce BlockNonce `json:"nonce"`
+ BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
+ WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
+ BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
+ ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
+ ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
+ Hash common.Hash `json:"hash"`
}
var enc Header
enc.ParentHash = h.ParentHash
@@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash
+ enc.ParentProposerPubkey = h.ParentProposerPubkey
enc.Hash = h.Hash()
return json.Marshal(&enc)
}
@@ -68,27 +70,28 @@ func (h Header) MarshalJSON() ([]byte, error) {
// UnmarshalJSON unmarshals from JSON.
func (h *Header) UnmarshalJSON(input []byte) error {
type Header struct {
- ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
- UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
- Coinbase *common.Address `json:"miner"`
- Root *common.Hash `json:"stateRoot" gencodec:"required"`
- TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
- ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
- Bloom *Bloom `json:"logsBloom" gencodec:"required"`
- Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
- Number *hexutil.Big `json:"number" gencodec:"required"`
- GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
- GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
- Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
- Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
- MixDigest *common.Hash `json:"mixHash"`
- Nonce *BlockNonce `json:"nonce"`
- BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
- WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
- BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
- ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
- ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
- RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
+ ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
+ UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
+ Coinbase *common.Address `json:"miner"`
+ Root *common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"`
+ ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"`
+ Bloom *Bloom `json:"logsBloom" gencodec:"required"`
+ Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
+ Number *hexutil.Big `json:"number" gencodec:"required"`
+ GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
+ Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
+ Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
+ MixDigest *common.Hash `json:"mixHash"`
+ Nonce *BlockNonce `json:"nonce"`
+ BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
+ WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
+ BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"`
+ ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
+ ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
+ RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
+ ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
}
var dec Header
if err := json.Unmarshal(input, &dec); err != nil {
@@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash
}
+ if dec.ParentProposerPubkey != nil {
+ h.ParentProposerPubkey = dec.ParentProposerPubkey
+ }
return nil
}
diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go
index c79aa8a250..842958ae93 100644
--- a/core/types/gen_header_rlp.go
+++ b/core/types/gen_header_rlp.go
@@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil
_tmp6 := obj.RequestsHash != nil
- if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ _tmp7 := obj.ParentProposerPubkey != nil
+ if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BaseFee == nil {
w.Write(rlp.EmptyString)
} else {
@@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee)
}
}
- if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.WithdrawalsHash[:])
}
}
- if _tmp3 || _tmp4 || _tmp5 || _tmp6 {
+ if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BlobGasUsed == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.BlobGasUsed))
}
}
- if _tmp4 || _tmp5 || _tmp6 {
+ if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80})
} else {
w.WriteUint64((*obj.ExcessBlobGas))
}
}
- if _tmp5 || _tmp6 {
+ if _tmp5 || _tmp6 || _tmp7 {
if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ParentBeaconRoot[:])
}
}
- if _tmp6 {
+ if _tmp6 || _tmp7 {
if obj.RequestsHash == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.RequestsHash[:])
}
}
+ if _tmp7 {
+ if obj.ParentProposerPubkey == nil {
+ w.Write([]byte{0x80})
+ } else {
+ w.WriteBytes(obj.ParentProposerPubkey[:])
+ }
+ }
w.ListEnd(_tmp0)
return w.Flush()
}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index 5b6669f274..6bbf2c217d 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -205,7 +205,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt
}
switch b[0] {
- case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType:
+ case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, PoLTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
@@ -368,7 +368,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
}
w.WriteByte(r.Type)
switch r.Type {
- case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType:
+ case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, PoLTxType:
rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 934feb7353..175434633e 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -49,6 +49,9 @@ const (
DynamicFeeTxType = 0x02
BlobTxType = 0x03
SetCodeTxType = 0x04
+
+ // Berachain specific.
+ PoLTxType = 0x7E
)
// Transaction is an Ethereum transaction.
@@ -211,6 +214,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(BlobTx)
case SetCodeTxType:
inner = new(SetCodeTx)
+ case PoLTxType:
+ inner = new(PoLTx)
default:
return nil, ErrTxTypeNotSupported
}
diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go
index f76d4b92e9..e4363e0d5e 100644
--- a/core/types/transaction_marshalling.go
+++ b/core/types/transaction_marshalling.go
@@ -507,6 +507,33 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
}
}
+ case PoLTxType:
+ var itx PoLTx
+ inner = &itx
+ if dec.ChainID == nil {
+ return errors.New("missing required field 'chainId' in transaction")
+ }
+ itx.ChainID = (*big.Int)(dec.ChainID)
+ if dec.To == nil {
+ return errors.New("missing required field 'to' in transaction")
+ }
+ itx.To = *dec.To
+ if dec.Nonce == nil {
+ return errors.New("missing required field 'nonce' in transaction")
+ }
+ itx.Nonce = uint64(*dec.Nonce)
+ if dec.Gas == nil {
+ return errors.New("missing required field 'gas' for txdata")
+ }
+ itx.GasLimit = uint64(*dec.Gas)
+ if dec.Value == nil {
+ return errors.New("missing required field 'value' in transaction")
+ }
+ if dec.Input == nil {
+ return errors.New("missing required field 'input' in transaction")
+ }
+ itx.Data = *dec.Input
+
default:
return ErrTxTypeNotSupported
}
diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go
index 01aa67c6ba..6bacc0ffcf 100644
--- a/core/types/transaction_signing.go
+++ b/core/types/transaction_signing.go
@@ -42,6 +42,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer
switch {
+ case config.IsPrague1(blockNumber, blockTime):
+ signer = NewPrague1Signer(config.ChainID)
case config.IsPrague(blockNumber, blockTime):
signer = NewPragueSigner(config.ChainID)
case config.IsCancun(blockNumber, blockTime):
@@ -71,6 +73,8 @@ func LatestSigner(config *params.ChainConfig) Signer {
var signer Signer
if config.ChainID != nil {
switch {
+ case config.Berachain.Prague1.Time != nil:
+ signer = NewPrague1Signer(config.ChainID)
case config.PragueTime != nil:
signer = NewPragueSigner(config.ChainID)
case config.CancunTime != nil:
@@ -100,7 +104,7 @@ func LatestSigner(config *params.ChainConfig) Signer {
func LatestSignerForChainID(chainID *big.Int) Signer {
var signer Signer
if chainID != nil {
- signer = NewPragueSigner(chainID)
+ signer = NewPrague1Signer(chainID)
} else {
signer = HomesteadSigner{}
}
@@ -219,6 +223,9 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if fork >= forks.Prague {
s.txtypes[SetCodeTxType] = struct{}{}
}
+ if fork >= forks.Prague1 {
+ s.txtypes[PoLTxType] = struct{}{}
+ }
return s
}
@@ -251,6 +258,12 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.ChainId().Cmp(s.chainID) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID)
}
+
+ // Berachain specific: PoLTx is sent by system address and carries no signature.
+ if tt == PoLTxType {
+ return params.SystemAddress, nil
+ }
+
// 'modern' txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V, R, S := tx.RawSignatureValues()
@@ -276,6 +289,18 @@ func (s *modernSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi
return R, S, V, nil
}
+// NewPrague1Signer returns a signer that accepts
+// - BRIP-0004 PoL transactions
+// - EIP-7702 set code transactions
+// - EIP-4844 blob transactions
+// - EIP-1559 dynamic fee transactions
+// - EIP-2930 access list transactions,
+// - EIP-155 replay protected transactions, and
+// - legacy Homestead transactions.
+func NewPrague1Signer(chainId *big.Int) Signer {
+ return newModernSigner(chainId, forks.Prague1)
+}
+
// NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions
// - EIP-4844 blob transactions
diff --git a/core/types/tx_pol.go b/core/types/tx_pol.go
new file mode 100644
index 0000000000..54cda4a05e
--- /dev/null
+++ b/core/types/tx_pol.go
@@ -0,0 +1,179 @@
+// Copyright 2025 Berachain Foundation
+// This file is part of the bera-geth library.
+//
+// The bera-geth 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 bera-geth 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 bera-geth library. If not, see .
+
+package types
+
+import (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+// PoLTx implements the TxData interface.
+var _ TxData = (*PoLTx)(nil)
+
+// PoLTx represents an BRIP-0004 transaction. No gas is consumed for execution.
+type PoLTx struct {
+ ChainID *big.Int
+ From common.Address // system address
+ To common.Address // address of the PoL Distributor contract
+ Nonce uint64 // block number distributing for
+ GasLimit uint64 // artificial gas limit for the PoL tx, not consumed against the block gas limit
+ GasPrice *big.Int // gas price is set to the baseFee to make the tx valid for EIP-1559 rules
+ Data []byte // encodes the pubkey distributing for
+}
+
+// NewPoLTx creates a new PoL transaction.
+func NewPoLTx(
+ chainID *big.Int,
+ distributorAddress common.Address,
+ distributionBlockNumber *big.Int,
+ gasLimit uint64,
+ baseFee *big.Int,
+ pubkey *common.Pubkey,
+) (*Transaction, error) {
+ data, err := getDistributeForData(pubkey)
+ if err != nil {
+ return nil, err
+ }
+ return NewTx(&PoLTx{
+ ChainID: chainID,
+ From: params.SystemAddress,
+ To: distributorAddress,
+ Nonce: distributionBlockNumber.Uint64(),
+ GasLimit: gasLimit,
+ GasPrice: baseFee,
+ Data: data,
+ }), nil
+}
+
+func (*PoLTx) txType() byte { return PoLTxType }
+
+// copy creates a deep copy of the transaction data and initializes all fields.
+func (tx *PoLTx) copy() TxData {
+ cpy := &PoLTx{
+ ChainID: new(big.Int),
+ From: tx.From,
+ To: tx.To,
+ Nonce: tx.Nonce,
+ GasLimit: tx.GasLimit,
+ GasPrice: new(big.Int),
+ Data: common.CopyBytes(tx.Data),
+ }
+ if tx.ChainID != nil {
+ cpy.ChainID.Set(tx.ChainID)
+ }
+ if tx.GasPrice != nil {
+ cpy.GasPrice.Set(tx.GasPrice)
+ }
+ return cpy
+}
+
+func (tx *PoLTx) chainID() *big.Int { return tx.ChainID }
+func (*PoLTx) accessList() AccessList { return nil }
+func (tx *PoLTx) data() []byte { return tx.Data }
+func (tx *PoLTx) gas() uint64 { return tx.GasLimit }
+func (tx *PoLTx) gasPrice() *big.Int { return tx.GasPrice }
+func (tx *PoLTx) gasTipCap() *big.Int { return tx.GasPrice }
+func (tx *PoLTx) gasFeeCap() *big.Int { return tx.GasPrice }
+func (*PoLTx) value() *big.Int { return new(big.Int) }
+func (tx *PoLTx) nonce() uint64 { return tx.Nonce }
+func (tx *PoLTx) to() *common.Address { return &tx.To }
+
+// No-op: PoLTx is originated from the system address and carries no signature.
+func (*PoLTx) rawSignatureValues() (v, r, s *big.Int) {
+ return nil, nil, nil
+}
+
+func (*PoLTx) setSignatureValues(chainID, v, r, s *big.Int) {
+ // No-op: PoLTx is originated from the system address and carries no signature.
+}
+
+// effectiveGasPrice returns the gas price. PoLTx does not pay for gas, but we
+// return the baseFee here to make the receipt valid for a 1559 tx.
+func (tx *PoLTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
+ return dst.Set(tx.GasPrice)
+}
+
+func (tx *PoLTx) encode(b *bytes.Buffer) error {
+ return rlp.Encode(b, tx)
+}
+
+func (tx *PoLTx) decode(input []byte) error {
+ return rlp.DecodeBytes(input, tx)
+}
+
+func (tx *PoLTx) sigHash(chainID *big.Int) common.Hash {
+ return prefixedRlpHash(
+ PoLTxType, // tx type: 0x7E
+ []any{
+ chainID, // chainID: EIP-155 chain ID
+ tx.From, // from = system address
+ tx.To, // to = address of the PoL Distributor contract
+ tx.Nonce, // nonce = block number distributing for
+ tx.GasLimit, // gasLimit = artificial gas limit for execution
+ tx.GasPrice, // gasPrice = baseFee to make the tx valid for EIP-1559 rules
+ tx.Data, // data ~= pubkey distributing for
+ })
+}
+
+var (
+ bytesType, _ = abi.NewType("bytes", "", nil)
+ distributeForMethod = abi.NewMethod(
+ "distributeFor", "distributeFor", abi.Function, "nonpayable", false, false, []abi.Argument{
+ {Name: "pubkey", Type: bytesType, Indexed: false},
+ }, nil,
+ )
+)
+
+// getDistributeForData returns the tx data for the `distributeFor(bytes pubkey)` method.
+func getDistributeForData(pubkey *common.Pubkey) ([]byte, error) {
+ var pubkeyBytes []byte
+ if pubkey == nil {
+ pubkeyBytes = common.Pubkey{}.Bytes()
+ } else {
+ pubkeyBytes = pubkey.Bytes()
+ }
+
+ arguments, err := distributeForMethod.Inputs.Pack(pubkeyBytes)
+ if err != nil {
+ return nil, err
+ }
+ return append(distributeForMethod.ID, arguments...), nil
+}
+
+// IsPoLDistribution returns true if the transaction is a PoL distribution.
+func IsPoLDistribution(to *common.Address, data []byte, distributorAddress common.Address) bool {
+ // Txs that call the `distributeFor(bytes pubkey)` method on the PoL Distributor
+ // contract are also consideredPoL txs.
+ return to != nil && *to == distributorAddress && isDistributeForCall(data)
+}
+
+// isDistributeForCall returns true if the provided calldata corresponds to a
+// call to the `distributeFor(bytes pubkey)` method defined in BRIP-0004.
+//
+// The function checks that the first four bytes (the function selector) match
+// the ID of the `distributeFor` ABI method declared in tx_pol.go.
+func isDistributeForCall(data []byte) bool {
+ if len(data) < 4 {
+ return false
+ }
+ return bytes.Equal(data[:4], distributeForMethod.ID)
+}
diff --git a/core/types/tx_pol_test.go b/core/types/tx_pol_test.go
new file mode 100644
index 0000000000..68233ee288
--- /dev/null
+++ b/core/types/tx_pol_test.go
@@ -0,0 +1,128 @@
+// Copyright 2025 Berachain Foundation
+// This file is part of the bera-geth library.
+//
+// The bera-geth 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 bera-geth 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 bera-geth library. If not, see .
+
+package types
+
+import (
+ "bytes"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// samplePubkey returns a deterministic 48-byte pubkey for tests.
+func samplePubkey() *common.Pubkey {
+ var pk common.Pubkey
+ for i := 0; i < common.PubkeyLength; i++ {
+ pk[i] = byte(i)
+ }
+ return &pk
+}
+
+// TestNewPoLTx_DataPacking verifies that NewPoLTx produces the expected calldata
+// (function selector + ABI-encoded pubkey) and that the transaction fields are
+// wired up correctly.
+func TestNewPoLTx_DataPacking(t *testing.T) {
+ chainID := big.NewInt(1)
+ distributor := common.HexToAddress("0x000000000000000000000000000000000000dEaD")
+ blockNum := big.NewInt(123)
+ pubkey := samplePubkey()
+ baseFee := big.NewInt(1000000000)
+
+ tx, err := NewPoLTx(chainID, distributor, blockNum, params.PoLTxGasLimit, baseFee, pubkey)
+ if err != nil {
+ t.Fatalf("NewPoLTx returned error: %v", err)
+ }
+ if tx.Type() != PoLTxType {
+ t.Fatalf("unexpected tx type: have %d, want %d", tx.Type(), PoLTxType)
+ }
+
+ expectedData, err := getDistributeForData(pubkey)
+ if err != nil {
+ t.Fatalf("getDistributeForData failed: %v", err)
+ }
+ if !bytes.Equal(tx.Data(), expectedData) {
+ t.Fatalf("calldata mismatch\n have: %x\n want: %x", tx.Data(), expectedData)
+ }
+ // Extra sanity: first 4 bytes must equal the method selector.
+ if !bytes.Equal(tx.Data()[:4], distributeForMethod.ID) {
+ t.Fatalf("calldata selector mismatch")
+ }
+}
+
+// TestNewPoLTx_NegativeBlockNumber ensures negative block numbers are handled
+// without panicking (uint64 wrap-around is expected).
+func TestNewPoLTx_NegativeBlockNumber(t *testing.T) {
+ chainID := big.NewInt(1)
+ distributor := common.Address{}
+ negBlock := big.NewInt(-1)
+ baseFee := big.NewInt(1000000000)
+
+ tx, err := NewPoLTx(chainID, distributor, negBlock, params.PoLTxGasLimit, baseFee, samplePubkey())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if got, want := tx.Nonce(), negBlock.Uint64(); got != want {
+ t.Fatalf("nonce mismatch: have %d, want %d", got, want)
+ }
+}
+
+// TestIsPoLDistribution exercises positive and negative cases for the helper.
+func TestIsPoLDistribution(t *testing.T) {
+ distributor := common.HexToAddress("0x1000000000000000000000000000000000000001")
+ baseFee := big.NewInt(1000000000)
+ tx, err := NewPoLTx(big.NewInt(1), distributor, big.NewInt(0), params.PoLTxGasLimit, baseFee, samplePubkey())
+ if err != nil {
+ t.Fatalf("failed to build PoL tx: %v", err)
+ }
+
+ // Positive case.
+ if !IsPoLDistribution(&distributor, tx.Data(), distributor) {
+ t.Fatalf("expected IsPoLDistribution to return true for valid PoL call")
+ }
+
+ // Wrong address.
+ otherAddr := common.HexToAddress("0x0200000000000000000000000000000000000002")
+ if IsPoLDistribution(&otherAddr, tx.Data(), distributor) {
+ t.Fatalf("expected false when distributor address mismatches")
+ }
+
+ // Too-short data.
+ shortData := []byte{0x01, 0x02, 0x03}
+ if IsPoLDistribution(&distributor, shortData, distributor) {
+ t.Fatalf("expected false for data shorter than selector")
+ }
+
+ // Nil address.
+ if IsPoLDistribution(nil, tx.Data(), distributor) {
+ t.Fatalf("expected false when to==nil")
+ }
+}
+
+// TestPoLTx_RawSignatureValues confirms that PoLTx reports no signature.
+func TestPoLTx_RawSignatureValues(t *testing.T) {
+ baseFee := big.NewInt(1000000000)
+ tx, err := NewPoLTx(big.NewInt(1), common.Address{}, big.NewInt(0), params.PoLTxGasLimit, baseFee, samplePubkey())
+ if err != nil {
+ t.Fatalf("failed to create PoL tx: %v", err)
+ }
+ v, r, s := tx.RawSignatureValues()
+ if v != nil || r != nil || s != nil {
+ t.Fatalf("expected nil signature values, have v=%v r=%v s=%v", v, r, s)
+ }
+}
diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go
index 409899d582..b7dfd0f055 100644
--- a/eth/catalyst/api.go
+++ b/eth/catalyst/api.go
@@ -87,14 +87,17 @@ var caps = []string{
"engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3",
+ "engine_forkchoiceUpdatedV3P11",
"engine_forkchoiceUpdatedWithWitnessV1",
"engine_forkchoiceUpdatedWithWitnessV2",
"engine_forkchoiceUpdatedWithWitnessV3",
+ "engine_forkchoiceUpdatedWithWitnessV3P11",
"engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1",
"engine_getPayloadV2",
"engine_getPayloadV3",
"engine_getPayloadV4",
+ "engine_getPayloadV4P11",
"engine_getPayloadV5",
"engine_getBlobsV1",
"engine_getBlobsV2",
@@ -102,14 +105,17 @@ var caps = []string{
"engine_newPayloadV2",
"engine_newPayloadV3",
"engine_newPayloadV4",
+ "engine_newPayloadV4P11",
"engine_newPayloadWithWitnessV1",
"engine_newPayloadWithWitnessV2",
"engine_newPayloadWithWitnessV3",
"engine_newPayloadWithWitnessV4",
+ "engine_newPayloadWithWitnessV4P11",
"engine_executeStatelessPayloadV1",
"engine_executeStatelessPayloadV2",
"engine_executeStatelessPayloadV3",
"engine_executeStatelessPayloadV4",
+ "engine_executeStatelessPayloadV4P11",
"engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1",
@@ -209,6 +215,8 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI {
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil {
switch {
+ case payloadAttributes.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
@@ -223,6 +231,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, pa
func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
+ case params.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
@@ -241,6 +251,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa
func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
+ case params.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
@@ -256,6 +268,28 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false)
}
+// ForkchoiceUpdatedV3P11 is equivalent to V3 with the addition of parents proposer pubkey
+// in the payload attributes. It supports only PayloadAttributesV3P11.
+func (api *ConsensusAPI) ForkchoiceUpdatedV3P11(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
+ if params != nil {
+ switch {
+ case params.Withdrawals == nil:
+ return engine.STATUS_INVALID, attributesErr("missing withdrawals")
+ case params.BeaconRoot == nil:
+ return engine.STATUS_INVALID, attributesErr("missing beacon root")
+ case params.ProposerPubkey == nil:
+ return engine.STATUS_INVALID, attributesErr("missing proposer pubkey")
+ case !api.checkFork(params.Timestamp, forks.Prague1):
+ return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads")
+ }
+ }
+ // TODO(matt): the spec requires that fcu is applied when called on a valid
+ // hash, even if params are wrong. To do this we need to split up
+ // forkchoiceUpdate into a function that only updates the head and then a
+ // function that kicks off block construction.
+ return api.forkchoiceUpdated(update, params, engine.PayloadV3P11, false)
+}
+
func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock()
@@ -383,13 +417,14 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// will replace it arbitrarily many times in between.
if payloadAttributes != nil {
args := &miner.BuildPayloadArgs{
- Parent: update.HeadBlockHash,
- Timestamp: payloadAttributes.Timestamp,
- FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
- Random: payloadAttributes.Random,
- Withdrawals: payloadAttributes.Withdrawals,
- BeaconRoot: payloadAttributes.BeaconRoot,
- Version: payloadVersion,
+ Parent: update.HeadBlockHash,
+ Timestamp: payloadAttributes.Timestamp,
+ FeeRecipient: payloadAttributes.SuggestedFeeRecipient,
+ Random: payloadAttributes.Random,
+ Withdrawals: payloadAttributes.Withdrawals,
+ BeaconRoot: payloadAttributes.BeaconRoot,
+ ProposerPubkey: payloadAttributes.ProposerPubkey,
+ Version: payloadVersion,
}
id := args.Id()
// If we already are busy generating this work, then we do not need
@@ -472,6 +507,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false)
}
+// GetPayloadV4P11 returns a cached payload by id.
+func (api *ConsensusAPI) GetPayloadV4P11(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
+ if !payloadID.Is(engine.PayloadV3P11) {
+ return nil, engine.UnsupportedFork
+ }
+ return api.getPayload(payloadID, false)
+}
+
// GetPayloadV5 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
@@ -589,7 +632,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
if params.Withdrawals != nil {
return invalidStatus, paramsErr("withdrawals not supported in V1")
}
- return api.newPayload(params, nil, nil, nil, false)
+ return api.newPayload(params, nil, nil, nil, false, nil)
}
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -610,7 +653,7 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
- return api.newPayload(params, nil, nil, nil, false)
+ return api.newPayload(params, nil, nil, nil, false, nil)
}
// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -629,7 +672,7 @@ func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHas
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
- return api.newPayload(params, versionedHashes, beaconRoot, nil, false)
+ return api.newPayload(params, versionedHashes, beaconRoot, nil, false, nil)
}
// NewPayloadV4 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
@@ -654,10 +697,37 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
- return api.newPayload(params, versionedHashes, beaconRoot, requests, false)
+ return api.newPayload(params, versionedHashes, beaconRoot, requests, false, nil)
}
-func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (engine.PayloadStatusV1, error) {
+// NewPayloadV4P11 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
+func (api *ConsensusAPI) NewPayloadV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
+ switch {
+ case params.Withdrawals == nil:
+ return invalidStatus, paramsErr("nil withdrawals post-shanghai")
+ case params.ExcessBlobGas == nil:
+ return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
+ case params.BlobGasUsed == nil:
+ return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
+ case versionedHashes == nil:
+ return invalidStatus, paramsErr("nil versionedHashes post-cancun")
+ case beaconRoot == nil:
+ return invalidStatus, paramsErr("nil beaconRoot post-cancun")
+ case executionRequests == nil:
+ return invalidStatus, paramsErr("nil executionRequests post-prague")
+ case proposerPubkey == nil:
+ return invalidStatus, paramsErr("nil proposerPubkey post-prague1")
+ case !api.checkFork(params.Timestamp, forks.Prague1):
+ return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads")
+ }
+ requests := convertRequests(executionRequests)
+ if err := validateRequests(requests); err != nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
+ }
+ return api.newPayload(params, versionedHashes, beaconRoot, requests, false, proposerPubkey)
+}
+
+func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
// The locking here is, strictly, not required. Without these locks, this can happen:
//
// 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to
@@ -675,7 +745,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
- block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests)
+ block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil {
bgu := "nil"
if params.BlobGasUsed != nil {
diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go
index cb6ae053b6..9bc413d632 100644
--- a/eth/catalyst/api_test.go
+++ b/eth/catalyst/api_test.go
@@ -305,7 +305,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create the executable data, block %d: %v", i, err)
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -347,7 +347,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create the executable data %v", err)
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -472,10 +472,10 @@ func TestFullAPI(t *testing.T) {
ethservice.TxPool().Add([]*types.Transaction{tx}, false)
}
- setupBlocks(t, ethservice, 10, parent, callback, nil, nil)
+ setupBlocks(t, ethservice, 10, parent, callback, nil, nil, nil)
}
-func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash) []*types.Header {
+func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.Header, callback func(parent *types.Header), withdrawals [][]*types.Withdrawal, beaconRoots []common.Hash, proposerPubkeys []common.Pubkey) []*types.Header {
api := NewConsensusAPI(ethservice)
var blocks []*types.Header
for i := 0; i < n; i++ {
@@ -488,9 +488,13 @@ func setupBlocks(t *testing.T, ethservice *eth.Ethereum, n int, parent *types.He
if beaconRoots != nil {
h = &beaconRoots[i]
}
+ var p *common.Pubkey
+ if proposerPubkeys != nil {
+ p = &proposerPubkeys[i]
+ }
- envelope := getNewEnvelope(t, api, parent, w, h)
- execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false)
+ envelope := getNewEnvelope(t, api, parent, w, h, p)
+ execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false, p)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
@@ -660,12 +664,13 @@ func TestNewPayloadOnInvalidChain(t *testing.T) {
func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.PayloadAttributes) (*engine.ExecutionPayloadEnvelope, error) {
args := &miner.BuildPayloadArgs{
- Parent: parentHash,
- Timestamp: params.Timestamp,
- FeeRecipient: params.SuggestedFeeRecipient,
- Random: params.Random,
- Withdrawals: params.Withdrawals,
- BeaconRoot: params.BeaconRoot,
+ Parent: parentHash,
+ Timestamp: params.Timestamp,
+ FeeRecipient: params.SuggestedFeeRecipient,
+ Random: params.Random,
+ Withdrawals: params.Withdrawals,
+ BeaconRoot: params.BeaconRoot,
+ ProposerPubkey: params.ProposerPubkey,
}
payload, err := api.eth.Miner().BuildPayload(args, false)
if err != nil {
@@ -691,10 +696,10 @@ func TestEmptyBlocks(t *testing.T) {
api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil)
+ setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil, nil)
+ payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
status, err := api.NewPayloadV1(*payload)
if err != nil {
@@ -708,7 +713,7 @@ func TestEmptyBlocks(t *testing.T) {
}
// (2) Now send P1' which is invalid
- payload = getNewPayload(t, api, commonAncestor, nil, nil)
+ payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.GasUsed += 1
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
@@ -726,7 +731,7 @@ func TestEmptyBlocks(t *testing.T) {
}
// (3) Now send a payload with unknown parent
- payload = getNewPayload(t, api, commonAncestor, nil, nil)
+ payload = getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.ParentHash = common.Hash{1}
payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor
@@ -742,13 +747,14 @@ func TestEmptyBlocks(t *testing.T) {
}
}
-func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutionPayloadEnvelope {
+func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash, proposerPubkey *common.Pubkey) *engine.ExecutionPayloadEnvelope {
params := engine.PayloadAttributes{
Timestamp: parent.Time + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase,
Withdrawals: withdrawals,
BeaconRoot: beaconRoot,
+ ProposerPubkey: proposerPubkey,
}
envelope, err := assembleEnvelope(api, parent.Hash(), ¶ms)
@@ -758,8 +764,8 @@ func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withd
return envelope
}
-func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutableData {
- return getNewEnvelope(t, api, parent, withdrawals, beaconRoot).ExecutionPayload
+func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash, proposerPubkey *common.Pubkey) *engine.ExecutableData {
+ return getNewEnvelope(t, api, parent, withdrawals, beaconRoot, proposerPubkey).ExecutionPayload
}
// setBlockhash sets the blockhash of a modified ExecutableData.
@@ -820,7 +826,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
commonAncestor := ethserviceA.BlockChain().CurrentBlock()
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil)
+ setupBlocks(t, ethserviceA, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*engine.ExecutableData
@@ -829,7 +835,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
//invalidChain = append(invalidChain, payload1)
// create an invalid payload2 (P2)
- payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil)
+ payload2 := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
//payload2.ParentHash = payload1.BlockHash
payload2.GasUsed += 1
payload2 = setBlockhash(payload2)
@@ -838,7 +844,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
head := payload2
// create some valid payloads on top
for i := 0; i < 10; i++ {
- payload := getNewPayload(t, apiA, commonAncestor, nil, nil)
+ payload := getNewPayload(t, apiA, commonAncestor, nil, nil, nil)
payload.ParentHash = head.BlockHash
payload = setBlockhash(payload)
invalidChain = append(invalidChain, payload)
@@ -875,10 +881,10 @@ func TestInvalidBloom(t *testing.T) {
api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain
- setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil)
+ setupBlocks(t, ethservice, 10, commonAncestor, func(parent *types.Header) {}, nil, nil, nil)
// (1) check LatestValidHash by sending a normal payload (P1'')
- payload := getNewPayload(t, api, commonAncestor, nil, nil)
+ payload := getNewPayload(t, api, commonAncestor, nil, nil, nil)
payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload)
if err != nil {
@@ -935,7 +941,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
t.Fatal(testErr)
}
}
- block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil)
+ block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err)
}
@@ -1275,7 +1281,7 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) {
}
// Create the blocks.
- newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots)
+ newHeaders := setupBlocks(t, ethservice, 10, parent, callback, withdrawals, beaconRoots, nil)
newBlocks := make([]*types.Block, len(newHeaders))
for i, header := range newHeaders {
newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64())
@@ -1534,7 +1540,7 @@ func TestBlockToPayloadWithBlobs(t *testing.T) {
if got := len(envelope.BlobsBundle.Blobs); got != want {
t.Fatalf("invalid number of blobs: got %v, want %v", got, want)
}
- _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil)
+ _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil, nil, nil)
if err != nil {
t.Error(err)
}
diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go
index 0642d6a1ad..dd46fcd43d 100644
--- a/eth/catalyst/simulated_beacon.go
+++ b/eth/catalyst/simulated_beacon.go
@@ -100,6 +100,8 @@ type SimulatedBeacon struct {
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
switch config.LatestFork(time) {
+ case forks.Prague1:
+ return engine.PayloadV3P11
case forks.Prague, forks.Cancun:
return engine.PayloadV3
case forks.Paris, forks.Shanghai:
@@ -191,6 +193,10 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
}
version := payloadVersion(c.eth.BlockChain().Config(), timestamp)
+ var proposerPubkey *common.Pubkey
+ if version == engine.PayloadV3P11 {
+ proposerPubkey = &common.Pubkey{}
+ }
var random [32]byte
rand.Read(random[:])
@@ -200,6 +206,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Withdrawals: withdrawals,
Random: random,
BeaconRoot: &common.Hash{},
+ ProposerPubkey: proposerPubkey,
}, version, false)
if err != nil {
return err
@@ -256,7 +263,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
}
// Mark the payload as canon
- _, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false)
+ _, err = c.engineAPI.newPayload(*payload, blobHashes, beaconRoot, requests, false, proposerPubkey)
if err != nil {
return err
}
@@ -317,6 +324,7 @@ func (c *SimulatedBeacon) Commit() common.Hash {
withdrawals := c.withdrawals.pop(10)
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err)
+ panic(err)
}
return c.eth.BlockChain().CurrentBlock().Hash()
}
diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go
index 712539c5e3..4c5aedddbd 100644
--- a/eth/catalyst/witness.go
+++ b/eth/catalyst/witness.go
@@ -37,6 +37,8 @@ import (
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil {
switch {
+ case payloadAttributes.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai):
@@ -51,6 +53,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.Forkchoice
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
+ case params.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil:
@@ -69,6 +73,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.Forkchoice
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil {
switch {
+ case params.ProposerPubkey != nil:
+ return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
@@ -84,13 +90,35 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true)
}
+// ForkchoiceUpdatedWithWitnessV3P11 is analogous to ForkchoiceUpdatedV3P11, only it
+// generates an execution witness too if block building was requested.
+func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3P11(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
+ if params != nil {
+ switch {
+ case params.Withdrawals == nil:
+ return engine.STATUS_INVALID, attributesErr("missing withdrawals")
+ case params.BeaconRoot == nil:
+ return engine.STATUS_INVALID, attributesErr("missing beacon root")
+ case params.ProposerPubkey == nil:
+ return engine.STATUS_INVALID, attributesErr("missing proposer pubkey")
+ case !api.checkFork(params.Timestamp, forks.Prague1):
+ return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads")
+ }
+ }
+ // TODO(matt): the spec requires that fcu is applied when called on a valid
+ // hash, even if params are wrong. To do this we need to split up
+ // forkchoiceUpdate into a function that only updates the head and then a
+ // function that kicks off block construction.
+ return api.forkchoiceUpdated(update, params, engine.PayloadV3P11, true)
+}
+
// NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
// and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
}
- return api.newPayload(params, nil, nil, nil, true)
+ return api.newPayload(params, nil, nil, nil, true, nil)
}
// NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
@@ -112,7 +140,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun")
}
- return api.newPayload(params, nil, nil, nil, true)
+ return api.newPayload(params, nil, nil, nil, true, nil)
}
// NewPayloadWithWitnessV3 is analogous to NewPayloadV3, only it also generates
@@ -132,7 +160,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV3(params engine.ExecutableData, v
case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
}
- return api.newPayload(params, versionedHashes, beaconRoot, nil, true)
+ return api.newPayload(params, versionedHashes, beaconRoot, nil, true, nil)
}
// NewPayloadWithWitnessV4 is analogous to NewPayloadV4, only it also generates
@@ -158,7 +186,35 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v
if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
- return api.newPayload(params, versionedHashes, beaconRoot, requests, true)
+ return api.newPayload(params, versionedHashes, beaconRoot, requests, true, nil)
+}
+
+// NewPayloadWithWitnessV4P11 is analogous to NewPayloadV4P11, only it also generates
+// and returns a stateless witness after running the payload.
+func (api *ConsensusAPI) NewPayloadWithWitnessV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.PayloadStatusV1, error) {
+ switch {
+ case params.Withdrawals == nil:
+ return invalidStatus, paramsErr("nil withdrawals post-shanghai")
+ case params.ExcessBlobGas == nil:
+ return invalidStatus, paramsErr("nil excessBlobGas post-cancun")
+ case params.BlobGasUsed == nil:
+ return invalidStatus, paramsErr("nil blobGasUsed post-cancun")
+ case versionedHashes == nil:
+ return invalidStatus, paramsErr("nil versionedHashes post-cancun")
+ case beaconRoot == nil:
+ return invalidStatus, paramsErr("nil beaconRoot post-cancun")
+ case executionRequests == nil:
+ return invalidStatus, paramsErr("nil executionRequests post-prague")
+ case proposerPubkey == nil:
+ return invalidStatus, paramsErr("nil proposerPubkey post-prague1")
+ case !api.checkFork(params.Timestamp, forks.Prague1):
+ return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads")
+ }
+ requests := convertRequests(executionRequests)
+ if err := validateRequests(requests); err != nil {
+ return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
+ }
+ return api.newPayload(params, versionedHashes, beaconRoot, requests, true, proposerPubkey)
}
// ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in
@@ -167,7 +223,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData,
if params.Withdrawals != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1"))
}
- return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness)
+ return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness, nil)
}
// ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
@@ -189,7 +245,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData,
case params.BlobGasUsed != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun")
}
- return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness)
+ return api.executeStatelessPayload(params, nil, nil, nil, opaqueWitness, nil)
}
// ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
@@ -207,9 +263,9 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData,
case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.Cancun):
- return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV3 must only be called for cancun payloads")
}
- return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness)
+ return api.executeStatelessPayload(params, versionedHashes, beaconRoot, nil, opaqueWitness, nil)
}
// ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
@@ -229,18 +285,46 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData,
case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague):
- return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads")
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV4 must only be called for prague payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
}
- return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness)
+ return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness, nil)
}
-func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, opaqueWitness hexutil.Bytes) (engine.StatelessPayloadStatusV1, error) {
+// ExecuteStatelessPayloadV4P11 is analogous to NewPayloadV4P11, only it operates in
+// a stateless mode on top of a provided witness instead of the local database.
+func (api *ConsensusAPI) ExecuteStatelessPayloadV4P11(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, executionRequests []hexutil.Bytes, opaqueWitness hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.StatelessPayloadStatusV1, error) {
+ switch {
+ case params.Withdrawals == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil withdrawals post-shanghai")
+ case params.ExcessBlobGas == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil excessBlobGas post-cancun")
+ case params.BlobGasUsed == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil blobGasUsed post-cancun")
+ case versionedHashes == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil versionedHashes post-cancun")
+ case beaconRoot == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
+ case executionRequests == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
+ case proposerPubkey == nil:
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil proposerPubkey post-prague1")
+ case !api.checkFork(params.Timestamp, forks.Prague1):
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV4P11 must only be called for prague1 payloads")
+ }
+ requests := convertRequests(executionRequests)
+ if err := validateRequests(requests); err != nil {
+ return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err)
+ }
+ return api.executeStatelessPayload(params, versionedHashes, beaconRoot, requests, opaqueWitness, proposerPubkey)
+}
+
+func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, opaqueWitness hexutil.Bytes, proposerPubkey *common.Pubkey) (engine.StatelessPayloadStatusV1, error) {
log.Trace("Engine API request received", "method", "ExecuteStatelessPayload", "number", params.Number, "hash", params.BlockHash)
- block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, requests)
+ block, err := engine.ExecutableDataToBlockNoHash(params, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil {
bgu := "nil"
if params.BlobGasUsed != nil {
@@ -268,6 +352,7 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v
"len(params.Transactions)", len(params.Transactions),
"len(params.Withdrawals)", len(params.Withdrawals),
"beaconRoot", beaconRoot,
+ "parentProposerPubkey", proposerPubkey,
"len(requests)", len(requests),
"error", err)
errorMsg := err.Error()
diff --git a/eth/tracers/api.go b/eth/tracers/api.go
index 6962023343..c13a8ad3ad 100644
--- a/eth/tracers/api.go
+++ b/eth/tracers/api.go
@@ -971,9 +971,11 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
return nil, err
}
var (
- msg = args.ToMessage(vmctx.BaseFee, true, true)
- tx = args.ToTransaction(types.LegacyTxType)
- traceConfig *TraceConfig
+ isPrague1 = api.backend.ChainConfig().IsPrague1(vmctx.BlockNumber, vmctx.Time)
+ distributorAddress = api.backend.ChainConfig().Berachain.Prague1.PoLDistributorAddress
+ msg = args.ToMessage(vmctx.BaseFee, true, true, isPrague1, distributorAddress)
+ tx = args.ToTransaction(types.LegacyTxType, isPrague1, distributorAddress)
+ traceConfig *TraceConfig
)
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).
diff --git a/graphql/graphql.go b/graphql/graphql.go
index 0b2a77a3c4..6105f7a543 100644
--- a/graphql/graphql.go
+++ b/graphql/graphql.go
@@ -318,7 +318,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big {
return nil
}
switch tx.Type() {
- case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
+ case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasFeeCap())
default:
return nil
@@ -331,7 +331,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big {
return nil
}
switch tx.Type() {
- case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
+ case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasTipCap())
default:
return nil
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 21c35b7665..4fdc7e2b9b 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -696,7 +696,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s
if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
return nil, err
}
- msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks)
+ msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks, b.ChainConfig().IsPrague1(header.Number, header.Time), b.ChainConfig().Berachain.Prague1.PoLDistributorAddress)
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 {
@@ -839,7 +839,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err
}
- call := args.ToMessage(header.BaseFee, true, true)
+ call := args.ToMessage(header.BaseFee, true, true, b.ChainConfig().IsPrague1(header.Number, header.Time), b.ChainConfig().Berachain.Prague1.PoLDistributorAddress)
// Run the gas estimation and wrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap)
@@ -904,6 +904,9 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} {
if head.RequestsHash != nil {
result["requestsHash"] = head.RequestsHash
}
+ if head.ParentProposerPubkey != nil {
+ result["parentProposerPubkey"] = head.ParentProposerPubkey
+ }
return result
}
@@ -1057,6 +1060,14 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
}
result.AuthorizationList = tx.SetCodeAuthorizations()
+
+ case types.PoLTxType:
+ al := tx.AccessList()
+ result.Accesses = &al
+ result.ChainID = (*hexutil.Big)(tx.ChainId())
+ result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
+ result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
+ result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
}
return result
}
@@ -1217,7 +1228,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
- msg := args.ToMessage(header.BaseFee, true, true)
+ isPrague1 := b.ChainConfig().IsPrague1(header.Number, header.Time)
+ distributorAddress := b.ChainConfig().Berachain.Prague1.PoLDistributorAddress
+ msg := args.ToMessage(header.BaseFee, true, true, isPrague1, distributorAddress)
// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, addressesToExclude)
@@ -1234,7 +1247,15 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
}
res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil {
- return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.ToTransaction(types.LegacyTxType).Hash(), err)
+ return nil, 0, nil, fmt.Errorf(
+ "failed to apply transaction: %v err: %v",
+ args.ToTransaction(
+ types.LegacyTxType,
+ b.ChainConfig().IsPrague1(header.Number, header.Time),
+ b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ ).Hash(),
+ err,
+ )
}
if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil
@@ -1503,7 +1524,13 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
- tx := args.ToTransaction(types.LegacyTxType)
+ header := api.b.CurrentHeader()
+
+ tx := args.ToTransaction(
+ types.LegacyTxType,
+ api.b.ChainConfig().IsPrague1(header.Number, header.Time),
+ api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ )
signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
if err != nil {
@@ -1523,7 +1550,12 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
return nil, err
}
// Assemble the transaction and obtain rlp
- tx := args.ToTransaction(types.LegacyTxType)
+ header := api.b.CurrentHeader()
+ tx := args.ToTransaction(
+ types.LegacyTxType,
+ api.b.ChainConfig().IsPrague1(header.Number, header.Time),
+ api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ )
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
@@ -1591,7 +1623,12 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
return nil, err
}
// Before actually sign the transaction, ensure the transaction fee is reasonable.
- tx := args.ToTransaction(types.LegacyTxType)
+ header := api.b.CurrentHeader()
+ tx := args.ToTransaction(
+ types.LegacyTxType,
+ api.b.ChainConfig().IsPrague1(header.Number, header.Time),
+ api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ )
if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
return nil, err
}
@@ -1649,7 +1686,12 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
return common.Hash{}, err
}
- matchTx := sendArgs.ToTransaction(types.LegacyTxType)
+ header := api.b.CurrentHeader()
+ matchTx := sendArgs.ToTransaction(
+ types.LegacyTxType,
+ api.b.ChainConfig().IsPrague1(header.Number, header.Time),
+ api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ )
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
price := matchTx.GasPrice()
@@ -1679,7 +1721,14 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit
}
- signedTx, err := api.sign(sendArgs.from(), sendArgs.ToTransaction(types.LegacyTxType))
+ header := api.b.CurrentHeader()
+ signedTx, err := api.sign(
+ sendArgs.from(), sendArgs.ToTransaction(
+ types.LegacyTxType,
+ api.b.ChainConfig().IsPrague1(header.Number, header.Time),
+ api.b.ChainConfig().Berachain.Prague1.PoLDistributorAddress,
+ ),
+ )
if err != nil {
return common.Hash{}, err
}
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
index 1c9be836fc..f9340eae14 100644
--- a/internal/ethapi/api_test.go
+++ b/internal/ethapi/api_test.go
@@ -1217,6 +1217,15 @@ func TestCall(t *testing.T) {
},
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`),
},
+ {
+ name: "unsupported block override proposerPubkey",
+ blockNumber: rpc.LatestBlockNumber,
+ call: TransactionArgs{},
+ blockOverrides: override.BlockOverrides{
+ ProposerPubkey: &common.Pubkey{},
+ },
+ expectErr: errors.New(`block override "proposerPubkey" is not supported for this RPC method`),
+ },
}
for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)
diff --git a/internal/ethapi/override/override.go b/internal/ethapi/override/override.go
index 0bcf3c444d..70449c1644 100644
--- a/internal/ethapi/override/override.go
+++ b/internal/ethapi/override/override.go
@@ -121,16 +121,17 @@ func (diff *StateOverride) Apply(statedb *state.StateDB, precompiles vm.Precompi
// BlockOverrides is a set of header fields to override.
type BlockOverrides struct {
- Number *hexutil.Big
- Difficulty *hexutil.Big // No-op if we're simulating post-merge calls.
- Time *hexutil.Uint64
- GasLimit *hexutil.Uint64
- FeeRecipient *common.Address
- PrevRandao *common.Hash
- BaseFeePerGas *hexutil.Big
- BlobBaseFee *hexutil.Big
- BeaconRoot *common.Hash
- Withdrawals *types.Withdrawals
+ Number *hexutil.Big
+ Difficulty *hexutil.Big // No-op if we're simulating post-merge calls.
+ Time *hexutil.Uint64
+ GasLimit *hexutil.Uint64
+ FeeRecipient *common.Address
+ PrevRandao *common.Hash
+ BaseFeePerGas *hexutil.Big
+ BlobBaseFee *hexutil.Big
+ BeaconRoot *common.Hash
+ Withdrawals *types.Withdrawals
+ ProposerPubkey *common.Pubkey
}
// Apply overrides the given header fields into the given block context.
@@ -144,6 +145,9 @@ func (o *BlockOverrides) Apply(blockCtx *vm.BlockContext) error {
if o.Withdrawals != nil {
return errors.New(`block override "withdrawals" is not supported for this RPC method`)
}
+ if o.ProposerPubkey != nil {
+ return errors.New(`block override "proposerPubkey" is not supported for this RPC method`)
+ }
if o.Number != nil {
blockCtx.BlockNumber = o.Number.ToInt()
}
diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go
index 362536bcb5..7f0f6537a7 100644
--- a/internal/ethapi/simulate.go
+++ b/internal/ethapi/simulate.go
@@ -199,6 +199,37 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
if err != nil {
return nil, err
}
+
+ // Berachain: Pre-compute expected PoL tx hash when in Prague1.
+ isPrague1 := sim.chainConfig.IsPrague1(result.Number(), result.Time())
+ var expectedPoLHash common.Hash
+ if isPrague1 {
+ polTx, err := types.NewPoLTx(
+ sim.chainConfig.ChainID,
+ sim.chainConfig.Berachain.Prague1.PoLDistributorAddress,
+ new(big.Int).Sub(result.Number(), big.NewInt(1)),
+ params.PoLTxGasLimit,
+ result.BaseFee(),
+ result.ProposerPubkey(),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to create expected PoL tx: %w", err)
+ }
+ expectedPoLHash = polTx.Hash()
+ }
+
+ // Berachain: validate the PoL tx is only the first tx in the block.
+ for i, tx := range result.Transactions() {
+ switch {
+ case isPrague1 && i == 0:
+ if tx.Hash() != expectedPoLHash {
+ return nil, fmt.Errorf("PoL tx hash mismatch: have %v, want %v", tx.Hash(), expectedPoLHash)
+ }
+ case tx.Type() == types.PoLTxType:
+ return nil, fmt.Errorf("invalid block: tx at index %d is a PoL tx", i)
+ }
+ }
+
headers[bi] = result.Header()
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
parent = result.Header()
@@ -279,15 +310,17 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
return nil, nil, nil, err
}
var (
- tx = call.ToTransaction(types.DynamicFeeTxType)
- txHash = tx.Hash()
+ isPrague1 = sim.chainConfig.IsPrague1(header.Number, header.Time)
+ distributorAddress = sim.chainConfig.Berachain.Prague1.PoLDistributorAddress
+ tx = call.ToTransaction(types.DynamicFeeTxType, isPrague1, distributorAddress)
+ txHash = tx.Hash()
)
txes[i] = tx
senders[txHash] = call.from()
tracer.reset(txHash, uint(i))
sim.state.SetTxContext(txHash, i)
// EoA check is always skipped, even in validation mode.
- msg := call.ToMessage(header.BaseFee, !sim.validate, true)
+ msg := call.ToMessage(header.BaseFee, !sim.validate, true, isPrague1, distributorAddress)
result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil {
txErr := txValidationError(err)
@@ -484,15 +517,24 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
parentBeaconRoot = overrides.BeaconRoot
}
}
+ var parentProposerPubkey *common.Pubkey
+ if sim.chainConfig.IsPrague1(overrides.Number.ToInt(), (uint64)(*overrides.Time)) {
+ parentProposerPubkey = new(common.Pubkey)
+ if overrides.ProposerPubkey == nil {
+ return nil, errors.New("proposer pubkey override is required for post Prague1 blocks")
+ }
+ *parentProposerPubkey = *overrides.ProposerPubkey
+ }
header = overrides.MakeHeader(&types.Header{
- UncleHash: types.EmptyUncleHash,
- ReceiptHash: types.EmptyReceiptsHash,
- TxHash: types.EmptyTxsHash,
- Coinbase: header.Coinbase,
- Difficulty: header.Difficulty,
- GasLimit: header.GasLimit,
- WithdrawalsHash: withdrawalsHash,
- ParentBeaconRoot: parentBeaconRoot,
+ UncleHash: types.EmptyUncleHash,
+ ReceiptHash: types.EmptyReceiptsHash,
+ TxHash: types.EmptyTxsHash,
+ Coinbase: header.Coinbase,
+ Difficulty: header.Difficulty,
+ GasLimit: header.GasLimit,
+ WithdrawalsHash: withdrawalsHash,
+ ParentBeaconRoot: parentBeaconRoot,
+ ParentProposerPubkey: parentProposerPubkey,
})
res[bi] = header
}
diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go
index 7a7d63c535..cb6ec6fdb7 100644
--- a/internal/ethapi/transaction_args.go
+++ b/internal/ethapi/transaction_args.go
@@ -414,7 +414,7 @@ func (args *TransactionArgs) CallDefaults(globalGasCap uint64, baseFee *big.Int,
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called.
-func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck bool) *core.Message {
+func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoACheck, isPrague1 bool, distributorAddress common.Address) *core.Message {
var (
gasPrice *big.Int
gasFeeCap *big.Int
@@ -463,14 +463,17 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA
SetCodeAuthorizations: args.AuthorizationList,
SkipNonceChecks: skipNonceCheck,
SkipFromEOACheck: skipEoACheck,
+ IsPoLTx: isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress),
}
}
// ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called.
-func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
+func (args *TransactionArgs) ToTransaction(defaultType int, isPrague1 bool, distributorAddress common.Address) *types.Transaction {
usedType := types.LegacyTxType
switch {
+ case isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress):
+ usedType = types.PoLTxType
case args.AuthorizationList != nil || defaultType == types.SetCodeTxType:
usedType = types.SetCodeTxType
case args.BlobHashes != nil || defaultType == types.BlobTxType:
@@ -486,6 +489,20 @@ func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
}
var data types.TxData
switch usedType {
+ case types.PoLTxType:
+ gasPrice := (*big.Int)(args.GasPrice)
+ if args.MaxFeePerGas != nil || defaultType == types.DynamicFeeTxType {
+ gasPrice = (*big.Int)(args.MaxFeePerGas)
+ }
+ data = &types.PoLTx{
+ ChainID: (*big.Int)(args.ChainID),
+ From: args.from(),
+ To: *args.To,
+ Nonce: uint64(*args.Nonce),
+ GasLimit: uint64(*args.Gas),
+ GasPrice: gasPrice,
+ Data: args.data(),
+ }
case types.SetCodeTxType:
al := types.AccessList{}
if args.AccessList != nil {
diff --git a/miner/miner.go b/miner/miner.go
index ddfe0dcf26..cffb5b8f21 100644
--- a/miner/miner.go
+++ b/miner/miner.go
@@ -140,6 +140,11 @@ func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// getPending retrieves the pending block based on the current head block.
// The result might be nil if pending generation is failed.
+//
+// Berachain: Note that the correct PoL tx is not included in the pending block as the
+// parent proposer pubkey of the pending block is not made available here.
+//
+// TODO(BRIP-4): Include parent proposer pubkey as soon as it is made available by CL.
func (miner *Miner) getPending() *newPayloadResult {
header := miner.chain.CurrentHeader()
miner.pendingMu.Lock()
@@ -156,14 +161,15 @@ func (miner *Miner) getPending() *newPayloadResult {
withdrawal = []*types.Withdrawal{}
}
ret := miner.generateWork(&generateParams{
- timestamp: timestamp,
- forceTime: false,
- parentHash: header.Hash(),
- coinbase: miner.config.PendingFeeRecipient,
- random: common.Hash{},
- withdrawals: withdrawal,
- beaconRoot: nil,
- noTxs: false,
+ timestamp: timestamp,
+ forceTime: false,
+ parentHash: header.Hash(),
+ coinbase: miner.config.PendingFeeRecipient,
+ random: common.Hash{},
+ withdrawals: withdrawal,
+ beaconRoot: nil,
+ proposerPubkey: &common.Pubkey{},
+ noTxs: false,
}, false) // we will never make a witness for a pending block
if ret.err != nil {
return nil
diff --git a/miner/payload_building.go b/miner/payload_building.go
index 6b010186bf..20a47d8959 100644
--- a/miner/payload_building.go
+++ b/miner/payload_building.go
@@ -37,13 +37,14 @@ import (
// Check engine-api specification for more details.
// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3
type BuildPayloadArgs struct {
- Parent common.Hash // The parent block to build payload on top
- Timestamp uint64 // The provided timestamp of generated payload
- FeeRecipient common.Address // The provided recipient address for collecting transaction fee
- Random common.Hash // The provided randomness value
- Withdrawals types.Withdrawals // The provided withdrawals
- BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
- Version engine.PayloadVersion // Versioning byte for payload id calculation.
+ Parent common.Hash // The parent block to build payload on top
+ Timestamp uint64 // The provided timestamp of generated payload
+ FeeRecipient common.Address // The provided recipient address for collecting transaction fee
+ Random common.Hash // The provided randomness value
+ Withdrawals types.Withdrawals // The provided withdrawals
+ BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
+ ProposerPubkey *common.Pubkey // Berachain BRIP-0004: The provided proposer pubkey (Prague1)
+ Version engine.PayloadVersion // Versioning byte for payload id calculation.
}
// Id computes an 8-byte identifier by hashing the components of the payload arguments.
@@ -57,6 +58,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:])
}
+ if args.ProposerPubkey != nil {
+ hasher.Write(args.ProposerPubkey[:])
+ }
var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8])
out[0] = byte(args.Version)
@@ -211,14 +215,15 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// enough to run. The empty payload can at least make sure there is something
// to deliver for not missing slot.
emptyParams := &generateParams{
- timestamp: args.Timestamp,
- forceTime: true,
- parentHash: args.Parent,
- coinbase: args.FeeRecipient,
- random: args.Random,
- withdrawals: args.Withdrawals,
- beaconRoot: args.BeaconRoot,
- noTxs: true,
+ timestamp: args.Timestamp,
+ forceTime: true,
+ parentHash: args.Parent,
+ coinbase: args.FeeRecipient,
+ random: args.Random,
+ withdrawals: args.Withdrawals,
+ beaconRoot: args.BeaconRoot,
+ proposerPubkey: args.ProposerPubkey,
+ noTxs: true,
}
empty := miner.generateWork(emptyParams, witness)
if empty.err != nil {
@@ -235,20 +240,24 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
timer := time.NewTimer(0)
defer timer.Stop()
- // Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in
- // the Mainnet configuration) have passed since the point in time identified
+ // Setup the timer for terminating the process if SECONDS_PER_SLOT (2s in
+ // the Berachain configuration) have passed since the point in time identified
// by the timestamp parameter.
- endTimer := time.NewTimer(time.Second * 12)
+ //
+ // TODO(Berachain): Using 4s for now to handle cases where block time exceeds 2s. To be
+ // verified further after stable block time is in effect.
+ endTimer := time.NewTimer(time.Second * 4)
fullParams := &generateParams{
- timestamp: args.Timestamp,
- forceTime: true,
- parentHash: args.Parent,
- coinbase: args.FeeRecipient,
- random: args.Random,
- withdrawals: args.Withdrawals,
- beaconRoot: args.BeaconRoot,
- noTxs: false,
+ timestamp: args.Timestamp,
+ forceTime: true,
+ parentHash: args.Parent,
+ coinbase: args.FeeRecipient,
+ random: args.Random,
+ withdrawals: args.Withdrawals,
+ beaconRoot: args.BeaconRoot,
+ proposerPubkey: args.ProposerPubkey,
+ noTxs: false,
}
for {
diff --git a/miner/worker.go b/miner/worker.go
index 198745ad27..cab6eed3f9 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -84,14 +84,15 @@ type newPayloadResult struct {
// generateParams wraps various settings for generating sealing task.
type generateParams struct {
- timestamp uint64 // The timestamp for sealing task
- forceTime bool // Flag whether the given timestamp is immutable or not
- parentHash common.Hash // Parent block hash, empty means the latest chain head
- coinbase common.Address // The fee recipient address for including transaction
- random common.Hash // The randomness generated by beacon chain, empty before the merge
- withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
- beaconRoot *common.Hash // The beacon root (cancun field).
- noTxs bool // Flag whether an empty block without any transaction is expected
+ timestamp uint64 // The timestamp for sealing task
+ forceTime bool // Flag whether the given timestamp is immutable or not
+ parentHash common.Hash // Parent block hash, empty means the latest chain head
+ coinbase common.Address // The fee recipient address for including transaction
+ random common.Hash // The randomness generated by beacon chain, empty before the merge
+ withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
+ beaconRoot *common.Hash // The beacon root (cancun field).
+ proposerPubkey *common.Pubkey // Berachain: The proposer pubkey (prague1 field).
+ noTxs bool // Flag whether an empty block without any transaction is expected
}
// generateWork generates a sealing block based on the given parameters.
@@ -221,6 +222,10 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot
}
+ // Berachain: Apply BRIP-0004.
+ if miner.chainConfig.IsPrague1(header.Number, header.Time) {
+ header.ParentProposerPubkey = genParams.proposerPubkey
+ }
// Could potentially happen if starting to mine in an odd state.
// Note genParams.coinbase can be different with header.Coinbase
// since clique algorithm can modify the coinbase field in header.
@@ -306,13 +311,23 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
var (
- snap = env.state.Snapshot()
- gp = env.gasPool.Gas()
+ gasPool = env.gasPool
+ blockGasUsed = &env.header.GasUsed
+ snap = env.state.Snapshot()
+ gasBefore uint64
)
- receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.header.GasUsed)
+ // Berachain: PoL tx does not consume gas.
+ if tx.Type() == types.PoLTxType {
+ gasPool = nil
+ blockGasUsed = new(uint64)
+ }
+ if gasPool != nil {
+ gasBefore = gasPool.Gas()
+ }
+ receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, blockGasUsed)
if err != nil {
env.state.RevertToSnapshot(snap)
- env.gasPool.SetGas(gp)
+ env.gasPool.SetGas(gasBefore)
}
return receipt, err
}
@@ -452,6 +467,29 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
prio := miner.prio
miner.confMu.RUnlock()
+ // Berachain: Post-Prague1, add PoL tx to the block according to BRIP-0004.
+ if miner.chainConfig.IsPrague1(env.header.Number, env.header.Time) {
+ tx, err := types.NewPoLTx(
+ miner.chainConfig.ChainID,
+ miner.chainConfig.Berachain.Prague1.PoLDistributorAddress,
+ new(big.Int).Sub(env.header.Number, big.NewInt(1)),
+ params.PoLTxGasLimit,
+ env.header.BaseFee,
+ env.header.ParentProposerPubkey,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create PoL tx: %v", err)
+ }
+
+ // Start executing the PoL transaction, outside of miner commitTransactions loop.
+ // env.tcount must be at 0 at this point.
+ env.state.SetTxContext(tx.Hash(), env.tcount)
+ if err = miner.commitTransaction(env, tx); err != nil {
+ log.Trace("Failed to commit PoL transaction", "hash", tx.Hash(), "err", err)
+ return err
+ }
+ }
+
// Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees
filter := txpool.PendingFilter{
MinTip: uint256.MustFromBig(tip),
diff --git a/params/config.go b/params/config.go
index 9f80fabae3..b5959f2833 100644
--- a/params/config.go
+++ b/params/config.go
@@ -548,6 +548,8 @@ type Prague1Config struct {
BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator,omitempty"`
// MinimumBaseFeeWei is the minimum base fee in wei.
MinimumBaseFeeWei uint64 `json:"minimumBaseFeeWei,omitempty"`
+ // PoLDistributorAddress is the address of the PoL distributor.
+ PoLDistributorAddress common.Address `json:"polDistributorAddress,omitempty"`
}
// String implements the stringer interface.
@@ -555,8 +557,8 @@ func (c Prague1Config) String() string {
banner := "prague1"
if c.Time != nil {
banner += fmt.Sprintf(
- "(time: %v, baseFeeChangeDenominator: %v, minimumBaseFeeWei: %v)",
- *c.Time, c.BaseFeeChangeDenominator, c.MinimumBaseFeeWei,
+ "(time: %v, baseFeeChangeDenominator: %v, minimumBaseFeeWei: %v, polDistributorAddress: %v)",
+ *c.Time, c.BaseFeeChangeDenominator, c.MinimumBaseFeeWei, c.PoLDistributorAddress,
)
}
return banner
@@ -1042,6 +1044,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
switch {
case c.IsOsaka(london, time):
return forks.Osaka
+ case c.IsPrague1(london, time):
+ return forks.Prague1
case c.IsPrague(london, time):
return forks.Prague
case c.IsCancun(london, time):
@@ -1059,6 +1063,8 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
switch {
case fork == forks.Osaka:
return c.OsakaTime
+ case fork == forks.Prague1:
+ return c.Berachain.Prague1.Time
case fork == forks.Prague:
return c.PragueTime
case fork == forks.Cancun:
@@ -1205,13 +1211,13 @@ func (err *ConfigCompatError) Error() string {
// Rules is a one time interface meaning that it shouldn't be used in between transition
// phases.
type Rules struct {
- ChainID *big.Int
- IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
- IsEIP2929, IsEIP4762 bool
- IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
- IsBerlin, IsLondon bool
- IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool
- IsVerkle bool
+ ChainID *big.Int
+ IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
+ IsEIP2929, IsEIP4762 bool
+ IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
+ IsBerlin, IsLondon bool
+ IsMerge, IsShanghai, IsCancun, IsPrague, IsPrague1, IsOsaka bool
+ IsVerkle bool
}
// Rules ensures c's ChainID is not nil.
@@ -1240,6 +1246,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsShanghai: isMerge && c.IsShanghai(num, timestamp),
IsCancun: isMerge && c.IsCancun(num, timestamp),
IsPrague: isMerge && c.IsPrague(num, timestamp),
+ IsPrague1: isMerge && c.IsPrague1(num, timestamp),
IsOsaka: isMerge && c.IsOsaka(num, timestamp),
IsVerkle: isVerkle,
IsEIP4762: isVerkle,
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 6b06dadaef..b8d2bfc357 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -193,7 +193,7 @@ var (
// System contracts.
var (
- // SystemAddress is where the system-transaction is sent from as per EIP-4788
+ // SystemAddress is where the system-transaction is sent from as per EIP-4788 and BRIP-0004.
SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// EIP-4788 - Beacon block root in the EVM
@@ -211,4 +211,7 @@ var (
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
+
+ // BRIP-0004 - PoL Distributor
+ PoLTxGasLimit uint64 = 30_000_000
)
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index a76037405b..1ac361a6fb 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -97,6 +97,7 @@ type btHeader struct {
BlobGasUsed *uint64
ExcessBlobGas *uint64
ParentBeaconBlockRoot *common.Hash
+ ParentProposerPubkey *common.Pubkey
}
type btHeaderMarshaling struct {
@@ -336,6 +337,9 @@ func validateHeader(h *btHeader, h2 *types.Header) error {
if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) {
return fmt.Errorf("parentBeaconBlockRoot: want: %v have: %v", h.ParentBeaconBlockRoot, h2.ParentBeaconRoot)
}
+ if !reflect.DeepEqual(h.ParentProposerPubkey, h2.ParentProposerPubkey) {
+ return fmt.Errorf("parentProposerPubkey: want: %v have: %v", h.ParentProposerPubkey, h2.ParentProposerPubkey)
+ }
return nil
}
diff --git a/tests/gen_btheader.go b/tests/gen_btheader.go
index 80ad89e03b..339d2d1e08 100644
--- a/tests/gen_btheader.go
+++ b/tests/gen_btheader.go
@@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash
+ ParentProposerPubkey *common.Pubkey
}
var enc btHeader
enc.Bloom = b.Bloom
@@ -61,6 +62,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
+ enc.ParentProposerPubkey = b.ParentProposerPubkey
return json.Marshal(&enc)
}
@@ -88,6 +90,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash
+ ParentProposerPubkey *common.Pubkey
}
var dec btHeader
if err := json.Unmarshal(input, &dec); err != nil {
@@ -156,5 +159,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil {
b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
}
+ if dec.ParentProposerPubkey != nil {
+ b.ParentProposerPubkey = dec.ParentProposerPubkey
+ }
return nil
}