Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ var (
utils.MinerRecommitIntervalFlag,
utils.MinerPendingFeeRecipientFlag,
utils.MinerNewPayloadTimeoutFlag, // deprecated
utils.MinerPIDEnabledFlag,
utils.MinerPIDKpFlag,
utils.MinerPIDKiFlag,
utils.MinerPIDKdFlag,
utils.MinerPIDMaxChangeFlag,
utils.NATFlag,
utils.NoDiscoverFlag,
utils.DiscoveryV4Flag,
Expand Down
49 changes: 49 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,38 @@ var (
Category: flags.MinerCategory,
}

// PID Controller settings
MinerPIDEnabledFlag = &cli.BoolFlag{
Name: "miner.pid.enabled",
Usage: "Enable PID controller for advanced fee management",
Value: true,
Category: flags.MinerCategory,
}
MinerPIDKpFlag = &cli.Float64Flag{
Name: "miner.pid.kp",
Usage: "PID controller proportional gain (0.1-10.0)",
Value: 1.8,
Category: flags.MinerCategory,
}
MinerPIDKiFlag = &cli.Float64Flag{
Name: "miner.pid.ki",
Usage: "PID controller integral gain (0.01-1.0)",
Value: 0.12,
Category: flags.MinerCategory,
}
MinerPIDKdFlag = &cli.Float64Flag{
Name: "miner.pid.kd",
Usage: "PID controller derivative gain (0.1-2.0)",
Value: 0.35,
Category: flags.MinerCategory,
}
MinerPIDMaxChangeFlag = &cli.Float64Flag{
Name: "miner.pid.maxchange",
Usage: "PID controller maximum fee change per block (0.01-0.5)",
Value: 0.12,
Category: flags.MinerCategory,
}

// Account settings
PasswordFileFlag = &cli.PathFlag{
Name: "password",
Expand Down Expand Up @@ -1711,6 +1743,23 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(RollupComputePendingBlock.Name) {
cfg.RollupComputePendingBlock = ctx.Bool(RollupComputePendingBlock.Name)
}

// PID Controller configuration
if ctx.IsSet(MinerPIDEnabledFlag.Name) {
cfg.PIDEnabled = ctx.Bool(MinerPIDEnabledFlag.Name)
}
if ctx.IsSet(MinerPIDKpFlag.Name) {
cfg.PIDKp = ctx.Float64(MinerPIDKpFlag.Name)
}
if ctx.IsSet(MinerPIDKiFlag.Name) {
cfg.PIDKi = ctx.Float64(MinerPIDKiFlag.Name)
}
if ctx.IsSet(MinerPIDKdFlag.Name) {
cfg.PIDKd = ctx.Float64(MinerPIDKdFlag.Name)
}
if ctx.IsSet(MinerPIDMaxChangeFlag.Name) {
cfg.PIDMaxChange = ctx.Float64(MinerPIDMaxChangeFlag.Name)
}
}

func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {
Expand Down
80 changes: 80 additions & 0 deletions consensus/misc/eip1559/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,20 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)

// PIDController defines the interface for PID-based base fee calculation.
// This interface allows for different PID controller implementations while
// maintaining compatibility with the existing EIP-1559 base fee calculation.
type PIDController interface {
// CalculateBaseFee computes the base fee using PID control algorithm
CalculateBaseFee(gasUsed uint64, currentBaseFee *big.Int, parentHeader *types.Header) *big.Int
// IsEnabled returns whether the PID controller is currently active
IsEnabled() bool
}

// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559,
// - gas limit check
// - basefee check
Expand Down Expand Up @@ -185,3 +196,72 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64)
return baseFee
}
}

// CalcBaseFeeWithPID calculates base fee using either PID or EIP-1559
func CalcBaseFeeWithPID(config *params.ChainConfig, parent *types.Header, gasUsed uint64, pidController PIDController) *big.Int {
// Check if PID control is activated for this block
if config.IsPID(parent.Number) && pidController != nil && pidController.IsEnabled() {
log.Debug("Using PID controller for base fee calculation",
"blockNumber", parent.Number,
"gasUsed", gasUsed,
"parentBaseFee", parent.BaseFee,
)
return pidController.CalculateBaseFee(gasUsed, parent.BaseFee, parent)
}

// Fallback to standard EIP-1559 calculation
log.Debug("Using EIP-1559 for base fee calculation",
"blockNumber", parent.Number,
"gasUsed", gasUsed,
"parentBaseFee", parent.BaseFee,
)
return CalcBaseFee(config, parent, parent.Time)
}

