Skip to content

Commit

Permalink
handle holocene EIP-1559 params
Browse files Browse the repository at this point in the history
  • Loading branch information
roberto-bayardo committed Oct 9, 2024
1 parent 9eb6114 commit de0864c
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 55 deletions.
6 changes: 6 additions & 0 deletions beacon/engine/gen_blockparams.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions beacon/engine/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@ type PayloadAttributes struct {
NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"`
// GasLimit is a field for rollups: if set, this sets the exact gas limit the block produced with.
GasLimit *uint64 `json:"gasLimit,omitempty" gencodec:"optional"`
// EIP1559Params is a field for rollups implementing the Holocene upgrade,
// and contains encoded EIP-1559 parameters. See:
// https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip1559params-encoding
EIP1559Params []byte `json:"eip1559Params,omitempty" gencodec:"optional"`
}

// JSON type overrides for PayloadAttributes.
type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64

Transactions []hexutil.Bytes
GasLimit *hexutil.Uint64
Transactions []hexutil.Bytes
GasLimit *hexutil.Uint64
EIP1559Params hexutil.Bytes
}

//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
Expand Down
34 changes: 29 additions & 5 deletions consensus/misc/eip1559/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package eip1559

import (
"encoding/binary"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -55,15 +56,38 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade
return nil
}

// DecodeHolocene1599Params extracts the Holcene 1599 parameters from the encoded form:
// https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#eip1559params-encoding
func DecodeHolocene1559Params(nonce types.BlockNonce) (uint64, uint64) {
elasticity := binary.BigEndian.Uint32(nonce[4:])
denominator := binary.BigEndian.Uint32(nonce[:4])
return uint64(elasticity), uint64(denominator)
}

func EncodeHolocene1559Params(elasticity, denom uint32) types.BlockNonce {
var nonce types.BlockNonce
binary.BigEndian.PutUint32(nonce[4:], elasticity)
binary.BigEndian.PutUint32(nonce[:4], denom)
return nonce
}

// CalcBaseFee calculates the basefee of the header.
// The time belongs to the new block to check if Canyon is activted or not
// The time belongs to the new block to check which upgrades are active.
func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) *big.Int {
// If the current block is the first EIP-1559 block, return the InitialBaseFee.
if !config.IsLondon(parent.Number) {
return new(big.Int).SetUint64(params.InitialBaseFee)
}

parentGasTarget := parent.GasLimit / config.ElasticityMultiplier()
elasticity := config.ElasticityMultiplier()
denominator := config.BaseFeeChangeDenominator(time)
if config.IsHolocene(time) {
// Holocene requires we get the 1559 parameters from the nonce field of parent header,
// unless the field is zero, in which case we use the Canyon values.
if parent.Nonce != types.BlockNonce([8]byte{}) {
elasticity, denominator = DecodeHolocene1559Params(parent.Nonce)
}
}
parentGasTarget := parent.GasLimit / elasticity
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if parent.GasUsed == parentGasTarget {
return new(big.Int).Set(parent.BaseFee)
Expand All @@ -80,7 +104,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
num.SetUint64(parent.GasUsed - parentGasTarget)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator(time)))
num.Div(num, denom.SetUint64(denominator))
baseFeeDelta := math.BigMax(num, common.Big1)

return num.Add(parent.BaseFee, baseFeeDelta)
Expand All @@ -90,7 +114,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
num.SetUint64(parentGasTarget - parent.GasUsed)
num.Mul(num, parent.BaseFee)
num.Div(num, denom.SetUint64(parentGasTarget))
num.Div(num, denom.SetUint64(config.BaseFeeChangeDenominator(time)))
num.Div(num, denom.SetUint64(denominator))
baseFee := num.Sub(parent.BaseFee, num)

