diff --git a/docs/cli/default_config.toml b/docs/cli/default_config.toml index 022a277ef1..e9f1f865d7 100644 --- a/docs/cli/default_config.toml +++ b/docs/cli/default_config.toml @@ -105,6 +105,7 @@ devfakeauthor = false base-fee-change-denominator = 0 prefetch = false prefetch-gaslimit-percent = 100 + disable-pending-block = false [jsonrpc] ipcdisable = false diff --git a/docs/cli/example_config.toml b/docs/cli/example_config.toml index 33b6249046..5bac960687 100644 --- a/docs/cli/example_config.toml +++ b/docs/cli/example_config.toml @@ -79,13 +79,14 @@ bp-rpc-endpoints = [ ] # Comma separated rpc endpoints of all block producers lifetime = "3h0m0s" # Maximum amount of time non-executable transaction are queued [miner] - mine = false # Enable mining - etherbase = "" # Public address for block mining rewards - extradata = "" # Block extra data set by the miner (default = client version) - gaslimit = 50000000 # Target gas ceiling for mined blocks (used when dynamic gas limit is disabled) - gasprice = "25000000000" # Minimum gas price for mining a transaction. Regardless the value set, it will be enforced to 25000000000 for all networks - recommit = "2m5s" # The time interval for miner to re-create mining work - commitinterrupt = true # Interrupt the current mining work when time is exceeded and create partial blocks + mine = false # Enable mining + etherbase = "" # Public address for block mining rewards + extradata = "" # Block extra data set by the miner (default = client version) + gaslimit = 50000000 # Target gas ceiling for mined blocks (used when dynamic gas limit is disabled) + gasprice = "25000000000" # Minimum gas price for mining a transaction. Regardless the value set, it will be enforced to 25000000000 for all networks + recommit = "2m5s" # The time interval for miner to re-create mining work + commitinterrupt = true # Interrupt the current mining work when time is exceeded and create partial blocks + disable-pending-block = false # Disable the pending block creation loop on non block producer nodes. When set, 'pending' block will be unavailable for RPC queries. # Dynamic gas limit configuration enableDynamicGasLimit = false # Enable dynamic gas limit adjustment based on base fee gasLimitMin = 50000000 # Minimum gas limit when dynamic gas limit is enabled diff --git a/docs/cli/server.md b/docs/cli/server.md index a8e4a3d595..11c63bfcc3 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -336,6 +336,8 @@ The ```bor server``` command runs the Bor client. - ```miner.blocktime```: The block time defined by the miner. Needs to be larger or equal to the consensus block time. If not set (default = 0), the miner will use the consensus block time. (default: 0s) +- ```miner.disable-pending-block```: Disable the pending block creation loop on non block producer nodes. When set, 'pending' block will be unavailable for RPC queries. (default: false) + - ```miner.enableDynamicGasLimit```: Enable dynamic gas limit adjustment based on base fee (default: false) - ```miner.enableDynamicTargetGas```: Enable dynamic EIP-1559 target gas percentage adjustment based on base fee (post-Lisovo, mutually exclusive with enableDynamicGasLimit) (default: false) diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go index bc4e168cde..0d86148b71 100644 --- a/internal/cli/server/config.go +++ b/internal/cli/server/config.go @@ -449,6 +449,11 @@ type SealerConfig struct { // PrefetchGasLimitPercent is the gas limit percentage for prefetching (e.g., 100 = 100%, 110 = 110%) PrefetchGasLimitPercent uint64 `hcl:"prefetch-gaslimit-percent,optional" toml:"prefetch-gaslimit-percent,optional"` + + // DisablePendingBlock disables the pending block creation loop on non block producer nodes. When + // set, 'pending' block will be unavailable for RPC queries. This won't apply for block producer + // nodes. + DisablePendingBlock bool `hcl:"disable-pending-block,optional" toml:"disable-pending-block,optional"` } type JsonRPCConfig struct { @@ -866,7 +871,6 @@ func DefaultConfig() *Config { StateScheme: "path", Snapshot: true, BorLogs: false, - TxPool: &TxPoolConfig{ Locals: []string{}, NoLocals: false, @@ -906,6 +910,7 @@ func DefaultConfig() *Config { PrefetchGasLimitPercent: 100, TargetGasPercentage: 0, // Initialize to 0, will be set from CLI or remain 0 (meaning use default) BaseFeeChangeDenominator: 0, // Initialize to 0, will be set from CLI or remain 0 (meaning use default) + DisablePendingBlock: false, }, Gpo: &GpoConfig{ Blocks: 20, @@ -1277,6 +1282,7 @@ func (c *Config) buildEth(stack *node.Node, accountManager *accounts.Manager) (* n.Miner.BlockTime = c.Sealer.BlockTime n.Miner.EnablePrefetch = c.Sealer.EnablePrefetch n.Miner.PrefetchGasLimitPercent = c.Sealer.PrefetchGasLimitPercent + n.Miner.DisablePendingBlock = c.Sealer.DisablePendingBlock // Validate prefetch gas limit percentage if c.Sealer.EnablePrefetch && c.Sealer.PrefetchGasLimitPercent > 150 { diff --git a/internal/cli/server/flags.go b/internal/cli/server/flags.go index 270d9f6bde..39e01ea7ab 100644 --- a/internal/cli/server/flags.go +++ b/internal/cli/server/flags.go @@ -508,6 +508,13 @@ func (c *Command) Flags(config *Config) *flagset.Flagset { Default: c.cliConfig.Sealer.TargetGasMaxPercentage, Group: "Sealer", }) + f.BoolFlag(&flagset.BoolFlag{ + Name: "miner.disable-pending-block", + Usage: "Disable the pending block creation loop on non block producer nodes. When set, 'pending' block will be unavailable for RPC queries.", + Value: &c.cliConfig.Sealer.DisablePendingBlock, + Default: c.cliConfig.Sealer.DisablePendingBlock, + Group: "Sealer", + }) // ethstats f.StringFlag(&flagset.StringFlag{ diff --git a/miner/miner.go b/miner/miner.go index a919717cc2..dbd4607542 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -67,6 +67,8 @@ type Config struct { PendingFeeRecipient common.Address `toml:"-"` // Address for pending block rewards. EnablePrefetch bool // Enable transaction prefetching from pool during block building PrefetchGasLimitPercent uint64 // Gas limit percentage for prefetching (e.g., 100 = 100%, 110 = 110%) + + DisablePendingBlock bool // Disable the pending block creation loop on non block producer nodes } // DefaultConfig contains default settings for miner. @@ -89,6 +91,8 @@ var DefaultConfig = Config{ Recommit: 2 * time.Second, EnablePrefetch: true, PrefetchGasLimitPercent: 100, // 100% of header gas limit + + DisablePendingBlock: false, } // Miner is the main object which takes care of submitting new work to consensus diff --git a/miner/worker.go b/miner/worker.go index 9a633a12f8..4a8c79bf29 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -832,6 +832,14 @@ func (w *worker) mainLoop() { for { select { case req := <-w.newWorkCh: + // When DisablePendingBlock is set and the worker is not actively producing + // blocks (non-validator), skip commitWork entirely — its only purpose in + // that case is to maintain the pending block snapshot for RPC. + if w.config.DisablePendingBlock && !w.IsRunning() { + w.pendingWorkBlock.Store(0) + continue + } + if w.chainConfig.ChainID.Cmp(params.BorMainnetChainConfig.ChainID) == 0 || w.chainConfig.ChainID.Cmp(params.MumbaiChainConfig.ChainID) == 0 || w.chainConfig.ChainID.Cmp(params.AmoyChainConfig.ChainID) == 0 { if w.eth.PeerCount() > 0 || devFakeAuthor { //nolint:contextcheck @@ -852,7 +860,7 @@ func (w *worker) mainLoop() { // already included in the current sealing block. These transactions will // be automatically eliminated. // nolint : nestif - if !w.IsRunning() && w.current != nil { + if !w.IsRunning() && !w.config.DisablePendingBlock && w.current != nil { // If block is already full, abort if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas { continue diff --git a/miner/worker_test.go b/miner/worker_test.go index 8da774b8e4..eaf87fe72d 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -2967,3 +2967,45 @@ func TestDelayFlagOffByOne(t *testing.T) { require.True(t, buggyDelayFlag(), "bug: last tx skipped, DAG hint incorrectly embedded") require.False(t, fixedDelayFlag(), "fix: last tx detected, DAG hint suppressed") } + +// TestDisablePendingBlock validates if setting `DisablePendingBlock` affects the +// creation of pending block or not. +func TestDisablePendingBlock(t *testing.T) { + t.Parallel() + + t.Run("pending block is nil when flag is enabled", func(t *testing.T) { + t.Parallel() + + config := DefaultTestConfig() + config.DisablePendingBlock = true + + w, _, cleanup := newTestWorker(t, config, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), false, 0) + defer cleanup() + + // Trigger the pending block build (non-validator path: worker is not started/running). + w.startCh <- struct{}{} + + require.Never(t, func() bool { + block, receipts, stateDB := w.pending() + return block != nil || receipts != nil || stateDB != nil + }, 500*time.Millisecond, 100*time.Millisecond, "pending block, receipts and state should be nil when DisablePendingBlock is true") + }) + + t.Run("pending block is created when flag is disabled", func(t *testing.T) { + t.Parallel() + + config := DefaultTestConfig() + config.DisablePendingBlock = false + + w, _, cleanup := newTestWorker(t, config, ethashChainConfig, ethash.NewFaker(), rawdb.NewMemoryDatabase(), false, 0) + defer cleanup() + + // Trigger the pending block build (non-validator path: worker is not started/running). + w.startCh <- struct{}{} + + require.Eventually(t, func() bool { + block, receipts, stateDB := w.pending() + return block != nil && receipts != nil && stateDB != nil + }, 2*time.Second, 100*time.Millisecond, "pending block, receipts and state should not be nil when DisablePendingBlock is false") + }) +}