feat: BRIP-0004 implementation (#21)

* wip

* more

* prague1 engineapi

* nits

* undo

* prague1 tx signer for pol tx

* more

* tx pol hash

* switch order

* use 0x7D

* PoL Tx processing

* nit

* wip

* wip

* setup

* process pol msg

* fixes

* todos

* nit

* tx args

* proper prague1 block validation

* cleanup

* linter

* move validation to BlockValidator

* To is not nillable

* tx type sorted out

* reject from top level mempool

* unit tests

* working

* set tx gas price to be block's base fee

* tx pol

* new Pectra11 engine API endpoints

* handle fork prague1 versions

* fix catalyst/api tests

* fix TestGeneratePOSChain

* remove some TODOs

* Address review comments

* use type check for critical path validation
This commit is contained in:
Cal Bera 2025-07-21 20:28:27 -07:00 committed by GitHub
parent 7489548fa7
commit e6517f4870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1336 additions and 250 deletions

3
.gitignore vendored
View file

@ -56,3 +56,6 @@ cmd/evm/evm
cmd/geth/geth cmd/geth/geth
cmd/rlpdump/rlpdump cmd/rlpdump/rlpdump
cmd/workload/workload cmd/workload/workload
# claude
.claude

View file

@ -101,6 +101,17 @@ func (ec *engineClient) callNewPayload(fork string, event types.ChainHeadEvent)
params = []any{execData} params = []any{execData}
) )
switch fork { 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": case "electra":
method = "engine_newPayloadV4" method = "engine_newPayloadV4"
parentBeaconRoot := event.BeaconHead.ParentRoot parentBeaconRoot := event.BeaconHead.ParentRoot
@ -145,6 +156,8 @@ func (ec *engineClient) callForkchoiceUpdated(fork string, event types.ChainHead
var method string var method string
switch fork { switch fork {
case "electra1":
method = "engine_forkchoiceUpdatedV3P11"
case "deneb", "electra": case "deneb", "electra":
method = "engine_forkchoiceUpdatedV3" method = "engine_forkchoiceUpdatedV3"
case "capella": case "capella":

View file

@ -21,6 +21,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +29,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot enc.BeaconRoot = p.BeaconRoot
enc.ProposerPubkey = p.ProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -39,6 +41,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +65,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil { if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot p.BeaconRoot = dec.BeaconRoot
} }
if dec.ProposerPubkey != nil {
p.ProposerPubkey = dec.ProposerPubkey
}
return nil return nil
} }

View file

@ -36,6 +36,7 @@ var (
PayloadV1 PayloadVersion = 0x1 PayloadV1 PayloadVersion = 0x1
PayloadV2 PayloadVersion = 0x2 PayloadV2 PayloadVersion = 0x2
PayloadV3 PayloadVersion = 0x3 PayloadV3 PayloadVersion = 0x3
PayloadV3P11 PayloadVersion = 0x31
) )
//go:generate go run github.com/fjl/gencodec -type PayloadAttributes -field-override payloadAttributesMarshaling -out gen_blockparams.go //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"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
ProposerPubkey *common.Pubkey `json:"parentProposerPubkey"` // Berachain: only used prague1 and onwards
} }
// JSON type overrides for PayloadAttributes. // 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 // and that the blockhash of the constructed block matches the parameters. Nil
// Withdrawals value will propagate through the returned block. Empty // Withdrawals value will propagate through the returned block. Empty
// Withdrawals value must be passed via non-nil, length 0 value in data. // 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) { 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) block, err := ExecutableDataToBlockNoHash(data, versionedHashes, beaconRoot, requests, proposerPubkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -232,7 +234,7 @@ func ExecutableDataToBlock(data ExecutableData, versionedHashes []common.Hash, b
// ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used // ExecutableDataToBlockNoHash is analogous to ExecutableDataToBlock, but is used
// for stateless execution, so it skips checking if the executable data hashes to // 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). // 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) txs, err := decodeTransactions(data.Transactions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -295,6 +297,7 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
BlobGasUsed: data.BlobGasUsed, BlobGasUsed: data.BlobGasUsed,
ParentBeaconRoot: beaconRoot, ParentBeaconRoot: beaconRoot,
RequestsHash: requestsHash, RequestsHash: requestsHash,
ParentProposerPubkey: proposerPubkey,
} }
return types.NewBlockWithHeader(header). return types.NewBlockWithHeader(header).
WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}). WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}).

View file

@ -56,6 +56,7 @@ type header struct {
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
type headerMarshaling struct { type headerMarshaling struct {
@ -136,6 +137,7 @@ func (i *bbInput) ToBlock() *types.Block {
BlobGasUsed: i.Header.BlobGasUsed, BlobGasUsed: i.Header.BlobGasUsed,
ExcessBlobGas: i.Header.ExcessBlobGas, ExcessBlobGas: i.Header.ExcessBlobGas,
ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, ParentBeaconRoot: i.Header.ParentBeaconBlockRoot,
ParentProposerPubkey: i.Header.ParentProposerPubkey,
} }
// Fill optional values. // Fill optional values.

View file

@ -101,6 +101,7 @@ type stEnv struct {
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
type stEnvMarshaling struct { type stEnvMarshaling struct {
@ -220,6 +221,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
) )
core.ProcessParentBlockHash(prevHash, evm) core.ProcessParentBlockHash(prevHash, evm)
} }
// TODO(BRIP-4): Validate Prague1 block rules.
for i := 0; txIt.Next(); i++ { for i := 0; txIt.Next(); i++ {
tx, err := txIt.Tx() tx, err := txIt.Tx()
if err != nil { if err != nil {

View file

@ -38,6 +38,7 @@ func (h header) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var enc header var enc header
enc.ParentHash = h.ParentHash enc.ParentHash = h.ParentHash
@ -60,6 +61,7 @@ func (h header) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas)
enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot
enc.ParentProposerPubkey = h.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -86,6 +88,7 @@ func (h *header) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"`
ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var dec header var dec header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -155,5 +158,8 @@ func (h *header) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
h.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -37,6 +37,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var enc stEnv var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase) enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@ -59,6 +60,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot
enc.ParentProposerPubkey = s.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -85,6 +87,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey"`
} }
var dec stEnv var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -154,5 +157,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
s.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -150,6 +150,9 @@ func Transition(ctx *cli.Context) error {
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return err return err
} }
if err := applyPrague1Checks(&prestate.Env, chainConfig); err != nil {
return err
}
// Configure tracer // Configure tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
@ -265,6 +268,19 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil 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 type Alloc map[common.Address]types.Account
func (g Alloc) OnRoot(common.Hash) {} func (g Alloc) OnRoot(common.Hash) {}