return math.BigMax(baseFee, common.Big0)
Expand Down
44 changes: 44 additions & 0 deletions consensus/misc/eip1559/eip1559_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func opConfig() *params.ChainConfig {
ct := uint64(10)
eip1559DenominatorCanyon := uint64(250)
config.CanyonTime = &ct
ht := uint64(12)
config.HoloceneTime = &ht
config.Optimism = &params.OptimismConfig{
EIP1559Elasticity: 6,
EIP1559Denominator: 50,
Expand Down Expand Up @@ -174,5 +176,47 @@ func TestCalcBaseFeeOptimism(t *testing.T) {
if have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 {
t.Errorf("test %d: have %d want %d, ", i, have, want)
}
if test.postCanyon {
// make sure Holocene activation doesn't change the outcome; since these tests have a
// zero nonce, they should be handled using the Canyon config.
parent.Time = 10
if have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 {
t.Errorf("test %d: have %d want %d, ", i, have, want)
}
}
}
}

// TestCalcBaseFeeHolocene assumes all blocks are Optimism blocks post-Holocene upgrade
func TestCalcBaseFeeOptimismHolocene(t *testing.T) {
elasticity2Denom10Nonce := EncodeHolocene1559Params(2, 10)
elasticity10Denom2Nonce := EncodeHolocene1559Params(10, 2)
parentBaseFee := int64(10_000_000)
parentGasLimit := uint64(30_000_000)

tests := []struct {
parentGasUsed uint64
expectedBaseFee int64
nonce types.BlockNonce
}{
{parentGasLimit / 2, parentBaseFee, elasticity2Denom10Nonce}, // target
{10_000_000, 9_666_667, elasticity2Denom10Nonce}, // below
{20_000_000, 10_333_333, elasticity2Denom10Nonce}, // above
{parentGasLimit / 10, parentBaseFee, elasticity10Denom2Nonce}, // target
{1_000_000, 6_666_667, elasticity10Denom2Nonce}, // below
{30_000_000, 55_000_000, elasticity10Denom2Nonce}, // above
}
for i, test := range tests {
parent := &types.Header{
Number: common.Big32,
GasLimit: parentGasLimit,
GasUsed: test.parentGasUsed,
BaseFee: big.NewInt(parentBaseFee),
Time: 10,
Nonce: test.nonce,
}
if have, want := CalcBaseFee(opConfig(), parent, parent.Time+2), big.NewInt(test.expectedBaseFee); have.Cmp(want) != 0 {
t.Errorf("test %d: have %d want %d, ", i, have, want)
}
}
}
13 changes: 11 additions & 2 deletions eth/catalyst/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,17 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// sealed by the beacon client. The payload will be requested later, and we
// will replace it arbitrarily many times in between.
if payloadAttributes != nil {
if api.eth.BlockChain().Config().Optimism != nil && payloadAttributes.GasLimit == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("gasLimit parameter is required"))
if api.eth.BlockChain().Config().Optimism != nil {
if payloadAttributes.GasLimit == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("gasLimit parameter is required"))
}
if api.eth.BlockChain().Config().IsHolocene(payloadAttributes.Timestamp) {
if payloadAttributes.EIP1559Params == nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("eip1559Params parameter is required"))
}
} else if payloadAttributes.EIP1559Params != nil {
return engine.STATUS_INVALID, engine.InvalidPayloadAttributes.With(errors.New("eip155Params not supported prior to Holocene upgrade"))
}
}
transactions := make(types.Transactions, 0, len(payloadAttributes.Transactions))
for i, otx := range payloadAttributes.Transactions {
Expand Down
52 changes: 29 additions & 23 deletions miner/payload_building.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ type BuildPayloadArgs struct {
BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
Version engine.PayloadVersion // Versioning byte for payload id calculation.

NoTxPool bool // Optimism addition: option to disable tx pool contents from being included
Transactions []*types.Transaction // Optimism addition: txs forced into the block via engine API
GasLimit *uint64 // Optimism addition: override gas limit of the block to build
NoTxPool bool // Optimism addition: option to disable tx pool contents from being included
Transactions []*types.Transaction // Optimism addition: txs forced into the block via engine API
GasLimit *uint64 // Optimism addition: override gas limit of the block to build
EIP1559Params *types.BlockNonce // Optimism addition: encodes Holocene EIP-1559 params
}

// Id computes an 8-byte identifier by hashing the components of the payload arguments.
Expand All @@ -75,6 +76,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.GasLimit != nil {
binary.Write(hasher, binary.BigEndian, *args.GasLimit)
}
if args.EIP1559Params != nil {
hasher.Write(args.EIP1559Params[:])
}

var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8])
Expand Down Expand Up @@ -280,16 +284,17 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
// to deliver for not missing slot.
// In OP-Stack, the "empty" block is constructed from provided txs only, i.e. no tx-pool usage.
emptyParams := &generateParams{
timestamp: args.Timestamp,
forceTime: true,
parentHash: args.Parent,
coinbase: args.FeeRecipient,
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: true,
txs: args.Transactions,
gasLimit: args.GasLimit,
timestamp: args.Timestamp,
forceTime: true,
parentHash: args.Parent,
coinbase: args.FeeRecipient,
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: true,
txs: args.Transactions,
gasLimit: args.GasLimit,
eip1559Params: args.EIP1559Params,
}
empty := miner.generateWork(emptyParams, witness)
if empty.err != nil {
Expand All @@ -304,16 +309,17 @@ func (miner *Miner) buildPayload(args *BuildPayloadArgs, witness bool) (*Payload
}

fullParams := &generateParams{
timestamp: args.Timestamp,
forceTime: true,
parentHash: args.Parent,
coinbase: args.FeeRecipient,
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: false,
txs: args.Transactions,
gasLimit: args.GasLimit,
timestamp: args.Timestamp,
forceTime: true,
parentHash: args.Parent,
coinbase: args.FeeRecipient,
random: args.Random,
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: false,
txs: args.Transactions,
gasLimit: args.GasLimit,
eip1559Params: args.EIP1559Params,
}

// Since we skip building the empty block when using the tx pool, we need to explicitly
Expand Down
Loading

0 comments on commit de0864c

Please sign in to comment.