// VerifyPIDHeader verifies the base fee in a header when PID is enabled
func VerifyPIDHeader(config *params.ChainConfig, parent, header *types.Header, pidController PIDController) error {
// First verify standard EIP-1559 constraints
err := VerifyEIP1559Header(config, parent, header)
if err != nil {
return err
}

// If PID is active, verify the base fee matches PID calculation
if config.IsPID(parent.Number) && pidController != nil && pidController.IsEnabled() {
expectedBaseFee := pidController.CalculateBaseFee(parent.GasUsed, parent.BaseFee, parent)

// Allow small rounding differences (within 1 wei per gas)
diff := new(big.Int).Sub(header.BaseFee, expectedBaseFee)
if diff.CmpAbs(big.NewInt(1)) > 0 {
log.Error("Invalid base fee in PID mode",
"expected", expectedBaseFee,
"actual", header.BaseFee,
"diff", diff,
)
return fmt.Errorf("invalid basefee: have %s, want %s", header.BaseFee, expectedBaseFee)
}
}

return nil
}

// VerifyEIP1559HeaderWithPID provides enhanced header verification for PID-enabled chains
func VerifyEIP1559HeaderWithPID(config *params.ChainConfig, parent, header *types.Header, pidController PIDController) error {
if config.IsPID(parent.Number) {
return VerifyPIDHeader(config, parent, header, pidController)
}

// Standard EIP-1559 verification for non-PID blocks
return VerifyEIP1559Header(config, parent, header)
}

// GetBaseFeeTargetUtilization returns the target utilization for base fee calculation
func GetBaseFeeTargetUtilization(config *params.ChainConfig) float64 {
if config.Optimism != nil {
// Optimism uses elasticity multiplier (target = limit / elasticity)
return 1.0 / float64(config.ElasticityMultiplier())
}

// Standard Ethereum targets 50% utilization
return 0.5
}
50 changes: 50 additions & 0 deletions eth/api_pid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package eth

// PIDAPI provides an API to control the PID controller for advanced fee management.
type PIDAPI struct {
e *Ethereum
}

// NewPIDAPI creates a new PIDAPI instance.
func NewPIDAPI(e *Ethereum) *PIDAPI {
return &PIDAPI{e}
}

// SetPIDEnabled enables or disables the PID controller
func (api *PIDAPI) SetPIDEnabled(enabled bool) bool {
api.e.Miner().SetPIDEnabled(enabled)
return true
}

// UpdatePIDParameters allows runtime adjustment of PID parameters
func (api *PIDAPI) UpdatePIDParameters(kp, ki, kd, maxChange float64) bool {
api.e.Miner().UpdatePIDParameters(kp, ki, kd, maxChange)
return true
}

// UpdatePIDExternalPressure receives pressure signals from batcher via RPC
func (api *PIDAPI) UpdatePIDExternalPressure(daPressure, l1Influence float64) bool {
api.e.Miner().UpdatePIDExternalPressure(daPressure, l1Influence)
return true
}

// GetPIDStatus returns current PID controller status
func (api *PIDAPI) GetPIDStatus() map[string]interface{} {
return api.e.Miner().GetPIDStatus()
}
3 changes: 3 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,9 @@ func (s *Ethereum) APIs() []rpc.API {
}, {
Namespace: "net",
Service: s.netRPCService,
}, {
Namespace: "pid",
Service: NewPIDAPI(s),
},
}...)
}
Expand Down
Binary file added geth
Binary file not shown.
78 changes: 77 additions & 1 deletion miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/interoptypes"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/pid"
)