View file

@ -286,7 +286,7 @@ func prepare(ctx *cli.Context) {
// If we're running a known preset, log it for convenience. // If we're running a known preset, log it for convenience.
switch { switch {
case ctx.IsSet(utils.MainnetFlag.Name): 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): case ctx.IsSet(utils.SepoliaFlag.Name):
log.Info("Starting Geth on Sepolia testnet...") log.Info("Starting Geth on Sepolia testnet...")

View file

@ -39,11 +39,16 @@ const (
HashLength = 32 HashLength = 32
// AddressLength is the expected length of the address // AddressLength is the expected length of the address
AddressLength = 20 AddressLength = 20
// Berachain: PubkeyLength represents the expected byte length of a BLS12-381 public key
// as used by the beacon chain.
PubkeyLength = 48
) )
var ( var (
hashT = reflect.TypeOf(Hash{}) hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{}) addressT = reflect.TypeOf(Address{})
pubkeyT = reflect.TypeOf(Pubkey{})
// MaxAddress represents the maximum possible address value. // MaxAddress represents the maximum possible address value.
MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") 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)) 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[:])
}

View file

@ -282,6 +282,17 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa
return err 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 return nil
} }

View file

@ -85,7 +85,7 @@ func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
switch config.LatestFork(header.Time) { switch config.LatestFork(header.Time) {
case forks.Osaka: case forks.Osaka:
frac = config.BlobScheduleConfig.Osaka.UpdateFraction frac = config.BlobScheduleConfig.Osaka.UpdateFraction
case forks.Prague: case forks.Prague, forks.Prague1:
frac = config.BlobScheduleConfig.Prague.UpdateFraction frac = config.BlobScheduleConfig.Prague.UpdateFraction
case forks.Cancun: case forks.Cancun:
frac = config.BlobScheduleConfig.Cancun.UpdateFraction frac = config.BlobScheduleConfig.Cancun.UpdateFraction

View file

@ -19,7 +19,9 @@ package core
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "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 // ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already // 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 { func (v *BlockValidator) ValidateBody(block *types.Block) error {
// Check whether the block is already imported. // Check whether the block is already imported.
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { 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") 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. // Blob transactions may be present after the Cancun fork.
var blobs int var blobs int
for i, tx := range block.Transactions() { 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 // Count the number of blobs to validate against the header's blobGasUsed
blobs += len(tx.BlobHashes()) blobs += len(tx.BlobHashes())

View file

@ -30,8 +30,127 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "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. // Tests that simple header verification works, for both good and bad blocks.
func TestHeaderVerification(t *testing.T) { func TestHeaderVerification(t *testing.T) {
testHeaderVerification(t, rawdb.HashScheme) testHeaderVerification(t, rawdb.HashScheme)

View file

@ -102,6 +102,12 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
ProcessBeaconBlockRoot(root, vm.NewEVM(blockContext, b.statedb, b.cm.config, vm.Config{})) 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 // addTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address. // 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 ( var (
blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase) blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase)
evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) 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)) 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 { if err != nil {
panic(err) panic(err)
} }
@ -617,6 +630,9 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi
header.BlobGasUsed = new(uint64) header.BlobGasUsed = new(uint64)
header.ParentBeaconRoot = new(common.Hash) header.ParentBeaconRoot = new(common.Hash)
} }
if cm.config.IsPrague1(header.Number, header.Time) {
header.ParentProposerPubkey = new(common.Pubkey)
}
return header return header
} }

View file

@ -526,6 +526,12 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
if conf.IsPrague(num, g.Timestamp) { if conf.IsPrague(num, g.Timestamp) {
head.RequestsHash = &types.EmptyRequestsHash 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)) return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
} }

View file

