Skip to content

Commit 9e7fc89

Browse files
authored
Merge pull request #5997 from obycode/feat/empty_mempool_sleep
feat: add `empty_mempool_sleep_ms`
2 parents 8f9c5ed + bc5e170 commit 9e7fc89

File tree

5 files changed

+166
-7
lines changed

5 files changed

+166
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
2020
- When a miner times out waiting for signatures, it will re-propose the same block instead of building a new block ([#5877](https://github.com/stacks-network/stacks-core/pull/5877))
2121
- Improve tenure downloader trace verbosity applying proper logging level depending on the tenure state ("debug" if unconfirmed, "info" otherwise) ([#5871](https://github.com/stacks-network/stacks-core/issues/5871))
2222
- Remove warning log about missing UTXOs when a node is configured as `miner` with `mock_mining` mode enabled ([#5841](https://github.com/stacks-network/stacks-core/issues/5841))
23-
- Deprecated the `wait_on_interim_blocks` option in the miner config file. This option is no longer needed, as the miner will always wait for interim blocks to be processed before mining a new block. To wait extra time in between blocks, use the `min_time_between_blocks_ms` option instead.
23+
- Deprecated the `wait_on_interim_blocks` option in the miner config file. This option is no longer needed, as the miner will always wait for interim blocks to be processed before mining a new block. To wait extra time in between blocks, use the `min_time_between_blocks_ms` option instead. ([#5979](https://github.com/stacks-network/stacks-core/pull/5979))
24+
- Added `empty_mempool_sleep_ms` to the miner config file to control the time to wait in between mining attempts when the mempool is empty. If not set, the default sleep time is 2.5s. ([#5997](https://github.com/stacks-network/stacks-core/pull/5997))
2425

2526
## [3.1.0.0.7]
2627

stackslib/src/config/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ const DEFAULT_TENURE_TIMEOUT_SECS: u64 = 180;
123123
/// Default percentage of block budget that must be used before attempting a
124124
/// time-based tenure extend
125125
const DEFAULT_TENURE_EXTEND_COST_THRESHOLD: u64 = 50;
126+
/// Default number of milliseconds that the miner should sleep between mining
127+
/// attempts when the mempool is empty.
128+
const DEFAULT_EMPTY_MEMPOOL_SLEEP_MS: u64 = 2_500;
126129

127130
static HELIUM_DEFAULT_CONNECTION_OPTIONS: LazyLock<ConnectionOptions> =
128131
LazyLock::new(|| ConnectionOptions {
@@ -2172,6 +2175,9 @@ pub struct MinerConfig {
21722175
/// The minimum time to wait between mining blocks in milliseconds. The value must be greater than or equal to 1000 ms because if a block is mined
21732176
/// within the same second as its parent, it will be rejected by the signers.
21742177
pub min_time_between_blocks_ms: u64,
2178+
/// The amount of time that the miner should sleep in between attempts to
2179+
/// mine a block when the mempool is empty
2180+
pub empty_mempool_sleep_time: Duration,
21752181
/// Time in milliseconds to pause after receiving the first threshold rejection, before proposing a new block.
21762182
pub first_rejection_pause_ms: u64,
21772183
/// Time in milliseconds to pause after receiving subsequent threshold rejections, before proposing a new block.
@@ -2224,6 +2230,7 @@ impl Default for MinerConfig {
22242230
max_reorg_depth: 3,
22252231
pre_nakamoto_mock_signing: false, // Should only default true if mining key is set
22262232
min_time_between_blocks_ms: DEFAULT_MIN_TIME_BETWEEN_BLOCKS_MS,
2233+
empty_mempool_sleep_time: Duration::from_millis(DEFAULT_EMPTY_MEMPOOL_SLEEP_MS),
22272234
first_rejection_pause_ms: DEFAULT_FIRST_REJECTION_PAUSE_MS,
22282235
subsequent_rejection_pause_ms: DEFAULT_SUBSEQUENT_REJECTION_PAUSE_MS,
22292236
block_commit_delay: Duration::from_millis(DEFAULT_BLOCK_COMMIT_DELAY_MS),
@@ -2639,6 +2646,7 @@ pub struct MinerConfigFile {
26392646
pub max_reorg_depth: Option<u64>,
26402647
pub pre_nakamoto_mock_signing: Option<bool>,
26412648
pub min_time_between_blocks_ms: Option<u64>,
2649+
pub empty_mempool_sleep_ms: Option<u64>,
26422650
pub first_rejection_pause_ms: Option<u64>,
26432651
pub subsequent_rejection_pause_ms: Option<u64>,
26442652
pub block_commit_delay_ms: Option<u64>,
@@ -2795,6 +2803,7 @@ impl MinerConfigFile {
27952803
} else {
27962804
ms
27972805
}).unwrap_or(miner_default_config.min_time_between_blocks_ms),
2806+
empty_mempool_sleep_time: self.empty_mempool_sleep_ms.map(Duration::from_millis).unwrap_or(miner_default_config.empty_mempool_sleep_time),
27982807
first_rejection_pause_ms: self.first_rejection_pause_ms.unwrap_or(miner_default_config.first_rejection_pause_ms),
27992808
subsequent_rejection_pause_ms: self.subsequent_rejection_pause_ms.unwrap_or(miner_default_config.subsequent_rejection_pause_ms),
28002809
block_commit_delay: self.block_commit_delay_ms.map(Duration::from_millis).unwrap_or(miner_default_config.block_commit_delay),

testnet/stacks-node/src/nakamoto_node/miner.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,38 @@ impl BlockMinerThread {
568568
continue;
569569
}
570570
Err(NakamotoNodeError::MiningFailure(ChainstateError::NoTransactionsToMine)) => {
571-
debug!("Miner did not find any transactions to mine");
571+
debug!(
572+
"Miner did not find any transactions to mine, sleeping for {:?}",
573+
self.config.miner.empty_mempool_sleep_time
574+
);
572575
self.reset_nonce_cache = false;
576+
577+
// Pause the miner to wait for transactions to arrive
578+
let now = Instant::now();
579+
while now.elapsed() < self.config.miner.empty_mempool_sleep_time {
580+
if self.abort_flag.load(Ordering::SeqCst) {
581+
info!("Miner interrupted while mining in order to shut down");
582+
self.globals
583+
.raise_initiative(format!("MiningFailure: aborted by node"));
584+
return Err(ChainstateError::MinerAborted.into());
585+
}
586+
587+
// Check if the burnchain tip has changed
588+
let Ok(sort_db) = SortitionDB::open(
589+
&self.config.get_burn_db_file_path(),
590+
false,
591+
self.burnchain.pox_constants.clone(),
592+
) else {
593+
error!("Failed to open sortition DB. Will try mining again.");
594+
continue;
595+
};
596+
if self.check_burn_tip_changed(&sort_db).is_err() {
597+
return Err(NakamotoNodeError::BurnchainTipChanged);
598+
}
599+
600+
thread::sleep(Duration::from_millis(ABORT_TRY_AGAIN_MS));
601+
}
602+
573603
break None;
574604
}
575605
Err(e) => {
@@ -680,6 +710,13 @@ impl BlockMinerThread {
680710

681711
thread::sleep(Duration::from_millis(ABORT_TRY_AGAIN_MS));
682712

713+
if self.abort_flag.load(Ordering::SeqCst) {
714+
info!("Miner interrupted while mining in order to shut down");
715+
self.globals
716+
.raise_initiative(format!("MiningFailure: aborted by node"));
717+
return Err(ChainstateError::MinerAborted.into());
718+
}
719+
683720
// Check if the burnchain tip has changed
684721
let Ok(sort_db) = SortitionDB::open(
685722
&self.config.get_burn_db_file_path(),

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9819,7 +9819,11 @@ fn skip_mining_long_tx() {
98199819

98209820
// Sleep for longer than the miner's attempt time, so that the miner will
98219821
// mark this tx as long-running and skip it in the next attempt
9822-
sleep_ms(naka_conf.miner.nakamoto_attempt_time_ms + 1000);
9822+
sleep_ms(
9823+
naka_conf.miner.nakamoto_attempt_time_ms
9824+
+ naka_conf.miner.empty_mempool_sleep_time.as_millis() as u64
9825+
+ 1000,
9826+
);
98239827

98249828
TEST_TX_STALL.set(false);
98259829

@@ -12339,3 +12343,114 @@ fn handle_considered_txs_foreign_key_failure() {
1233912343

1234012344
run_loop_thread.join().unwrap();
1234112345
}
12346+
12347+
#[test]
12348+
#[ignore]
12349+
fn empty_mempool_sleep_ms() {
12350+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
12351+
return;
12352+
}
12353+
12354+
let (mut conf, _miner_account) = naka_neon_integration_conf(None);
12355+
let password = "12345".to_string();
12356+
conf.connection_options.auth_token = Some(password.clone());
12357+
let stacker_sk = setup_stacker(&mut conf);
12358+
let signer_sk = Secp256k1PrivateKey::random();
12359+
let signer_addr = tests::to_addr(&signer_sk);
12360+
let sender_sk = Secp256k1PrivateKey::random();
12361+
// setup sender + recipient for a stx transfer
12362+
let sender_addr = tests::to_addr(&sender_sk);
12363+
let send_amt = 100;
12364+
let send_fee = 180;
12365+
conf.add_initial_balance(
12366+
PrincipalData::from(sender_addr).to_string(),
12367+
send_amt + send_fee,
12368+
);
12369+
conf.add_initial_balance(PrincipalData::from(signer_addr).to_string(), 100000);
12370+
12371+
// Set the empty mempool sleep time to something long enough that we can
12372+
// see the effect in the test.
12373+
conf.miner.empty_mempool_sleep_time = Duration::from_secs(30);
12374+
12375+
let mut btcd_controller = BitcoinCoreController::new(conf.clone());
12376+
btcd_controller
12377+
.start_bitcoind()
12378+
.expect("Failed starting bitcoind");
12379+
let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None);
12380+
btc_regtest_controller.bootstrap_chain(201);
12381+
12382+
let mut run_loop = boot_nakamoto::BootRunLoop::new(conf.clone()).unwrap();
12383+
let run_loop_stopper = run_loop.get_termination_switch();
12384+
let Counters {
12385+
blocks_processed,
12386+
naka_submitted_commits: commits_submitted,
12387+
naka_proposed_blocks,
12388+
..
12389+
} = run_loop.counters();
12390+
let counters = run_loop.counters();
12391+
12392+
let coord_channel = run_loop.coordinator_channels();
12393+
let http_origin = format!("http://{}", &conf.node.rpc_bind);
12394+
12395+
let run_loop_thread = thread::spawn(move || run_loop.start(None, 0));
12396+
let mut signers = TestSigners::new(vec![signer_sk]);
12397+
wait_for_runloop(&blocks_processed);
12398+
boot_to_epoch_3(
12399+
&conf,
12400+
&blocks_processed,
12401+
&[stacker_sk],
12402+
&[signer_sk],
12403+
&mut Some(&mut signers),
12404+
&mut btc_regtest_controller,
12405+
);
12406+
12407+
info!("------------------------- Reached Epoch 3.0 -------------------------");
12408+
12409+
blind_signer(&conf, &signers, &counters);
12410+
12411+
wait_for_first_naka_block_commit(60, &commits_submitted);
12412+
12413+
next_block_and_wait(&mut btc_regtest_controller, &blocks_processed);
12414+
12415+
// Sleep for 5 seconds to ensure that the miner tries to mine and sees an
12416+
// empty mempool.
12417+
thread::sleep(Duration::from_secs(5));
12418+
12419+
info!("------------------------- Submit a transaction -------------------------");
12420+
let proposals_before = naka_proposed_blocks.load(Ordering::SeqCst);
12421+
12422+
let transfer_tx = make_stacks_transfer(
12423+
&sender_sk,
12424+
0,
12425+
send_fee,
12426+
conf.burnchain.chain_id,
12427+
&signer_addr.into(),
12428+
send_amt,
12429+
);
12430+
submit_tx(&http_origin, &transfer_tx);
12431+
12432+
// The miner should have slept for 30 seconds after seeing an empty mempool
12433+
// before trying to mine again. Let's check that there was at least 10s
12434+
// before the next block proposal.
12435+
wait_for(10, || {
12436+
let proposals_after = naka_proposed_blocks.load(Ordering::SeqCst);
12437+
Ok(proposals_after > proposals_before)
12438+
})
12439+
.expect_err("Expected to wait for 30 seconds before mining a block");
12440+
12441+
// Wait for the transaction to be mined
12442+
wait_for(60, || {
12443+
let account = get_account(&http_origin, &sender_addr);
12444+
Ok(account.nonce == 1)
12445+
})
12446+
.expect("Timed out waiting for transaction to be mined after delay");
12447+
12448+
coord_channel
12449+
.lock()
12450+
.expect("Mutex poisoned")
12451+
.stop_chains_coordinator();
12452+
12453+
run_loop_stopper.store(false, Ordering::SeqCst);
12454+
12455+
run_loop_thread.join().unwrap();
12456+
}

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9360,10 +9360,7 @@ fn injected_signatures_are_ignored_across_boundaries() {
93609360
}
93619361

93629362
info!("---- Manually mine a single burn block to force the signers to update ----");
9363-
next_block_and_wait(
9364-
&mut signer_test.running_nodes.btc_regtest_controller,
9365-
&signer_test.running_nodes.counters.blocks_processed,
9366-
);
9363+
signer_test.mine_nakamoto_block(Duration::from_secs(60), true);
93679364

93689365
signer_test.wait_for_registered_both_reward_cycles();
93699366

0 commit comments

Comments
 (0)