Skip to content

Commit 24c425f

Browse files
committed
Add a config option and wait a minimum number of seconds between mining blocks to prevent signers rejecting a block with same timestamp as its parent
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent cbf0c52 commit 24c425f

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

testnet/stacks-node/src/config.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub const OP_TX_ANY_ESTIM_SIZE: u64 = fmax!(
8686
const DEFAULT_MAX_RBF_RATE: u64 = 150; // 1.5x
8787
const DEFAULT_RBF_FEE_RATE_INCREMENT: u64 = 5;
8888
const INV_REWARD_CYCLES_TESTNET: u64 = 6;
89+
const DEFAULT_MINIMUM_GAP_SECS: u64 = 1;
8990

9091
#[derive(Clone, Deserialize, Default, Debug)]
9192
pub struct ConfigFile {
@@ -2358,6 +2359,9 @@ pub struct MinerConfig {
23582359
pub wait_on_signers: Duration,
23592360
/// Whether to mock sign in Epoch 2.5 through the .miners and .signers contracts. This is used for testing purposes in Epoch 2.5 only.
23602361
pub pre_nakamoto_mock_signing: bool,
2362+
/// The minimum gap to wait between blocks in seconds. The value must be greater than or equal to 1 second because if a block is mined
2363+
/// within the same second as its parent, it will be rejected by the signers.
2364+
pub min_block_time_gap_secs: u64,
23612365
}
23622366

23632367
impl Default for MinerConfig {
@@ -2389,6 +2393,7 @@ impl Default for MinerConfig {
23892393
// TODO: update to a sane value based on stackerdb benchmarking
23902394
wait_on_signers: Duration::from_secs(200),
23912395
pre_nakamoto_mock_signing: false, // Should only default true if mining key is set
2396+
min_block_time_gap_secs: DEFAULT_MINIMUM_GAP_SECS,
23922397
}
23932398
}
23942399
}
@@ -2739,6 +2744,7 @@ pub struct MinerConfigFile {
27392744
pub max_reorg_depth: Option<u64>,
27402745
pub wait_on_signers_ms: Option<u64>,
27412746
pub pre_nakamoto_mock_signing: Option<bool>,
2747+
pub min_block_time_gap_secs: Option<u64>,
27422748
}
27432749

27442750
impl MinerConfigFile {
@@ -2850,6 +2856,12 @@ impl MinerConfigFile {
28502856
pre_nakamoto_mock_signing: self
28512857
.pre_nakamoto_mock_signing
28522858
.unwrap_or(pre_nakamoto_mock_signing), // Should only default true if mining key is set
2859+
min_block_time_gap_secs: self.min_block_time_gap_secs.map(|secs| if secs < DEFAULT_MINIMUM_GAP_SECS {
2860+
warn!("miner.min_block_time_gap_secs is less than the minimum allowed value of {DEFAULT_MINIMUM_GAP_SECS} secs. Using the default value instead.");
2861+
DEFAULT_MINIMUM_GAP_SECS
2862+
} else {
2863+
secs
2864+
}).unwrap_or(miner_default_config.min_block_time_gap_secs),
28532865
})
28542866
}
28552867
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ use stacks::chainstate::stacks::{
4545
use stacks::net::p2p::NetworkHandle;
4646
use stacks::net::stackerdb::StackerDBs;
4747
use stacks::net::{NakamotoBlocksData, StacksMessageType};
48+
use stacks::util::get_epoch_time_secs;
4849
use stacks::util::secp256k1::MessageSignature;
4950
use stacks_common::codec::read_next;
5051
use stacks_common::types::chainstate::{StacksAddress, StacksBlockId};
@@ -317,6 +318,8 @@ impl BlockMinerThread {
317318
}
318319
}
319320
}
321+
self.wait_min_time_between_blocks()?;
322+
320323
match self.mine_block(&stackerdbs) {
321324
Ok(x) => break Some(x),
322325
Err(NakamotoNodeError::MiningFailure(ChainstateError::MinerAborted)) => {
@@ -1037,6 +1040,31 @@ impl BlockMinerThread {
10371040
Some(vrf_proof)
10381041
}
10391042

1043+
/// Wait the minimum time between blocks before mining a new block (if necessary)
1044+
/// This is to ensure that the signers do not reject the block due to the block being mined within the same second as the parent block.
1045+
fn wait_min_time_between_blocks(&self) -> Result<(), NakamotoNodeError> {
1046+
let burn_db_path = self.config.get_burn_db_file_path();
1047+
let mut burn_db =
1048+
SortitionDB::open(&burn_db_path, false, self.burnchain.pox_constants.clone())
1049+
.expect("FATAL: could not open sortition DB");
1050+
1051+
let mut chain_state = neon_node::open_chainstate_with_faults(&self.config)
1052+
.expect("FATAL: could not open chainstate DB");
1053+
let parent_block_info = self.load_block_parent_info(&mut burn_db, &mut chain_state)?;
1054+
let time_since_parent_secs = get_epoch_time_secs()
1055+
.saturating_sub(parent_block_info.stacks_parent_header.burn_header_timestamp);
1056+
if time_since_parent_secs < self.config.miner.min_block_time_gap_secs {
1057+
let wait_secs = self
1058+
.config
1059+
.miner
1060+
.min_block_time_gap_secs
1061+
.saturating_sub(time_since_parent_secs);
1062+
info!("Waiting {wait_secs} seconds before mining a new block.");
1063+
std::thread::sleep(Duration::from_secs(wait_secs));
1064+
}
1065+
Ok(())
1066+
}
1067+
10401068
// TODO: add tests from mutation testing results #4869
10411069
#[cfg_attr(test, mutants::skip)]
10421070
/// Try to mine a Stacks block by assembling one from mempool transactions and sending a

0 commit comments

Comments
 (0)