From f42560029ebafa32a46d0c4aa96a14e30ced93cb Mon Sep 17 00:00:00 2001 From: Cal Bera Date: Wed, 10 Sep 2025 10:06:36 -0700 Subject: [PATCH] feat(forks): prague2 for new minimum base fee --- consensus/misc/eip1559/eip1559.go | 8 ++-- core/block_validator_test.go | 2 +- core/forkid/forkid.go | 5 +- eth/catalyst/api.go | 4 +- eth/catalyst/simulated_beacon.go | 2 +- eth/catalyst/witness.go | 6 +-- params/config.go | 77 ++++++++++++++++++++++++++++--- params/forks/forks.go | 2 + 8 files changed, 86 insertions(+), 20 deletions(-) diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index f6b5af915f..fcba5f2056 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -57,11 +57,9 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header) *big.Int { calculatedBaseFee := calcBaseFee(config, parent) // Starting at the Prague1 fork, the base fee must be at least the minimum base fee. - if config.IsPrague1(parent.Number, parent.Time) { - minBaseFee := new(big.Int).SetUint64(config.Berachain.Prague1.MinimumBaseFeeWei) - if calculatedBaseFee.Cmp(minBaseFee) < 0 { - calculatedBaseFee = minBaseFee - } + minBaseFee := new(big.Int).SetUint64(config.MinBaseFee(parent.Number, parent.Time)) + if calculatedBaseFee.Cmp(minBaseFee) < 0 { + calculatedBaseFee = minBaseFee } return calculatedBaseFee diff --git a/core/block_validator_test.go b/core/block_validator_test.go index ba6de304c9..492df7436d 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -34,7 +34,7 @@ import ( ) // ----------------------------------------------------------------------------- -// Berachain:Prague-1 PoL validation tests +// Berachain: Prague-1 PoL validation tests // ----------------------------------------------------------------------------- // newPrague1Config returns a ChainConfig where Prague (and Prague-1) are active diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 113cc5362d..3c07d5b0c5 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -268,11 +268,14 @@ func gatherForks(config *params.ChainConfig, genesis uint64) ([]uint64, []uint64 } } } - // Berachain-specific: include nested Prague1 fork time in the fork id set + // Berachain-specific: include nested Prague1 and Prague2 fork times in the fork id set // so that clients using it (e.g. reth) compute the same fork checksum. if config != nil && config.Berachain.Prague1.Time != nil { forksByTime = append(forksByTime, *config.Berachain.Prague1.Time) } + if config != nil && config.Berachain.Prague2.Time != nil { + forksByTime = append(forksByTime, *config.Berachain.Prague2.Time) + } slices.Sort(forksByBlock) slices.Sort(forksByTime) diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index f677ccd137..94e66b0f3d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -241,7 +241,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3P11(update engine.ForkchoiceStateV1, 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, forks.Osaka): + case !api.checkFork(params.Timestamp, forks.Prague1, forks.Prague2, forks.Osaka): return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads") } } @@ -700,7 +700,7 @@ func (api *ConsensusAPI) NewPayloadV4P11(params engine.ExecutableData, versioned return invalidStatus, paramsErr("nil executionRequests post-prague") case proposerPubkey == nil: return invalidStatus, paramsErr("nil proposerPubkey post-prague1") - case !api.checkFork(params.Timestamp, forks.Prague1, forks.Osaka): + case !api.checkFork(params.Timestamp, forks.Prague1, forks.Prague2, forks.Osaka): return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads") } requests := convertRequests(executionRequests) diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index dd46fcd43d..5a144c8027 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -100,7 +100,7 @@ type SimulatedBeacon struct { func payloadVersion(config *params.ChainConfig, time uint64) engine.PayloadVersion { switch config.LatestFork(time) { - case forks.Prague1: + case forks.Prague1, forks.Prague2: return engine.PayloadV3P11 case forks.Prague, forks.Cancun: return engine.PayloadV3 diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 48e8c9ca28..4591e10849 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -101,7 +101,7 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3P11(update engine.Forkcho 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, forks.Osaka): + case !api.checkFork(params.Timestamp, forks.Prague1, forks.Prague2, forks.Osaka): return engine.STATUS_INVALID, unsupportedForkErr("fcuV3P11 must only be called for prague1 payloads") } } @@ -207,7 +207,7 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4P11(params engine.ExecutableData return invalidStatus, paramsErr("nil executionRequests post-prague") case proposerPubkey == nil: return invalidStatus, paramsErr("nil proposerPubkey post-prague1") - case !api.checkFork(params.Timestamp, forks.Prague1, forks.Osaka): + case !api.checkFork(params.Timestamp, forks.Prague1, forks.Prague2, forks.Osaka): return invalidStatus, unsupportedForkErr("newPayloadV4P11 must only be called for prague1 payloads") } requests := convertRequests(executionRequests) @@ -312,7 +312,7 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4P11(params engine.ExecutableDa 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, forks.Osaka): + case !api.checkFork(params.Timestamp, forks.Prague1, forks.Prague2, forks.Osaka): return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("executeStatelessPayloadV4P11 must only be called for prague1 payloads") } requests := convertRequests(executionRequests) diff --git a/params/config.go b/params/config.go index 71a3b785a9..7ef189d53c 100644 --- a/params/config.go +++ b/params/config.go @@ -198,6 +198,10 @@ var ( BaseFeeChangeDenominator: BerachainBaseFeeChangeDenominator, PoLDistributorAddress: PoLDistributorAddress, }, + Prague2: Prague2Config{ + Time: newUint64(9999999999), // TODO(Prague2): Replace with actual time. + MinimumBaseFeeWei: 0, + }, }, } // BepoliaChainConfig contains the chain parameters to run a node on the Bepolia test network. @@ -236,6 +240,10 @@ var ( BaseFeeChangeDenominator: BerachainBaseFeeChangeDenominator, PoLDistributorAddress: PoLDistributorAddress, }, + Prague2: Prague2Config{ + Time: newUint64(9999999999), // TODO(Prague2): Replace with actual time. + MinimumBaseFeeWei: 0, + }, }, } // AllEthashProtocolChanges contains every protocol change (EIPs) introduced @@ -540,6 +548,9 @@ type ChainConfig struct { type BerachainConfig struct { // Prague1 fork values. Prague1 Prague1Config `json:"prague1,omitempty"` + + // Prague2 fork values. + Prague2 Prague2Config `json:"prague2,omitempty"` } // String implements the stringer interface. @@ -548,6 +559,9 @@ func (o *BerachainConfig) String() string { if o.Prague1.Time != nil { banner += fmt.Sprintf("(%s)", o.Prague1) } + if o.Prague2.Time != nil { + banner += fmt.Sprintf("(%s)", o.Prague2) + } return banner } @@ -575,6 +589,23 @@ func (c Prague1Config) String() string { return banner } +// Prague2Config is the config values for the Prague2 fork on Berachain. +type Prague2Config struct { + // Time is the time of the Prague2 fork. + Time *uint64 `json:"time,omitempty"` // Prague2 switch time (0 = already on prague2, nil = no fork) + // MinimumBaseFeeWei is the minimum base fee in wei. + MinimumBaseFeeWei uint64 `json:"minimumBaseFeeWei,omitempty"` +} + +// String implements the stringer interface. +func (c Prague2Config) String() string { + banner := "prague2" + if c.Time != nil { + banner += fmt.Sprintf("(time: %v, minimumBaseFeeWei: %v)", *c.Time, c.MinimumBaseFeeWei) + } + return banner +} + // EthashConfig is the consensus engine configs for proof-of-work based sealing. type EthashConfig struct{} @@ -666,6 +697,9 @@ func (c *ChainConfig) Description() string { if c.Berachain.Prague1.Time != nil { banner += fmt.Sprintf(" - Prague1: %-10v (https://github.com/berachain/BRIPs/blob/main/meta/BRIP-0004.md)\n", c.Berachain.Prague1) } + if c.Berachain.Prague2.Time != nil { + banner += fmt.Sprintf(" - Prague2: %-10v\n", c.Berachain.Prague2) + } if c.OsakaTime != nil { banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime) } @@ -811,6 +845,12 @@ func (c *ChainConfig) IsPrague1(num *big.Int, time uint64) bool { return c.IsPrague(num, time) && isTimestampForked(c.Berachain.Prague1.Time, time) } +// IsPrague2 returns whether time is either equal to the Prague2 fork time or greater. +// NOTE: Prague2 is a Berachain fork and must be on Ethereum's Prague fork. +func (c *ChainConfig) IsPrague2(num *big.Int, time uint64) bool { + return c.IsPrague(num, time) && isTimestampForked(c.Berachain.Prague2.Time, time) +} + // IsOsaka returns whether time is either equal to the Osaka fork time or greater. func (c *ChainConfig) IsOsaka(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.OsakaTime, time) @@ -920,6 +960,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "cancunTime", timestamp: c.CancunTime, optional: true}, {name: "pragueTime", timestamp: c.PragueTime, optional: true}, {name: "prague1Time", timestamp: c.Berachain.Prague1.Time, optional: true}, + {name: "prague2Time", timestamp: c.Berachain.Prague2.Time, optional: true}, {name: "osakaTime", timestamp: c.OsakaTime, optional: true}, {name: "verkleTime", timestamp: c.VerkleTime, optional: true}, {name: "bpo1", timestamp: c.BPO1Time, optional: true}, @@ -1088,6 +1129,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.Berachain.Prague1.Time, newcfg.Berachain.Prague1.Time, headTimestamp) { return newTimestampCompatError("Prague1 fork timestamp", c.Berachain.Prague1.Time, newcfg.Berachain.Prague1.Time) } + if isForkTimestampIncompatible(c.Berachain.Prague2.Time, newcfg.Berachain.Prague2.Time, headTimestamp) { + return newTimestampCompatError("Prague2 fork timestamp", c.Berachain.Prague2.Time, newcfg.Berachain.Prague2.Time) + } if isForkTimestampIncompatible(c.OsakaTime, newcfg.OsakaTime, headTimestamp) { return newTimestampCompatError("Osaka fork timestamp", c.OsakaTime, newcfg.OsakaTime) } @@ -1122,6 +1166,17 @@ func (c *ChainConfig) BaseFeeChangeDenominator(num *big.Int, time uint64) uint64 return DefaultBaseFeeChangeDenominator } +// MinBaseFee returns the minimum base fee (in wei) based on the active fork. +func (c *ChainConfig) MinBaseFee(num *big.Int, time uint64) uint64 { + if c.IsPrague2(num, time) { + return c.Berachain.Prague2.MinimumBaseFeeWei + } + if c.IsPrague1(num, time) { + return c.Berachain.Prague1.MinimumBaseFeeWei + } + return 0 +} + // ElasticityMultiplier bounds the maximum gas limit an EIP-1559 block may have. func (c *ChainConfig) ElasticityMultiplier() uint64 { return DefaultElasticityMultiplier @@ -1135,6 +1190,8 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { switch { case c.IsOsaka(london, time): return forks.Osaka + case c.IsPrague2(london, time): + return forks.Prague2 case c.IsPrague1(london, time): return forks.Prague1 case c.IsPrague(london, time): @@ -1153,6 +1210,8 @@ func (c *ChainConfig) BlobConfig(fork forks.Fork) *BlobConfig { switch fork { case forks.Osaka: return DefaultOsakaBlobConfig + case forks.Prague1, forks.Prague2: + return DefaultBerachainPragueBlobConfig case forks.Prague: return DefaultPragueBlobConfig case forks.Cancun: @@ -1170,6 +1229,7 @@ func (c *ChainConfig) ActiveSystemContracts(time uint64) map[string]common.Addre if fork >= forks.Osaka { // no new system contracts } + // TODO(BRIP-4): Add PoL distributor address to the system contracts? if fork >= forks.Prague { active["CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS"] = ConsolidationQueueAddress active["DEPOSIT_CONTRACT_ADDRESS"] = c.DepositContractAddress @@ -1188,6 +1248,8 @@ func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 { switch { case fork == forks.Osaka: return c.OsakaTime + case fork == forks.Prague2: + return c.Berachain.Prague2.Time case fork == forks.Prague1: return c.Berachain.Prague1.Time case fork == forks.Prague: @@ -1336,13 +1398,13 @@ func (err *ConfigCompatError) Error() string { // Rules is a one time interface meaning that it shouldn't be used in between transition // phases. type Rules struct { - ChainID *big.Int - IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool - IsEIP2929, IsEIP4762 bool - IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool - IsBerlin, IsLondon bool - IsMerge, IsShanghai, IsCancun, IsPrague, IsPrague1, IsOsaka bool - IsVerkle bool + ChainID *big.Int + IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool + IsEIP2929, IsEIP4762 bool + IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool + IsBerlin, IsLondon bool + IsMerge, IsShanghai, IsCancun, IsPrague, IsPrague1, IsPrague2, IsOsaka bool + IsVerkle bool } // Rules ensures c's ChainID is not nil. @@ -1372,6 +1434,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), IsPrague1: isMerge && c.IsPrague1(num, timestamp), + IsPrague2: isMerge && c.IsPrague2(num, timestamp), IsOsaka: isMerge && c.IsOsaka(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, diff --git a/params/forks/forks.go b/params/forks/forks.go index e4075b3ca3..4cf86cbebd 100644 --- a/params/forks/forks.go +++ b/params/forks/forks.go @@ -40,6 +40,7 @@ const ( Cancun Prague Prague1 + Prague2 Osaka ) @@ -73,5 +74,6 @@ var forkToString = map[Fork]string{ Cancun: "Cancun", Prague: "Prague", Prague1: "Prague1", + Prague2: "Prague2", Osaka: "Osaka", }