feat: update base fee via cli (#1183)

* feat: update base fee via cli

* fix

* improve comments
This commit is contained in:
Péter Garamvölgyi 2025-05-14 12:41:19 +02:00 committed by GitHub
parent 3edb331d88
commit 0b6f8f1889
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 157 additions and 14 deletions

View file

@ -185,6 +185,8 @@ var (
utils.DARecoverySignBlocksFlag, utils.DARecoverySignBlocksFlag,
utils.DARecoveryL2EndBlockFlag, utils.DARecoveryL2EndBlockFlag,
utils.DARecoveryProduceBlocksFlag, utils.DARecoveryProduceBlocksFlag,
utils.L2BaseFeeScalarFlag,
utils.L2BaseFeeOverheadFlag,
} }
rpcFlags = []cli.Flag{ rpcFlags = []cli.Flag{

View file

@ -161,6 +161,7 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalEVMTimeoutFlag,
utils.RPCGlobalTxFeeCapFlag, utils.RPCGlobalTxFeeCapFlag,
utils.AllowUnprotectedTxs, utils.AllowUnprotectedTxs,
utils.MaxBlockRangeFlag,
utils.JSpathFlag, utils.JSpathFlag,
utils.ExecFlag, utils.ExecFlag,
utils.PreloadJSFlag, utils.PreloadJSFlag,
@ -227,6 +228,30 @@ var AppHelpFlagGroups = []flags.FlagGroup{
Name: "METRICS AND STATS", Name: "METRICS AND STATS",
Flags: metricsFlags, Flags: metricsFlags,
}, },
{
Name: "ROLLUP",
Flags: []cli.Flag{
utils.L1EndpointFlag,
utils.L1ConfirmationsFlag,
utils.L1DeploymentBlockFlag,
utils.L1DisableMessageQueueV2Flag,
utils.RollupVerifyEnabledFlag,
utils.L2BaseFeeScalarFlag,
utils.L2BaseFeeOverheadFlag,
utils.DASyncEnabledFlag,
utils.DABlobScanAPIEndpointFlag,
utils.DABlockNativeAPIEndpointFlag,
utils.DABeaconNodeAPIEndpointFlag,
utils.DARecoveryModeFlag,
utils.DARecoveryInitialL1BlockFlag,
utils.DARecoveryInitialBatchFlag,
utils.DARecoverySignBlocksFlag,
utils.DARecoveryL2EndBlockFlag,
utils.DARecoveryProduceBlocksFlag,
utils.CircuitCapacityCheckEnabledFlag,
utils.CircuitCapacityCheckWorkersFlag,
},
},
{ {
Name: "ALIASED (deprecated)", Name: "ALIASED (deprecated)",
Flags: []cli.Flag{ Flags: []cli.Flag{

View file

@ -47,6 +47,7 @@ import (
"github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/clique"
"github.com/scroll-tech/go-ethereum/consensus/ethash" "github.com/scroll-tech/go-ethereum/consensus/ethash"
"github.com/scroll-tech/go-ethereum/consensus/misc"
"github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core"
"github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/core/vm"
@ -934,6 +935,18 @@ var (
Name: "da.recovery.produceblocks", Name: "da.recovery.produceblocks",
Usage: "Produce unsigned blocks after L1 recovery for permissionless batch submission", Usage: "Produce unsigned blocks after L1 recovery for permissionless batch submission",
} }
// L2 base fee settings
L2BaseFeeScalarFlag = BigFlag{
Name: "basefee.scalar",
Usage: "Scalar used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.",
Value: misc.DefaultBaseFeeScalar,
}
L2BaseFeeOverheadFlag = BigFlag{
Name: "basefee.overhead",
Usage: "Overhead used in the l2 base fee formula. Signer nodes will use this for computing the next block's base fee. Follower nodes will use this in RPC.",
Value: misc.DefaultBaseFeeOverhead,
}
) )
// MakeDataDir retrieves the currently requested data directory, terminating // MakeDataDir retrieves the currently requested data directory, terminating
@ -1709,6 +1722,29 @@ func setDA(ctx *cli.Context, cfg *ethconfig.Config) {
} }
} }
func setBaseFee(ctx *cli.Context, cfg *ethconfig.Config) {
cfg.BaseFeeScalar = misc.DefaultBaseFeeScalar
if ctx.GlobalIsSet(L2BaseFeeScalarFlag.Name) {
cfg.BaseFeeScalar = GlobalBig(ctx, L2BaseFeeScalarFlag.Name)
}
cfg.BaseFeeOverhead = misc.DefaultBaseFeeOverhead
if ctx.GlobalIsSet(L2BaseFeeOverheadFlag.Name) {
cfg.BaseFeeOverhead = GlobalBig(ctx, L2BaseFeeOverheadFlag.Name)
}
log.Info("L2 base fee coefficients", "scalar", cfg.BaseFeeScalar, "overhead", cfg.BaseFeeOverhead)
var minBaseFee uint64
if fee := misc.MinBaseFee(cfg.BaseFeeScalar, cfg.BaseFeeOverhead); fee.IsUint64() {
minBaseFee = fee.Uint64()
}
if cfg.TxPool.PriceLimit < minBaseFee {
log.Warn("Updating txpool price limit to min L2 base fee", "provided", cfg.TxPool.PriceLimit, "updated", minBaseFee)
cfg.TxPool.PriceLimit = minBaseFee
}
}
func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) { func setMaxBlockRange(ctx *cli.Context, cfg *ethconfig.Config) {
if ctx.GlobalIsSet(MaxBlockRangeFlag.Name) { if ctx.GlobalIsSet(MaxBlockRangeFlag.Name) {
cfg.MaxBlockRange = ctx.GlobalInt64(MaxBlockRangeFlag.Name) cfg.MaxBlockRange = ctx.GlobalInt64(MaxBlockRangeFlag.Name)
@ -1785,6 +1821,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
setCircuitCapacityCheck(ctx, cfg) setCircuitCapacityCheck(ctx, cfg)
setEnableRollupVerify(ctx, cfg) setEnableRollupVerify(ctx, cfg)
setDA(ctx, cfg) setDA(ctx, cfg)
setBaseFee(ctx, cfg)
setMaxBlockRange(ctx, cfg) setMaxBlockRange(ctx, cfg)
if ctx.GlobalIsSet(ShadowforkPeersFlag.Name) { if ctx.GlobalIsSet(ShadowforkPeersFlag.Name) {
cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name) cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name)

View file

@ -28,6 +28,16 @@ import (
// We would only go above this if L1 base fee hits 2931 Gwei. // We would only go above this if L1 base fee hits 2931 Gwei.
const MaximumL2BaseFee = 10000000000 const MaximumL2BaseFee = 10000000000
// L2 base fee formula constants and defaults.
// l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead.
// `scalar` accounts for finalization costs. `overhead` accounts for sequencing and proving costs.
// we use 1e18 for precision to match the contract implementation.
var (
BaseFeePrecision = new(big.Int).SetUint64(1e18)
DefaultBaseFeeScalar = new(big.Int).SetUint64(34000000000000)
DefaultBaseFeeOverhead = new(big.Int).SetUint64(15680000)
)
// VerifyEip1559Header verifies some header attributes which were changed in EIP-1559, // VerifyEip1559Header verifies some header attributes which were changed in EIP-1559,
// - gas limit check // - gas limit check
// - basefee check // - basefee check
@ -54,18 +64,29 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, parentL1BaseF
if config.Clique != nil && config.Clique.ShadowForkHeight != 0 && parent.Number.Uint64() >= config.Clique.ShadowForkHeight { if config.Clique != nil && config.Clique.ShadowForkHeight != 0 && parent.Number.Uint64() >= config.Clique.ShadowForkHeight {
return big.NewInt(10000000) // 0.01 Gwei return big.NewInt(10000000) // 0.01 Gwei
} }
l2SequencerFee := big.NewInt(1000000) // 0.001 Gwei
provingFee := big.NewInt(14680000) // 0.01468 Gwei
// L1_base_fee * 0.000034 scalar := config.Scroll.BaseFeeScalar
verificationFee := parentL1BaseFee if scalar == nil {
verificationFee = new(big.Int).Mul(verificationFee, big.NewInt(34)) scalar = DefaultBaseFeeScalar
verificationFee = new(big.Int).Div(verificationFee, big.NewInt(1000000)) }
overhead := config.Scroll.BaseFeeOverhead
if overhead == nil {
overhead = DefaultBaseFeeOverhead
}
baseFee := big.NewInt(0) return calcBaseFee(scalar, overhead, parentL1BaseFee)
baseFee.Add(baseFee, l2SequencerFee) }
baseFee.Add(baseFee, provingFee)
baseFee.Add(baseFee, verificationFee) // MinBaseFee calculates the minimum L2 base fee based on the configured coefficients.
func MinBaseFee(scalar, overhead *big.Int) *big.Int {
return calcBaseFee(scalar, overhead, big.NewInt(0))
}
func calcBaseFee(scalar, overhead, parentL1BaseFee *big.Int) *big.Int {
baseFee := new(big.Int).Set(parentL1BaseFee)
baseFee.Mul(baseFee, scalar)
baseFee.Div(baseFee, BaseFeePrecision)
baseFee.Add(baseFee, overhead)
if baseFee.Cmp(big.NewInt(MaximumL2BaseFee)) > 0 { if baseFee.Cmp(big.NewInt(MaximumL2BaseFee)) > 0 {
baseFee = big.NewInt(MaximumL2BaseFee) baseFee = big.NewInt(MaximumL2BaseFee)

View file

@ -111,6 +111,27 @@ func TestCalcBaseFee(t *testing.T) {
tests := []struct { tests := []struct {
parentL1BaseFee int64 parentL1BaseFee int64
expectedL2BaseFee int64 expectedL2BaseFee int64
}{
{0, 1},
{1000000000, 1},
{2000000000, 1},
{100000000000, 2},
{111111111111, 2},
{2164000000000, 22},
{644149677419355, 6442},
}
for i, test := range tests {
config := config()
config.Scroll.BaseFeeScalar = big.NewInt(10000000)
config.Scroll.BaseFeeOverhead = big.NewInt(1)
if have, want := CalcBaseFee(config, nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 {
t.Errorf("test %d: have %d want %d, ", i, have, want)
}
}
testsWithDefaults := []struct {
parentL1BaseFee int64
expectedL2BaseFee int64
}{ }{
{0, 15680000}, {0, 15680000},
{1000000000, 15714000}, {1000000000, 15714000},
@ -120,9 +141,20 @@ func TestCalcBaseFee(t *testing.T) {
{2164000000000, 89256000}, {2164000000000, 89256000},
{644149677419355, 10000000000}, // cap at max L2 base fee {644149677419355, 10000000000}, // cap at max L2 base fee
} }
for i, test := range tests { for i, test := range testsWithDefaults {
if have, want := CalcBaseFee(config(), nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 { if have, want := CalcBaseFee(config(), nil, big.NewInt(test.parentL1BaseFee)), big.NewInt(test.expectedL2BaseFee); have.Cmp(want) != 0 {
t.Errorf("test %d: have %d want %d, ", i, have, want) t.Errorf("test %d: have %d want %d, ", i, have, want)
} }
} }
} }
// TestMinBaseFee assumes all blocks are 1559-blocks
func TestMinBaseFee(t *testing.T) {
if have, want := MinBaseFee(DefaultBaseFeeScalar, DefaultBaseFeeOverhead), big.NewInt(15680000); have.Cmp(want) != 0 {
t.Errorf("have %d want %d, ", have, want)
}
if have, want := MinBaseFee(big.NewInt(10000000), big.NewInt(1)), big.NewInt(1); have.Cmp(want) != 0 {
t.Errorf("have %d want %d, ", have, want)
}
}

View file

@ -148,6 +148,13 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok {
return nil, genesisErr return nil, genesisErr
} }
// Hacky workaround:
// It's hard to pass these fields to `CalcBaseFee`, etc.
// So pass them as part of the genesis config instead.
chainConfig.Scroll.BaseFeeScalar = config.BaseFeeScalar
chainConfig.Scroll.BaseFeeOverhead = config.BaseFeeOverhead
log.Info("Initialised chain configuration", "config", chainConfig) log.Info("Initialised chain configuration", "config", chainConfig)
if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil {

View file

@ -230,6 +230,10 @@ type Config struct {
// DA syncer options // DA syncer options
DA da_syncer.Config DA da_syncer.Config
// L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`.
BaseFeeScalar *big.Int
BaseFeeOverhead *big.Int
} }
// CreateConsensusEngine creates a consensus engine for the given chain configuration. // CreateConsensusEngine creates a consensus engine for the given chain configuration.

View file

@ -704,6 +704,11 @@ type ScrollConfig struct {
// Genesis State Root for MPT clients // Genesis State Root for MPT clients
GenesisStateRoot *common.Hash `json:"genesisStateRoot,omitempty"` GenesisStateRoot *common.Hash `json:"genesisStateRoot,omitempty"`
// L2 base fee coefficients for the formula: `l2BaseFee = (l1BaseFee * scalar) / PRECISION + overhead`.
// These fields are populated from configuration flags, and passed to `CalcBaseFee`.
BaseFeeScalar *big.Int `json:"-"`
BaseFeeOverhead *big.Int `json:"-"`
} }
// L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer // L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer
@ -753,8 +758,18 @@ func (s ScrollConfig) String() string {
genesisStateRoot = fmt.Sprintf("%v", *s.GenesisStateRoot) genesisStateRoot = fmt.Sprintf("%v", *s.GenesisStateRoot)
} }
return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v}", baseFeeScalar := "<nil>"
s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot) if s.BaseFeeScalar != nil {
baseFeeScalar = s.BaseFeeScalar.String()
}
baseFeeOverhead := "<nil>"
if s.BaseFeeOverhead != nil {
baseFeeOverhead = s.BaseFeeOverhead.String()
}
return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, l1Config: %v, genesisStateRoot: %v, baseFeeScalar: %v, baseFeeOverhead: %v}",
s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.L1Config.String(), genesisStateRoot, baseFeeScalar, baseFeeOverhead)
} }
// IsValidTxCount returns whether the given block's transaction count is below the limit. // IsValidTxCount returns whether the given block's transaction count is below the limit.

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release VersionMinor = 8 // Minor version component of the current release
VersionPatch = 44 // Patch version component of the current release VersionPatch = 45 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string VersionMeta = "mainnet" // Version metadata to append to the version string
) )