@ -406,6 +406,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
nBlobs += len(tx.BlobHashes()) nBlobs += len(tx.BlobHashes())
} }
header.Root = common.BytesToHash(hasher.Sum(nil)) 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) { if config.IsCancun(header.Number, header.Time) {
excess := eip4844.CalcExcessBlobGas(config, parent.Header(), header.Time) excess := eip4844.CalcExcessBlobGas(config, parent.Header(), header.Time)
used := uint64(nBlobs * params.BlobTxBlobGasPerBlob) used := uint64(nBlobs * params.BlobTxBlobGasPerBlob)

View file

@ -166,6 +166,9 @@ type Message struct {
// When SkipFromEOACheck is true, the message sender is not checked to be an EOA. // When SkipFromEOACheck is true, the message sender is not checked to be an EOA.
SkipFromEOACheck bool SkipFromEOACheck bool
// Berachain:IsPoLTx is true if the message is a PoL tx.
IsPoLTx bool
} }
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
@ -185,6 +188,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipFromEOACheck: false, SkipFromEOACheck: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobGasFeeCap: tx.BlobGasFeeCap(),
IsPoLTx: tx.Type() == types.PoLTxType,
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { 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. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
evm.SetTxContext(NewEVMTxContext(msg)) 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() 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. // stateTransition represents a state transition.
// //
// == The State Transitioning Model // == The State Transitioning Model

View file

@ -327,6 +327,11 @@ func (p *TxPool) Add(txs []*types.Transaction, sync bool) []error {
// Mark this transaction belonging to no-subpool // Mark this transaction belonging to no-subpool
splits[i] = -1 splits[i] = -1
// Berachain: PoL txs are rejected.
if tx.Type() == types.PoLTxType {
continue
}
// Try to find a subpool that accepts the transaction // Try to find a subpool that accepts the transaction
for j, subpool := range p.subpools { for j, subpool := range p.subpools {
if subpool.Filter(tx) { if subpool.Filter(tx) {

View file

@ -106,6 +106,9 @@ type Header struct {
// RequestsHash was added by EIP-7685 and is ignored in legacy headers. // RequestsHash was added by EIP-7685 and is ignored in legacy headers.
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` 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 // field type overrides for gencodec
@ -329,6 +332,10 @@ func CopyHeader(h *Header) *Header {
cpy.RequestsHash = new(common.Hash) cpy.RequestsHash = new(common.Hash)
*cpy.RequestsHash = *h.RequestsHash *cpy.RequestsHash = *h.RequestsHash
} }
if h.ParentProposerPubkey != nil {
cpy.ParentProposerPubkey = new(common.Pubkey)
*cpy.ParentProposerPubkey = *h.ParentProposerPubkey
}
return &cpy return &cpy
} }
@ -410,6 +417,7 @@ func (b *Block) BaseFee() *big.Int {
func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot }
func (b *Block) RequestsHash() *common.Hash { return b.header.RequestsHash } 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 { func (b *Block) ExcessBlobGas() *uint64 {
var excessBlobGas *uint64 var excessBlobGas *uint64

View file

@ -37,6 +37,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
} }
var enc Header var enc Header
@ -61,6 +62,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas)
enc.ParentBeaconRoot = h.ParentBeaconRoot enc.ParentBeaconRoot = h.ParentBeaconRoot
enc.RequestsHash = h.RequestsHash enc.RequestsHash = h.RequestsHash
enc.ParentProposerPubkey = h.ParentProposerPubkey
enc.Hash = h.Hash() enc.Hash = h.Hash()
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -89,6 +91,7 @@ func (h *Header) UnmarshalJSON(input []byte) error {
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"`
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"`
ParentProposerPubkey *common.Pubkey `json:"parentProposerPubkey" rlp:"optional"`
} }
var dec Header var dec Header
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -169,5 +172,8 @@ func (h *Header) UnmarshalJSON(input []byte) error {
if dec.RequestsHash != nil { if dec.RequestsHash != nil {
h.RequestsHash = dec.RequestsHash h.RequestsHash = dec.RequestsHash
} }
if dec.ParentProposerPubkey != nil {
h.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }

View file

@ -43,7 +43,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
_tmp4 := obj.ExcessBlobGas != nil _tmp4 := obj.ExcessBlobGas != nil
_tmp5 := obj.ParentBeaconRoot != nil _tmp5 := obj.ParentBeaconRoot != nil
_tmp6 := obj.RequestsHash != 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 { if obj.BaseFee == nil {
w.Write(rlp.EmptyString) w.Write(rlp.EmptyString)
} else { } else {
@ -53,41 +54,48 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
w.WriteBigInt(obj.BaseFee) w.WriteBigInt(obj.BaseFee)
} }
} }
if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.WithdrawalsHash == nil { if obj.WithdrawalsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.WithdrawalsHash[:]) w.WriteBytes(obj.WithdrawalsHash[:])
} }
} }
if _tmp3 || _tmp4 || _tmp5 || _tmp6 { if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.BlobGasUsed == nil { if obj.BlobGasUsed == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.BlobGasUsed)) w.WriteUint64((*obj.BlobGasUsed))
} }
} }
if _tmp4 || _tmp5 || _tmp6 { if _tmp4 || _tmp5 || _tmp6 || _tmp7 {
if obj.ExcessBlobGas == nil { if obj.ExcessBlobGas == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteUint64((*obj.ExcessBlobGas)) w.WriteUint64((*obj.ExcessBlobGas))
} }
} }
if _tmp5 || _tmp6 { if _tmp5 || _tmp6 || _tmp7 {
if obj.ParentBeaconRoot == nil { if obj.ParentBeaconRoot == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.ParentBeaconRoot[:]) w.WriteBytes(obj.ParentBeaconRoot[:])
} }
} }
if _tmp6 { if _tmp6 || _tmp7 {
if obj.RequestsHash == nil { if obj.RequestsHash == nil {
w.Write([]byte{0x80}) w.Write([]byte{0x80})
} else { } else {
w.WriteBytes(obj.RequestsHash[:]) w.WriteBytes(obj.RequestsHash[:])
} }
} }
if _tmp7 {
if obj.ParentProposerPubkey == nil {
w.Write([]byte{0x80})
} else {
w.WriteBytes(obj.ParentProposerPubkey[:])
}
}
w.ListEnd(_tmp0) w.ListEnd(_tmp0)
return w.Flush() return w.Flush()
} }

View file

@ -205,7 +205,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt return errShortTypedReceipt
} }
switch b[0] { switch b[0] {
case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType: case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, PoLTxType:
var data receiptRLP var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data) err := rlp.DecodeBytes(b[1:], &data)
if err != nil { if err != nil {
@ -368,7 +368,7 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
} }
w.WriteByte(r.Type) w.WriteByte(r.Type)
switch r.Type { switch r.Type {
case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType: case AccessListTxType, DynamicFeeTxType, BlobTxType, SetCodeTxType, PoLTxType:
rlp.Encode(w, data) rlp.Encode(w, data)
default: default:
// For unsupported types, write nothing. Since this is for // For unsupported types, write nothing. Since this is for

View file

@ -49,6 +49,9 @@ const (
DynamicFeeTxType = 0x02 DynamicFeeTxType = 0x02
BlobTxType = 0x03 BlobTxType = 0x03
SetCodeTxType = 0x04 SetCodeTxType = 0x04
// Berachain specific.
PoLTxType = 0x7E
) )
// Transaction is an Ethereum transaction. // Transaction is an Ethereum transaction.
@ -211,6 +214,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(BlobTx) inner = new(BlobTx)
case SetCodeTxType: case SetCodeTxType:
inner = new(SetCodeTx) inner = new(SetCodeTx)
case PoLTxType:
inner = new(PoLTx)
default: default:
return nil, ErrTxTypeNotSupported return nil, ErrTxTypeNotSupported
} }

View file

@ -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: default:
return ErrTxTypeNotSupported return ErrTxTypeNotSupported
} }

View file