var (
Expand Down Expand Up @@ -71,6 +73,13 @@ type Config struct {
EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value
MaxDATxSize *big.Int `toml:",omitempty"` // if non-nil, don't include any txs with data availability size larger than this in any built block
MaxDABlockSize *big.Int `toml:",omitempty"` // if non-nil, then don't build a block requiring more than this amount of total data availability

// PID Controller configuration
PIDEnabled bool // Enable PID controller for advanced fee management
PIDKp float64 // PID proportional gain
PIDKi float64 // PID integral gain
PIDKd float64 // PID derivative gain
PIDMaxChange float64 // Maximum fee change per block
}

// DefaultConfig contains default settings for miner.
Expand All @@ -83,6 +92,13 @@ var DefaultConfig = Config{
// for payload generation. It should be enough for Geth to
// run 3 rounds.
Recommit: 2 * time.Second,

// PID Controller defaults
PIDEnabled: true,
PIDKp: 1.8,
PIDKi: 0.12,
PIDKd: 0.35,
PIDMaxChange: 0.12,
}

// Miner is the main object which takes care of submitting new work to consensus
Expand All @@ -102,22 +118,48 @@ type Miner struct {

lifeCtxCancel context.CancelFunc
lifeCtx context.Context

// PID controller for advanced fee management
pidController *pid.FastPIDController
}

// New creates a new miner with provided config.
func New(eth Backend, config Config, engine consensus.Engine) *Miner {
ctx, cancel := context.WithCancel(context.Background())

// Initialize PID controller for sequencer fee management
var pidController *pid.FastPIDController
chainConfig := eth.BlockChain().Config()

// Use gas ceiling from config as gas limit, and calculate target as 50% of limit
gasLimit := config.GasCeil
if gasLimit == 0 {
gasLimit = DefaultConfig.GasCeil // Use default if not set
}
gasTarget := gasLimit / 2 // Target is typically 50% of limit for EIP-1559

pidController = pid.NewFastPIDController(gasTarget, gasLimit, log.New("module", "miner-pid"))

// Apply PID configuration from command line flags
if config.PIDEnabled {
pidController.SetEnabled(true)
pidController.UpdateParameters(config.PIDKp, config.PIDKi, config.PIDKd, config.PIDMaxChange)
} else {
pidController.SetEnabled(false)
}

return &Miner{
backend: eth,
config: &config,
chainConfig: eth.BlockChain().Config(),
chainConfig: chainConfig,
engine: engine,
txpool: eth.TxPool(),
chain: eth.BlockChain(),
pending: &pending{},
// To interrupt background tasks that may be attached to external processes
lifeCtxCancel: cancel,
lifeCtx: ctx,
pidController: pidController,
}
}

Expand Down Expand Up @@ -245,3 +287,37 @@ func (miner *Miner) getPending() *newPayloadResult {
func (miner *Miner) Close() {
miner.lifeCtxCancel()
}

// GetPIDController returns the PID controller instance for external access
func (miner *Miner) GetPIDController() *pid.FastPIDController {
return miner.pidController
}

// SetPIDEnabled enables or disables the PID controller
func (miner *Miner) SetPIDEnabled(enabled bool) {
if miner.pidController != nil {
miner.pidController.SetEnabled(enabled)
}
}

// UpdatePIDParameters allows runtime adjustment of PID parameters
func (miner *Miner) UpdatePIDParameters(kp, ki, kd, maxChange float64) {
if miner.pidController != nil {
miner.pidController.UpdateParameters(kp, ki, kd, maxChange)
}
}

// UpdatePIDExternalPressure updates external pressure signals from batcher
func (miner *Miner) UpdatePIDExternalPressure(daPressure, l1Influence float64) {
if miner.pidController != nil {
miner.pidController.UpdateExternalPressure(daPressure, l1Influence)
}
}

// GetPIDStatus returns current PID controller status
func (miner *Miner) GetPIDStatus() map[string]interface{} {
if miner.pidController != nil {
return miner.pidController.GetStatus()
}
return map[string]interface{}{"error": "PID controller not initialized"}
}
24 changes: 23 additions & 1 deletion miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,29 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir
}
// Set baseFee and GasLimit if we are on an EIP-1559 chain
if miner.chainConfig.IsLondon(header.Number) {
header.BaseFee = eip1559.CalcBaseFee(miner.chainConfig, parent, header.Time)
// Calculate base fee using standard EIP-1559 algorithm
standardBaseFee := eip1559.CalcBaseFee(miner.chainConfig, parent, header.Time)

// Apply PID controller adjustment if enabled
if miner.pidController != nil && miner.pidController.IsEnabled() {
// Get gas usage from parent block for PID calculation
gasUsed := parent.GasUsed
adjustedBaseFee := eip1559.CalcBaseFeeWithPID(miner.chainConfig, parent, gasUsed, miner.pidController)
header.BaseFee = adjustedBaseFee

// Get gas target from PID status for logging
status := miner.pidController.GetStatus()
gasTarget := status["gasTarget"]

log.Debug("PID controller applied to base fee",
"standard", standardBaseFee,
"adjusted", adjustedBaseFee,
"gasUsed", gasUsed,
"gasTarget", gasTarget)
} else {
header.BaseFee = standardBaseFee
}

if !miner.chainConfig.IsLondon(parent.Number) {
parentGasLimit := parent.GasLimit * miner.chainConfig.ElasticityMultiplier()
header.GasLimit = core.CalcGasLimit(parentGasLimit, miner.config.GasCeil)
Expand Down
Loading