From de0864c39c19f93f602c1fa887d1c10c9a399792 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Tue, 8 Oct 2024 21:16:49 -0700 Subject: [PATCH] handle holocene EIP-1559 params --- beacon/engine/gen_blockparams.go | 6 ++ beacon/engine/types.go | 9 +- consensus/misc/eip1559/eip1559.go | 34 ++++++- consensus/misc/eip1559/eip1559_test.go | 44 +++++++++ eth/catalyst/api.go | 13 ++- miner/payload_building.go | 52 +++++----- miner/payload_building_test.go | 126 +++++++++++++++++++++---- miner/worker.go | 28 +++++- 8 files changed, 257 insertions(+), 55 deletions(-) diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go index c343e58906..1a9a45fba5 100644 --- a/beacon/engine/gen_blockparams.go +++ b/beacon/engine/gen_blockparams.go @@ -24,6 +24,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"` GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` + EIP1559Params hexutil.Bytes `json:"eip1559Params,omitempty" gencodec:"optional"` } var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) @@ -39,6 +40,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { } enc.NoTxPool = p.NoTxPool enc.GasLimit = (*hexutil.Uint64)(p.GasLimit) + enc.EIP1559Params = p.EIP1559Params return json.Marshal(&enc) } @@ -53,6 +55,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` NoTxPool *bool `json:"noTxPool,omitempty" gencodec:"optional"` GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` + EIP1559Params *hexutil.Bytes `json:"eip1559Params,omitempty" gencodec:"optional"` } var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { @@ -88,5 +91,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { if dec.GasLimit != nil { p.GasLimit = (*uint64)(dec.GasLimit) } + if dec.EIP1559Params != nil { + p.EIP1559Params = *dec.EIP1559Params + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 9b06b1e1bd..41c475ed57 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -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 diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index a66298af69..9d3a1a529f 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -17,6 +17,7 @@ package eip1559 import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -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) @@ -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) @@ -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) diff --git a/consensus/misc/eip1559/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go index 39766b57cc..a58e4eb480 100644 --- a/consensus/misc/eip1559/eip1559_test.go +++ b/consensus/misc/eip1559/eip1559_test.go @@ -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 = ¶ms.OptimismConfig{ EIP1559Elasticity: 6, EIP1559Denominator: 50, @@ -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) + } } } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index e8c98d1e1d..c9826eb6e2 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -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 { diff --git a/miner/payload_building.go b/miner/payload_building.go index 8cbab3042e..eef8e62021 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -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. @@ -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]) @@ -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 { @@ -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 diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index eea55e734a..5f171015b7 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool" @@ -53,6 +54,9 @@ var ( testUserKey, _ = crypto.GenerateKey() testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) + testRecipient = common.HexToAddress("0xdeadbeef") + testTimestamp = uint64(time.Now().Unix()) + // Test transactions pendingTxs []*types.Transaction newTxs []*types.Transaction @@ -147,20 +151,75 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens } func TestBuildPayload(t *testing.T) { - t.Run("no-tx-pool", func(t *testing.T) { testBuildPayload(t, true, false) }) + t.Run("no-tx-pool", func(t *testing.T) { testBuildPayload(t, true, false, nil) }) // no-tx-pool case with interrupt not interesting because no-tx-pool doesn't run // the builder routine - t.Run("with-tx-pool", func(t *testing.T) { testBuildPayload(t, false, false) }) - t.Run("with-tx-pool-interrupt", func(t *testing.T) { testBuildPayload(t, false, true) }) + t.Run("with-tx-pool", func(t *testing.T) { testBuildPayload(t, false, false, nil) }) + t.Run("with-tx-pool-interrupt", func(t *testing.T) { testBuildPayload(t, false, true, nil) }) + nonce := types.BlockNonce([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + t.Run("with-nonce", func(t *testing.T) { testBuildPayload(t, false, false, &nonce) }) + t.Run("with-nonce-no-tx-pool", func(t *testing.T) { testBuildPayload(t, true, false, &nonce) }) + t.Run("with-nonce-interrupt", func(t *testing.T) { testBuildPayload(t, false, true, &nonce) }) + + t.Run("wrong-config-no-nonce", func(t *testing.T) { testBuildPayloadWrongConfig(t, nil) }) + t.Run("wrong-config-nonce", func(t *testing.T) { testBuildPayloadWrongConfig(t, &nonce) }) + + var zeroNonce types.BlockNonce + t.Run("with-zero-nonce", func(t *testing.T) { testBuildPayload(t, true, false, &zeroNonce) }) +} + +func holoceneConfig() *params.ChainConfig { + config := *params.TestChainConfig + config.LondonBlock = big.NewInt(0) + t := uint64(0) + config.CanyonTime = &t + config.HoloceneTime = &t + canyonDenom := uint64(250) + config.Optimism = ¶ms.OptimismConfig{ + EIP1559Elasticity: 6, + EIP1559Denominator: 50, + EIP1559DenominatorCanyon: &canyonDenom, + } + return &config +} + +// newPayloadArgs returns a BuildPaylooadArgs with the given parentHash and nonce, testTimestamp +// for Timestamp, and testRecipient for recipient. NoTxPool is set to true. +func newPayloadArgs(parentHash common.Hash, nonce *types.BlockNonce) *BuildPayloadArgs { + return &BuildPayloadArgs{ + Parent: parentHash, + Timestamp: testTimestamp, + Random: common.Hash{}, + FeeRecipient: testRecipient, + NoTxPool: true, + EIP1559Params: nonce, + } } -func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { +func TestBuildPayloadInvalidHoloceneNonce(t *testing.T) { t.Parallel() - var ( - db = rawdb.NewMemoryDatabase() - recipient = common.HexToAddress("0xdeadbeef") - ) - w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0) + db := rawdb.NewMemoryDatabase() + config := holoceneConfig() + w, b := newTestWorker(t, config, ethash.NewFaker(), db, 0) + + // 0 denominators shouldn't be allowed + badNonce := eip1559.EncodeHolocene1559Params(6, 0) + + args := newPayloadArgs(b.chain.CurrentBlock().Hash(), &badNonce) + payload, err := w.buildPayload(args, false) + if err == nil && (payload == nil || payload.err == nil) { + t.Fatalf("expected error, got none") + } +} + +func testBuildPayload(t *testing.T, noTxPool, interrupt bool, nonce *types.BlockNonce) { + t.Parallel() + db := rawdb.NewMemoryDatabase() + config := params.TestChainConfig + if nonce != nil { + config = holoceneConfig() + } + w, b := newTestWorker(t, config, ethash.NewFaker(), db, 0) const numInterruptTxs = 256 if interrupt { @@ -170,14 +229,9 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { b.txPool.Add(txs, true, false) } - timestamp := uint64(time.Now().Unix()) - args := &BuildPayloadArgs{ - Parent: b.chain.CurrentBlock().Hash(), - Timestamp: timestamp, - Random: common.Hash{}, - FeeRecipient: recipient, - NoTxPool: noTxPool, - } + args := newPayloadArgs(b.chain.CurrentBlock().Hash(), nonce) + args.NoTxPool = noTxPool + // payload resolution now interrupts block building, so we have to // wait for the payloading building process to build its first block payload, err := w.buildPayload(args, false) @@ -196,10 +250,10 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { if payload.Random != (common.Hash{}) { t.Fatal("Unexpected random value") } - if payload.Timestamp != timestamp { + if payload.Timestamp != testTimestamp { t.Fatal("Unexpected timestamp") } - if payload.FeeRecipient != recipient { + if payload.FeeRecipient != testRecipient { t.Fatal("Unexpected fee recipient") } if !interrupt && len(payload.Transactions) != txs { @@ -209,6 +263,23 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { } } + // make sure the nonce we've specied (if any) ends up in both the full and empty block headers + var expected types.BlockNonce + if nonce != nil { + if *nonce == expected { + expected = eip1559.EncodeHolocene1559Params(6, 250) // canyon defaults + } else { + expected = *nonce + } + } + t.Logf("expected nonce: %x\n", expected[:]) + if payload.full != nil && payload.full.Header().Nonce != expected { + t.Fatalf("Nonces don't match. want: %x, got %x", expected, payload.full.Header().Nonce) + } + if payload.empty != nil && payload.empty.Header().Nonce != expected { + t.Fatalf("Nonces don't match on empty block. want: %x, got %x", expected, payload.empty.Header().Nonce) + } + if noTxPool { // we only build the empty block when ignoring the tx pool empty := payload.ResolveEmpty() @@ -233,6 +304,23 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { } } +func testBuildPayloadWrongConfig(t *testing.T, nonce *types.BlockNonce) { + t.Parallel() + db := rawdb.NewMemoryDatabase() + config := holoceneConfig() + if nonce != nil { + // deactivate holocene and make sure non-nil nonce gets rejected + config.HoloceneTime = nil + } + w, b := newTestWorker(t, config, ethash.NewFaker(), db, 0) + + args := newPayloadArgs(b.chain.CurrentBlock().Hash(), nonce) + payload, err := w.buildPayload(args, false) + if err == nil && (payload == nil || payload.err == nil) { + t.Fatalf("expected error, got none") + } +} + func genTxs(startNonce, count uint64) types.Transactions { txs := make(types.Transactions, 0, count) signer := types.LatestSigner(params.TestChainConfig) diff --git a/miner/worker.go b/miner/worker.go index 0146700283..e46fcca21b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -107,10 +107,11 @@ type generateParams struct { beaconRoot *common.Hash // The beacon root (cancun field). noTxs bool // Flag whether an empty block without any transaction is expected - txs types.Transactions // Deposit transactions to include at the start of the block - gasLimit *uint64 // Optional gas limit override - interrupt *atomic.Int32 // Optional interruption signal to pass down to worker.generateWork - isUpdate bool // Optional flag indicating that this is building a discardable update + txs types.Transactions // Deposit transactions to include at the start of the block + gasLimit *uint64 // Optional gas limit override + eip1559Params *types.BlockNonce // Optional EIP-1559 parameters + interrupt *atomic.Int32 // Optional interruption signal to pass down to worker.generateWork + isUpdate bool // Optional flag indicating that this is building a discardable update } // generateWork generates a sealing block based on the given parameters. @@ -249,6 +250,25 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir // configure the gas limit of pending blocks with the miner gas limit config when using optimism header.GasLimit = miner.config.GasCeil } + if miner.chainConfig.IsHolocene(header.Time) { + if genParams.eip1559Params == nil { + return nil, errors.New("expected eip1559 params, got none") + } + header.Nonce = *genParams.eip1559Params + // If this is a holocene block and the params are 0, we must convert them to their Canyon + // defaults in the header. + if header.Nonce == types.BlockNonce([8]byte{}) { + elasticity := miner.chainConfig.ElasticityMultiplier() + denominator := miner.chainConfig.BaseFeeChangeDenominator(header.Time) + header.Nonce = eip1559.EncodeHolocene1559Params(uint32(elasticity), uint32(denominator)) + } + _, d := eip1559.DecodeHolocene1559Params(header.Nonce) + if d == 0 { + return nil, errors.New("got a 0 eip1559 denominator, which is not allowed") + } + } else if genParams.eip1559Params != nil { + return nil, errors.New("got eip1559 params, expected none") + } // Run the consensus preparation with the default or customized consensus engine. // Note that the `header.Time` may be changed. if err := miner.engine.Prepare(miner.chain, header); err != nil {