@ -42,6 +42,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer var signer Signer
switch { switch {
case config.IsPrague1(blockNumber, blockTime):
signer = NewPrague1Signer(config.ChainID)
case config.IsPrague(blockNumber, blockTime): case config.IsPrague(blockNumber, blockTime):
signer = NewPragueSigner(config.ChainID) signer = NewPragueSigner(config.ChainID)
case config.IsCancun(blockNumber, blockTime): case config.IsCancun(blockNumber, blockTime):
@ -71,6 +73,8 @@ func LatestSigner(config *params.ChainConfig) Signer {
var signer Signer var signer Signer
if config.ChainID != nil { if config.ChainID != nil {
switch { switch {
case config.Berachain.Prague1.Time != nil:
signer = NewPrague1Signer(config.ChainID)
case config.PragueTime != nil: case config.PragueTime != nil:
signer = NewPragueSigner(config.ChainID) signer = NewPragueSigner(config.ChainID)
case config.CancunTime != nil: case config.CancunTime != nil:
@ -100,7 +104,7 @@ func LatestSigner(config *params.ChainConfig) Signer {
func LatestSignerForChainID(chainID *big.Int) Signer { func LatestSignerForChainID(chainID *big.Int) Signer {
var signer Signer var signer Signer
if chainID != nil { if chainID != nil {
signer = NewPragueSigner(chainID) signer = NewPrague1Signer(chainID)
} else { } else {
signer = HomesteadSigner{} signer = HomesteadSigner{}
} }
@ -219,6 +223,9 @@ func newModernSigner(chainID *big.Int, fork forks.Fork) Signer {
if fork >= forks.Prague { if fork >= forks.Prague {
s.txtypes[SetCodeTxType] = struct{}{} s.txtypes[SetCodeTxType] = struct{}{}
} }
if fork >= forks.Prague1 {
s.txtypes[PoLTxType] = struct{}{}
}
return s return s
} }
@ -251,6 +258,12 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.ChainId().Cmp(s.chainID) != 0 { if tx.ChainId().Cmp(s.chainID) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainID) 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 // 'modern' txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures. // id, add 27 to become equivalent to unprotected Homestead signatures.
V, R, S := tx.RawSignatureValues() 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 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 // NewPragueSigner returns a signer that accepts
// - EIP-7702 set code transactions // - EIP-7702 set code transactions
// - EIP-4844 blob transactions // - EIP-4844 blob transactions

179
core/types/tx_pol.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

128
core/types/tx_pol_test.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}

View file

@ -87,14 +87,17 @@ var caps = []string{
"engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV2",
"engine_forkchoiceUpdatedV3", "engine_forkchoiceUpdatedV3",
"engine_forkchoiceUpdatedV3P11",
"engine_forkchoiceUpdatedWithWitnessV1", "engine_forkchoiceUpdatedWithWitnessV1",
"engine_forkchoiceUpdatedWithWitnessV2", "engine_forkchoiceUpdatedWithWitnessV2",
"engine_forkchoiceUpdatedWithWitnessV3", "engine_forkchoiceUpdatedWithWitnessV3",
"engine_forkchoiceUpdatedWithWitnessV3P11",
"engine_exchangeTransitionConfigurationV1", "engine_exchangeTransitionConfigurationV1",
"engine_getPayloadV1", "engine_getPayloadV1",
"engine_getPayloadV2", "engine_getPayloadV2",
"engine_getPayloadV3", "engine_getPayloadV3",
"engine_getPayloadV4", "engine_getPayloadV4",
"engine_getPayloadV4P11",
"engine_getPayloadV5", "engine_getPayloadV5",
"engine_getBlobsV1", "engine_getBlobsV1",
"engine_getBlobsV2", "engine_getBlobsV2",
@ -102,14 +105,17 @@ var caps = []string{
"engine_newPayloadV2", "engine_newPayloadV2",
"engine_newPayloadV3", "engine_newPayloadV3",
"engine_newPayloadV4", "engine_newPayloadV4",
"engine_newPayloadV4P11",
"engine_newPayloadWithWitnessV1", "engine_newPayloadWithWitnessV1",
"engine_newPayloadWithWitnessV2", "engine_newPayloadWithWitnessV2",
"engine_newPayloadWithWitnessV3", "engine_newPayloadWithWitnessV3",
"engine_newPayloadWithWitnessV4", "engine_newPayloadWithWitnessV4",
"engine_newPayloadWithWitnessV4P11",
"engine_executeStatelessPayloadV1", "engine_executeStatelessPayloadV1",
"engine_executeStatelessPayloadV2", "engine_executeStatelessPayloadV2",
"engine_executeStatelessPayloadV3", "engine_executeStatelessPayloadV3",
"engine_executeStatelessPayloadV4", "engine_executeStatelessPayloadV4",
"engine_executeStatelessPayloadV4P11",
"engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByHashV2", "engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1", "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) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): 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) { func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root") return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: 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) { func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil: case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals") return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil: case params.BeaconRoot == nil:
@ -256,6 +268,28 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa
return api.forkchoiceUpdated(update, params, engine.PayloadV3, false) 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) { func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes, payloadVersion engine.PayloadVersion, payloadWitness bool) (engine.ForkChoiceResponse, error) {
api.forkchoiceLock.Lock() api.forkchoiceLock.Lock()
defer api.forkchoiceLock.Unlock() defer api.forkchoiceLock.Unlock()
@ -389,6 +423,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
Random: payloadAttributes.Random, Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals, Withdrawals: payloadAttributes.Withdrawals,
BeaconRoot: payloadAttributes.BeaconRoot, BeaconRoot: payloadAttributes.BeaconRoot,
ProposerPubkey: payloadAttributes.ProposerPubkey,
Version: payloadVersion, Version: payloadVersion,
} }
id := args.Id() id := args.Id()
@ -472,6 +507,14 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false) 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. // GetPayloadV5 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) { if !payloadID.Is(engine.PayloadV3) {
@ -589,7 +632,7 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
if params.Withdrawals != nil { if params.Withdrawals != nil {
return invalidStatus, paramsErr("withdrawals not supported in V1") 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. // 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: case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") 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. // 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): case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 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. // 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 { if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 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: // 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 // 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() defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) 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 { if err != nil {
bgu := "nil" bgu := "nil"
if params.BlobGasUsed != nil { if params.BlobGasUsed != nil {

View file

@ -305,7 +305,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data, block %d: %v", i, err) 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 { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
@ -347,7 +347,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to create the executable data %v", err) 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 { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) 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) 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) api := NewConsensusAPI(ethservice)
var blocks []*types.Header var blocks []*types.Header
for i := 0; i < n; i++ { 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 { if beaconRoots != nil {
h = &beaconRoots[i] h = &beaconRoots[i]
} }
var p *common.Pubkey
if proposerPubkeys != nil {
p = &proposerPubkeys[i]
}
envelope := getNewEnvelope(t, api, parent, w, h) envelope := getNewEnvelope(t, api, parent, w, h, p)
execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false) execResp, err := api.newPayload(*envelope.ExecutionPayload, []common.Hash{}, h, envelope.Requests, false, p)
if err != nil { if err != nil {
t.Fatalf("can't execute payload: %v", err) t.Fatalf("can't execute payload: %v", err)
} }
@ -666,6 +670,7 @@ func assembleEnvelope(api *ConsensusAPI, parentHash common.Hash, params *engine.
Random: params.Random, Random: params.Random,
Withdrawals: params.Withdrawals, Withdrawals: params.Withdrawals,
BeaconRoot: params.BeaconRoot, BeaconRoot: params.BeaconRoot,
ProposerPubkey: params.ProposerPubkey,
} }
payload, err := api.eth.Miner().BuildPayload(args, false) payload, err := api.eth.Miner().BuildPayload(args, false)
if err != nil { if err != nil {
@ -691,10 +696,10 @@ func TestEmptyBlocks(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // 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'') // (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) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -708,7 +713,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (2) Now send P1' which is invalid // (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.GasUsed += 1
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // Now latestValidHash should be the common ancestor
@ -726,7 +731,7 @@ func TestEmptyBlocks(t *testing.T) {
} }
// (3) Now send a payload with unknown parent // (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.ParentHash = common.Hash{1}
payload = setBlockhash(payload) payload = setBlockhash(payload)
// Now latestValidHash should be the common ancestor // 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{ params := engine.PayloadAttributes{
Timestamp: parent.Time + 1, Timestamp: parent.Time + 1,
Random: crypto.Keccak256Hash([]byte{byte(1)}), Random: crypto.Keccak256Hash([]byte{byte(1)}),
SuggestedFeeRecipient: parent.Coinbase, SuggestedFeeRecipient: parent.Coinbase,
Withdrawals: withdrawals, Withdrawals: withdrawals,
BeaconRoot: beaconRoot, BeaconRoot: beaconRoot,
ProposerPubkey: proposerPubkey,
} }
envelope, err := assembleEnvelope(api, parent.Hash(), &params) envelope, err := assembleEnvelope(api, parent.Hash(), &params)
@ -758,8 +764,8 @@ func getNewEnvelope(t *testing.T, api *ConsensusAPI, parent *types.Header, withd
return envelope return envelope
} }
func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdrawals []*types.Withdrawal, beaconRoot *common.Hash) *engine.ExecutableData { 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).ExecutionPayload return getNewEnvelope(t, api, parent, withdrawals, beaconRoot, proposerPubkey).ExecutionPayload
} }
// setBlockhash sets the blockhash of a modified ExecutableData. // setBlockhash sets the blockhash of a modified ExecutableData.
@ -820,7 +826,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
commonAncestor := ethserviceA.BlockChain().CurrentBlock() commonAncestor := ethserviceA.BlockChain().CurrentBlock()
// Setup 10 blocks on the canonical chain // 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() commonAncestor = ethserviceA.BlockChain().CurrentBlock()
var invalidChain []*engine.ExecutableData var invalidChain []*engine.ExecutableData
@ -829,7 +835,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
//invalidChain = append(invalidChain, payload1) //invalidChain = append(invalidChain, payload1)
// create an invalid payload2 (P2) // 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.ParentHash = payload1.BlockHash
payload2.GasUsed += 1 payload2.GasUsed += 1
payload2 = setBlockhash(payload2) payload2 = setBlockhash(payload2)
@ -838,7 +844,7 @@ func TestTrickRemoteBlockCache(t *testing.T) {
head := payload2 head := payload2
// create some valid payloads on top // create some valid payloads on top
for i := 0; i < 10; i++ { 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.ParentHash = head.BlockHash
payload = setBlockhash(payload) payload = setBlockhash(payload)
invalidChain = append(invalidChain, payload) invalidChain = append(invalidChain, payload)
@ -875,10 +881,10 @@ func TestInvalidBloom(t *testing.T) {
api := NewConsensusAPI(ethservice) api := NewConsensusAPI(ethservice)
// Setup 10 blocks on the canonical chain // 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'') // (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)) payload.LogsBloom = append(payload.LogsBloom, byte(1))
status, err := api.NewPayloadV1(*payload) status, err := api.NewPayloadV1(*payload)
if err != nil { if err != nil {
@ -935,7 +941,7 @@ func TestSimultaneousNewBlock(t *testing.T) {
t.Fatal(testErr) t.Fatal(testErr)
} }
} }
block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil) block, err := engine.ExecutableDataToBlock(*execData, nil, nil, nil, nil)
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) 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. // 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)) newBlocks := make([]*types.Block, len(newHeaders))
for i, header := range newHeaders { for i, header := range newHeaders {
newBlocks[i] = ethservice.BlockChain().GetBlock(header.Hash(), header.Number.Uint64()) 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 { if got := len(envelope.BlobsBundle.Blobs); got != want {
t.Fatalf("invalid number of blobs: got %v, want %v", 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 { if err != nil {
t.Error(err) t.Error(err)
} }

View file

@ -100,6 +100,8 @@ type SimulatedBeacon struct {
func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion { func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion {
switch config.LatestFork(time) { switch config.LatestFork(time) {
case forks.Prague1:
return engine.PayloadV3P11
case forks.Prague, forks.Cancun: case forks.Prague, forks.Cancun:
return engine.PayloadV3 return engine.PayloadV3
case forks.Paris, forks.Shanghai: 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) version := payloadVersion(c.eth.BlockChain().Config(), timestamp)
var proposerPubkey *common.Pubkey
if version == engine.PayloadV3P11 {
proposerPubkey = &common.Pubkey{}
}
var random [32]byte var random [32]byte
rand.Read(random[:]) rand.Read(random[:])
@ -200,6 +206,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Withdrawals: withdrawals, Withdrawals: withdrawals,
Random: random, Random: random,
BeaconRoot: &common.Hash{}, BeaconRoot: &common.Hash{},
ProposerPubkey: proposerPubkey,
}, version, false) }, version, false)
if err != nil { if err != nil {
return err return err
@ -256,7 +263,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
} }
// Mark the payload as canon // 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 { if err != nil {
return err return err
} }
@ -317,6 +324,7 @@ func (c *SimulatedBeacon) Commit() common.Hash {
withdrawals := c.withdrawals.pop(10) withdrawals := c.withdrawals.pop(10)
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err) log.Warn("Error performing sealing work", "err", err)
panic(err)
} }
return c.eth.BlockChain().CurrentBlock().Hash() return c.eth.BlockChain().CurrentBlock().Hash()
} }

View file

@ -37,6 +37,8 @@ import (
func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV1(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if payloadAttributes != nil { if payloadAttributes != nil {
switch { switch {
case payloadAttributes.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("proposer pubkey not supported in V1")
case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil: case payloadAttributes.Withdrawals != nil || payloadAttributes.BeaconRoot != nil:
return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1") return engine.STATUS_INVALID, paramsErr("withdrawals and beacon root not supported in V1")
case !api.checkFork(payloadAttributes.Timestamp, forks.Paris, forks.Shanghai): 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) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV2(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.BeaconRoot != nil: case params.BeaconRoot != nil:
return engine.STATUS_INVALID, attributesErr("unexpected beacon root") return engine.STATUS_INVALID, attributesErr("unexpected beacon root")
case api.checkFork(params.Timestamp, forks.Paris) && params.Withdrawals != nil: 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) { func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.ForkchoiceStateV1, params *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) {
if params != nil { if params != nil {
switch { switch {
case params.ProposerPubkey != nil:
return engine.STATUS_INVALID, attributesErr("unexpected proposer pubkey")
case params.Withdrawals == nil: case params.Withdrawals == nil:
return engine.STATUS_INVALID, attributesErr("missing withdrawals") return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil: case params.BeaconRoot == nil:
@ -84,13 +90,35 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
return api.forkchoiceUpdated(update, params, engine.PayloadV3, true) 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 // NewPayloadWithWitnessV1 is analogous to NewPayloadV1, only it also generates
// and returns a stateless witness after running the payload. // and returns a stateless witness after running the payload.
func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { func (api *ConsensusAPI) NewPayloadWithWitnessV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) 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 // NewPayloadWithWitnessV2 is analogous to NewPayloadV2, only it also generates
@ -112,7 +140,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV2(params engine.ExecutableData) (
case params.BlobGasUsed != nil: case params.BlobGasUsed != nil:
return invalidStatus, paramsErr("non-nil blobGasUsed pre-cancun") 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 // 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): case !api.checkFork(params.Timestamp, forks.Cancun):
return invalidStatus, unsupportedForkErr("newPayloadV3 must only be called for cancun payloads") 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 // 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 { if err := validateRequests(requests); err != nil {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 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 // ExecuteStatelessPayloadV1 is analogous to NewPayloadV1, only it operates in
@ -167,7 +223,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV1(params engine.ExecutableData,
if params.Withdrawals != nil { if params.Withdrawals != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) 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 // ExecuteStatelessPayloadV2 is analogous to NewPayloadV2, only it operates in
@ -189,7 +245,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV2(params engine.ExecutableData,
case params.BlobGasUsed != nil: case params.BlobGasUsed != nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("non-nil blobGasUsed pre-cancun") 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 // ExecuteStatelessPayloadV3 is analogous to NewPayloadV3, only it operates in
@ -207,9 +263,9 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV3(params engine.ExecutableData,
case beaconRoot == nil: case beaconRoot == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case !api.checkFork(params.Timestamp, forks.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 // ExecuteStatelessPayloadV4 is analogous to NewPayloadV4, only it operates in
@ -229,18 +285,46 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData,
case executionRequests == nil: case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague") return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.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) requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil { if err := validateRequests(requests); err != nil {
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) 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) 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 { if err != nil {
bgu := "nil" bgu := "nil"
if params.BlobGasUsed != 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.Transactions)", len(params.Transactions),
"len(params.Withdrawals)", len(params.Withdrawals), "len(params.Withdrawals)", len(params.Withdrawals),
"beaconRoot", beaconRoot, "beaconRoot", beaconRoot,
"parentProposerPubkey", proposerPubkey,
"len(requests)", len(requests), "len(requests)", len(requests),
"error", err) "error", err)
errorMsg := err.Error() errorMsg := err.Error()

View file

@ -971,8 +971,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
return nil, err return nil, err
} }
var ( var (
msg = args.ToMessage(vmctx.BaseFee, true, true) isPrague1 = api.backend.ChainConfig().IsPrague1(vmctx.BlockNumber, vmctx.Time)
tx = args.ToTransaction(types.LegacyTxType) 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 traceConfig *TraceConfig
) )
// Lower the basefee to 0 to avoid breaking EVM // Lower the basefee to 0 to avoid breaking EVM

View file

@ -318,7 +318,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big {
return nil return nil
} }
switch tx.Type() { switch tx.Type() {
case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasFeeCap()) return (*hexutil.Big)(tx.GasFeeCap())
default: default:
return nil return nil
@ -331,7 +331,7 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big {
return nil return nil
} }
switch tx.Type() { switch tx.Type() {
case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType, types.PoLTxType:
return (*hexutil.Big)(tx.GasTipCap()) return (*hexutil.Big)(tx.GasTipCap())
default: default:
return nil return nil

View file

@ -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 { if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil {
return nil, err 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 // Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap). // invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 { 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 { if err := args.CallDefaults(gasCap, header.BaseFee, b.ChainConfig().ChainID); err != nil {
return 0, err 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 // Run the gas estimation and wrap any revertals into a custom return
estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) 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 { if head.RequestsHash != nil {
result["requestsHash"] = head.RequestsHash result["requestsHash"] = head.RequestsHash
} }
if head.ParentProposerPubkey != nil {
result["parentProposerPubkey"] = head.ParentProposerPubkey
}
return result return result
} }
@ -1057,6 +1060,14 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
} }
result.AuthorizationList = tx.SetCodeAuthorizations() 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 return result
} }
@ -1217,7 +1228,9 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy() statedb := db.Copy()
// Set the accesslist to the last al // Set the accesslist to the last al
args.AccessList = &accessList 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 // Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, addressesToExclude) 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)) res, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit))
if err != nil { 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) { if tracer.Equal(prevTracer) {
return accessList, res.UsedGas, res.Err, nil return accessList, res.UsedGas, res.Err, nil
@ -1503,7 +1524,13 @@ func (api *TransactionAPI) SendTransaction(ctx context.Context, args Transaction
return common.Hash{}, err return common.Hash{}, err
} }
// Assemble the transaction and sign with the wallet // 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) signed, err := wallet.SignTx(account, tx, api.b.ChainConfig().ChainID)
if err != nil { if err != nil {
@ -1523,7 +1550,12 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
return nil, err return nil, err
} }
// Assemble the transaction and obtain rlp // 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() data, err := tx.MarshalBinary()
if err != nil { if err != nil {
return nil, err return nil, err
@ -1591,7 +1623,12 @@ func (api *TransactionAPI) SignTransaction(ctx context.Context, args Transaction
return nil, err return nil, err
} }
// Before actually sign the transaction, ensure the transaction fee is reasonable. // 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 { if err := checkTxFee(tx.GasPrice(), tx.Gas(), api.b.RPCTxFeeCap()); err != nil {
return nil, err 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 { if err := sendArgs.setDefaults(ctx, api.b, false); err != nil {
return common.Hash{}, err 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. // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
price := matchTx.GasPrice() price := matchTx.GasPrice()
@ -1679,7 +1721,14 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs,
if gasLimit != nil && *gasLimit != 0 { if gasLimit != nil && *gasLimit != 0 {
sendArgs.Gas = gasLimit 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 { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }

View file

@ -1217,6 +1217,15 @@ func TestCall(t *testing.T) {
}, },
expectErr: errors.New(`block override "withdrawals" is not supported for this RPC method`), 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 { for _, tc := range testSuite {
result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides)

View file

@ -131,6 +131,7 @@ type BlockOverrides struct {
BlobBaseFee *hexutil.Big BlobBaseFee *hexutil.Big
BeaconRoot *common.Hash BeaconRoot *common.Hash
Withdrawals *types.Withdrawals Withdrawals *types.Withdrawals
ProposerPubkey *common.Pubkey
} }
// Apply overrides the given header fields into the given block context. // 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 { if o.Withdrawals != nil {
return errors.New(`block override "withdrawals" is not supported for this RPC method`) 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 { if o.Number != nil {
blockCtx.BlockNumber = o.Number.ToInt() blockCtx.BlockNumber = o.Number.ToInt()
} }

View file

@ -199,6 +199,37 @@ func (sim *simulator) execute(ctx context.Context, blocks []simBlock) ([]*simBlo
if err != nil { if err != nil {
return nil, err 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() headers[bi] = result.Header()
results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders} results[bi] = &simBlockResult{fullTx: sim.fullTx, chainConfig: sim.chainConfig, Block: result, Calls: callResults, senders: senders}
parent = result.Header() parent = result.Header()
@ -279,7 +310,9 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
return nil, nil, nil, err return nil, nil, nil, err
} }
var ( var (
tx = call.ToTransaction(types.DynamicFeeTxType) isPrague1 = sim.chainConfig.IsPrague1(header.Number, header.Time)
distributorAddress = sim.chainConfig.Berachain.Prague1.PoLDistributorAddress
tx = call.ToTransaction(types.DynamicFeeTxType, isPrague1, distributorAddress)
txHash = tx.Hash() txHash = tx.Hash()
) )
txes[i] = tx txes[i] = tx
@ -287,7 +320,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header,
tracer.reset(txHash, uint(i)) tracer.reset(txHash, uint(i))
sim.state.SetTxContext(txHash, i) sim.state.SetTxContext(txHash, i)
// EoA check is always skipped, even in validation mode. // 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) result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp)
if err != nil { if err != nil {
txErr := txValidationError(err) txErr := txValidationError(err)
@ -484,6 +517,14 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
parentBeaconRoot = overrides.BeaconRoot 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{ header = overrides.MakeHeader(&types.Header{
UncleHash: types.EmptyUncleHash, UncleHash: types.EmptyUncleHash,
ReceiptHash: types.EmptyReceiptsHash, ReceiptHash: types.EmptyReceiptsHash,
@ -493,6 +534,7 @@ func (sim *simulator) makeHeaders(blocks []simBlock) ([]*types.Header, error) {
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
WithdrawalsHash: withdrawalsHash, WithdrawalsHash: withdrawalsHash,
ParentBeaconRoot: parentBeaconRoot, ParentBeaconRoot: parentBeaconRoot,
ParentProposerPubkey: parentProposerPubkey,
}) })
res[bi] = header res[bi] = header
} }

View file

@ -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 // core evm. This method is used in calls and traces that do not require a real
// live transaction. // live transaction.
// Assumes that fields are not nil, i.e. setDefaults or CallDefaults has been called. // 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 ( var (
gasPrice *big.Int gasPrice *big.Int
gasFeeCap *big.Int gasFeeCap *big.Int
@ -463,14 +463,17 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA
SetCodeAuthorizations: args.AuthorizationList, SetCodeAuthorizations: args.AuthorizationList,
SkipNonceChecks: skipNonceCheck, SkipNonceChecks: skipNonceCheck,
SkipFromEOACheck: skipEoACheck, SkipFromEOACheck: skipEoACheck,
IsPoLTx: isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress),
} }
} }
// ToTransaction converts the arguments to a transaction. // ToTransaction converts the arguments to a transaction.
// This assumes that setDefaults has been called. // 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 usedType := types.LegacyTxType
switch { switch {
case isPrague1 && types.IsPoLDistribution(args.To, args.data(), distributorAddress):
usedType = types.PoLTxType
case args.AuthorizationList != nil || defaultType == types.SetCodeTxType: case args.AuthorizationList != nil || defaultType == types.SetCodeTxType:
usedType = types.SetCodeTxType usedType = types.SetCodeTxType
case args.BlobHashes != nil || defaultType == types.BlobTxType: case args.BlobHashes != nil || defaultType == types.BlobTxType:
@ -486,6 +489,20 @@ func (args *TransactionArgs) ToTransaction(defaultType int) *types.Transaction {
} }
var data types.TxData var data types.TxData
switch usedType { 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: case types.SetCodeTxType:
al := types.AccessList{} al := types.AccessList{}
if args.AccessList != nil { if args.AccessList != nil {

View file

@ -140,6 +140,11 @@ func (miner *Miner) BuildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// getPending retrieves the pending block based on the current head block. // getPending retrieves the pending block based on the current head block.
// The result might be nil if pending generation is failed. // 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 { func (miner *Miner) getPending() *newPayloadResult {
header := miner.chain.CurrentHeader() header := miner.chain.CurrentHeader()
miner.pendingMu.Lock() miner.pendingMu.Lock()
@ -163,6 +168,7 @@ func (miner *Miner) getPending() *newPayloadResult {
random: common.Hash{}, random: common.Hash{},
withdrawals: withdrawal, withdrawals: withdrawal,
beaconRoot: nil, beaconRoot: nil,
proposerPubkey: &common.Pubkey{},
noTxs: false, noTxs: false,
}, false) // we will never make a witness for a pending block }, false) // we will never make a witness for a pending block
if ret.err != nil { if ret.err != nil {

View file

@ -43,6 +43,7 @@ type BuildPayloadArgs struct {
Random common.Hash // The provided randomness value Random common.Hash // The provided randomness value
Withdrawals types.Withdrawals // The provided withdrawals Withdrawals types.Withdrawals // The provided withdrawals
BeaconRoot *common.Hash // The provided beaconRoot (Cancun) 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. Version engine.PayloadVersion // Versioning byte for payload id calculation.
} }
@ -57,6 +58,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil { if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:]) hasher.Write(args.BeaconRoot[:])
} }
if args.ProposerPubkey != nil {
hasher.Write(args.ProposerPubkey[:])
}
var out engine.PayloadID var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8]) copy(out[:], hasher.Sum(nil)[:8])
out[0] = byte(args.Version) out[0] = byte(args.Version)
@ -218,6 +222,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot, beaconRoot: args.BeaconRoot,
proposerPubkey: args.ProposerPubkey,
noTxs: true, noTxs: true,
} }
empty := miner.generateWork(emptyParams, witness) empty := miner.generateWork(emptyParams, witness)
@ -235,10 +240,13 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
timer := time.NewTimer(0) timer := time.NewTimer(0)
defer timer.Stop() defer timer.Stop()
// Setup the timer for terminating the process if SECONDS_PER_SLOT (12s in // Setup the timer for terminating the process if SECONDS_PER_SLOT (2s in
// the Mainnet configuration) have passed since the point in time identified // the Berachain configuration) have passed since the point in time identified
// by the timestamp parameter. // 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{ fullParams := &generateParams{
timestamp: args.Timestamp, timestamp: args.Timestamp,
@ -248,6 +256,7 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
random: args.Random, random: args.Random,
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot, beaconRoot: args.BeaconRoot,
proposerPubkey: args.ProposerPubkey,
noTxs: false, noTxs: false,
} }

View file

@ -91,6 +91,7 @@ type generateParams struct {
random common.Hash // The randomness generated by beacon chain, empty before the merge 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) withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
beaconRoot *common.Hash // The beacon root (cancun 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 noTxs bool // Flag whether an empty block without any transaction is expected
} }
@ -221,6 +222,10 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
header.ExcessBlobGas = &excessBlobGas header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot 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. // Could potentially happen if starting to mine in an odd state.
// Note genParams.coinbase can be different with header.Coinbase // Note genParams.coinbase can be different with header.Coinbase
// since clique algorithm can modify the coinbase field in header. // 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. // 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) { func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) {
var ( var (
gasPool = env.gasPool
blockGasUsed = &env.header.GasUsed
snap = env.state.Snapshot() snap = env.state.Snapshot()
gp = env.gasPool.Gas() 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 { if err != nil {
env.state.RevertToSnapshot(snap) env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp) env.gasPool.SetGas(gasBefore)
} }
return receipt, err return receipt, err
} }
@ -452,6 +467,29 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
prio := miner.prio prio := miner.prio
miner.confMu.RUnlock() 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 // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees
filter := txpool.PendingFilter{ filter := txpool.PendingFilter{
MinTip: uint256.MustFromBig(tip), MinTip: uint256.MustFromBig(tip),

View file

@ -548,6 +548,8 @@ type Prague1Config struct {
BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator,omitempty"` BaseFeeChangeDenominator uint64 `json:"baseFeeChangeDenominator,omitempty"`
// MinimumBaseFeeWei is the minimum base fee in wei. // MinimumBaseFeeWei is the minimum base fee in wei.
MinimumBaseFeeWei uint64 `json:"minimumBaseFeeWei,omitempty"` MinimumBaseFeeWei uint64 `json:"minimumBaseFeeWei,omitempty"`
// PoLDistributorAddress is the address of the PoL distributor.
PoLDistributorAddress common.Address `json:"polDistributorAddress,omitempty"`
} }
// String implements the stringer interface. // String implements the stringer interface.
@ -555,8 +557,8 @@ func (c Prague1Config) String() string {
banner := "prague1" banner := "prague1"
if c.Time != nil { if c.Time != nil {
banner += fmt.Sprintf( banner += fmt.Sprintf(
"(time: %v, baseFeeChangeDenominator: %v, minimumBaseFeeWei: %v)", "(time: %v, baseFeeChangeDenominator: %v, minimumBaseFeeWei: %v, polDistributorAddress: %v)",
*c.Time, c.BaseFeeChangeDenominator, c.MinimumBaseFeeWei, *c.Time, c.BaseFeeChangeDenominator, c.MinimumBaseFeeWei, c.PoLDistributorAddress,
) )
} }
return banner return banner
@ -1042,6 +1044,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork {
switch { switch {
case c.IsOsaka(london, time): case c.IsOsaka(london, time):
return forks.Osaka return forks.Osaka
case c.IsPrague1(london, time):
return forks.Prague1
case c.IsPrague(london, time): case c.IsPrague(london, time):
return forks.Prague return forks.Prague
case c.IsCancun(london, time): case c.IsCancun(london, time):
@ -1059,6 +1063,8 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 {
switch { switch {
case fork == forks.Osaka: case fork == forks.Osaka:
return c.OsakaTime return c.OsakaTime
case fork == forks.Prague1:
return c.Berachain.Prague1.Time
case fork == forks.Prague: case fork == forks.Prague:
return c.PragueTime return c.PragueTime
case fork == forks.Cancun: case fork == forks.Cancun:
@ -1210,7 +1216,7 @@ type Rules struct {
IsEIP2929, IsEIP4762 bool IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool IsBerlin, IsLondon bool
IsMerge, IsShanghai, IsCancun, IsPrague, IsOsaka bool IsMerge, IsShanghai, IsCancun, IsPrague, IsPrague1, IsOsaka bool
IsVerkle bool IsVerkle bool
} }
@ -1240,6 +1246,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsShanghai: isMerge && c.IsShanghai(num, timestamp),
IsCancun: isMerge && c.IsCancun(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp),
IsPrague: isMerge && c.IsPrague(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp),
IsPrague1: isMerge && c.IsPrague1(num, timestamp),
IsOsaka: isMerge && c.IsOsaka(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp),
IsVerkle: isVerkle, IsVerkle: isVerkle,
IsEIP4762: isVerkle, IsEIP4762: isVerkle,

View file

@ -193,7 +193,7 @@ var (
// System contracts. // System contracts.
var ( 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") SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe")
// EIP-4788 - Beacon block root in the EVM // EIP-4788 - Beacon block root in the EVM
@ -211,4 +211,7 @@ var (
// EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE // EIP-7251 - Increase the MAX_EFFECTIVE_BALANCE
ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251") ConsolidationQueueAddress = common.HexToAddress("0x0000BBdDc7CE488642fb579F8B00f3a590007251")
ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460d35760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f82111560685781019083028483029004916001019190604d565b9093900492505050366060146088573661019a573461019a575f5260205ff35b341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060021160e7575060025b5f5b8181146101295782810160040260040181607402815460601b815260140181600101548152602001816002015481526020019060030154905260010160e9565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd")
// BRIP-0004 - PoL Distributor
PoLTxGasLimit uint64 = 30_000_000
) )

View file

@ -97,6 +97,7 @@ type btHeader struct {
BlobGasUsed *uint64 BlobGasUsed *uint64
ExcessBlobGas *uint64 ExcessBlobGas *uint64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
type btHeaderMarshaling struct { type btHeaderMarshaling struct {
@ -336,6 +337,9 @@ func validateHeader(h *btHeader, h2 *types.Header) error {
if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) { if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) {
return fmt.Errorf("parentBeaconBlockRoot: want: %v have: %v", 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 return nil
} }

View file

@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
var enc btHeader var enc btHeader
enc.Bloom = b.Bloom enc.Bloom = b.Bloom
@ -61,6 +62,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) {
enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed) enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed)
enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas) enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas)
enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot
enc.ParentProposerPubkey = b.ParentProposerPubkey
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -88,6 +90,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
BlobGasUsed *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
ParentBeaconBlockRoot *common.Hash ParentBeaconBlockRoot *common.Hash
ParentProposerPubkey *common.Pubkey
} }
var dec btHeader var dec btHeader
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -156,5 +159,8 @@ func (b *btHeader) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil { if dec.ParentBeaconBlockRoot != nil {
b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
} }
if dec.ParentProposerPubkey != nil {
b.ParentProposerPubkey = dec.ParentProposerPubkey
}
return nil return nil
} }