Skip to content

Commit bd45dd7

Browse files
authored
Merge pull request #5835 from stacks-network/feat/block-commit-checks
Fix: enable patient block commits
2 parents bdc76d8 + b611cfc commit bd45dd7

File tree

7 files changed

+573
-385
lines changed

7 files changed

+573
-385
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1616

1717
- Implement faster cost tracker for default cost functions in Clarity
1818
- Miner will stop waiting for signatures on a block if the Stacks tip advances (causing the block it had proposed to be invalid).
19+
- By default, miners will wait for a new tenure to start for a configurable amount of time after receiving a burn block before
20+
submitting a block commit. This will reduce the amount of RBF transactions miners are expected to need.
1921
- Logging improvements:
2022
- P2P logs now includes a reason for dropping a peer or neighbor
2123
- Improvements to how a PeerAddress is logged (human readable format vs hex)

stackslib/src/chainstate/burn/db/sortdb.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ pub const REWARD_WINDOW_END: u64 = 144 * 90 + REWARD_WINDOW_START;
9595

9696
pub type BlockHeaderCache = HashMap<ConsensusHash, (Option<BlockHeaderHash>, ConsensusHash)>;
9797

98+
pub enum FindIter<R> {
99+
Found(R),
100+
Continue,
101+
Halt,
102+
}
103+
98104
impl FromRow<SortitionId> for SortitionId {
99105
fn from_row(row: &Row) -> Result<SortitionId, db_error> {
100106
SortitionId::from_column(row, "sortition_id")
@@ -4989,6 +4995,60 @@ impl SortitionDB {
49894995
Ok(handle.get_reward_set_size_at(&sort_id_of_start)? > 0)
49904996
}
49914997

4998+
/// Find a sortition by traversing the sortition history backwards, starting
4999+
/// from the current canonical burn tip.
5000+
///
5001+
/// The supplied function `f` is applied to each traversed snapshot
5002+
/// and the return value of `f` controls the iteration.
5003+
///
5004+
/// FindIter::Found(x) => tells the search to stop, and return `Ok(Some(x))`
5005+
/// FindIter::Halt => tells the search to stop, and return `Ok(None)`
5006+
/// FindIter::Continue will continue the iteration
5007+
///
5008+
/// This function exits early and returns the error if either `f` or a SortitionDB
5009+
/// error occurs while processing.
5010+
/// It returns `Ok(None)` if the traversal reaches the sortition root without finding `x`
5011+
pub fn find_in_canonical<F, R, E>(&self, f: F) -> Result<Option<R>, E>
5012+
where
5013+
F: FnMut(&BlockSnapshot) -> Result<FindIter<R>, E>,
5014+
E: From<db_error>,
5015+
{
5016+
let cursor = Self::get_canonical_burn_chain_tip(self.conn())?;
5017+
self.find_from(cursor, f)
5018+
}
5019+
5020+
/// Find a sortition by traversing the sortition history backwards, starting
5021+
/// from `sn`. The supplied function `f` is applied to each traversed snapshot
5022+
/// and the return value of `f` controls the iteration.
5023+
///
5024+
/// FindIter::Found(x) => tells the search to stop, and return `Ok(Some(x))`
5025+
/// FindIter::Halt => tells the search to stop, and return `Ok(None)`
5026+
/// FindIter::Continue will continue the iteration
5027+
///
5028+
/// This function exits early and returns the error if either `f` or a SortitionDB
5029+
/// error occurs while processing.
5030+
/// It returns `Ok(None)` if the traversal reaches the sortition root without finding `x`
5031+
pub fn find_from<F, R, E>(&self, sn: BlockSnapshot, mut f: F) -> Result<Option<R>, E>
5032+
where
5033+
F: FnMut(&BlockSnapshot) -> Result<FindIter<R>, E>,
5034+
E: From<db_error>,
5035+
{
5036+
let mut cursor_opt = Some(sn);
5037+
loop {
5038+
let Some(ref cursor) = cursor_opt else {
5039+
return Ok(None);
5040+
};
5041+
let next_id = &cursor.parent_sortition_id;
5042+
match f(cursor)? {
5043+
FindIter::Found(x) => return Ok(Some(x)),
5044+
FindIter::Halt => return Ok(None),
5045+
FindIter::Continue => {
5046+
cursor_opt = Self::get_block_snapshot(self.conn(), next_id)?;
5047+
}
5048+
}
5049+
}
5050+
}
5051+
49925052
/// Find out how any burn tokens were destroyed in a given block on a given fork.
49935053
pub fn get_block_burn_amount(
49945054
conn: &Connection,

stackslib/src/config/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const DEFAULT_FIRST_REJECTION_PAUSE_MS: u64 = 5_000;
107107
const DEFAULT_SUBSEQUENT_REJECTION_PAUSE_MS: u64 = 10_000;
108108
/// Default time in milliseconds to wait for a Nakamoto block after seeing a
109109
/// burnchain block before submitting a block commit.
110-
const DEFAULT_BLOCK_COMMIT_DELAY_MS: u64 = 20_000;
110+
const DEFAULT_BLOCK_COMMIT_DELAY_MS: u64 = 40_000;
111111
/// Default percentage of the remaining tenure cost limit to consume each block
112112
const DEFAULT_TENURE_COST_LIMIT_PER_BLOCK_PERCENTAGE: u8 = 25;
113113
/// Default number of seconds to wait in-between polling the sortition DB to

testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,30 @@ impl BitcoinRegtestController {
20002000
}
20012001
}
20022002

2003+
#[cfg(test)]
2004+
/// Instruct a regtest Bitcoin node to build an empty block.
2005+
pub fn build_empty_block(&self) {
2006+
info!("Generate empty block");
2007+
let public_key_bytes = match &self.config.burnchain.local_mining_public_key {
2008+
Some(public_key) => hex_bytes(public_key).expect("Invalid byte sequence"),
2009+
None => panic!("Unable to make new block, mining public key"),
2010+
};
2011+
2012+
// NOTE: miner address is whatever the configured segwit setting is
2013+
let public_key = Secp256k1PublicKey::from_slice(&public_key_bytes)
2014+
.expect("FATAL: invalid public key bytes");
2015+
let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key);
2016+
let result = BitcoinRPCRequest::generate_empty_to_address(&self.config, addr2str(&address));
2017+
2018+
match result {
2019+
Ok(_) => {}
2020+
Err(e) => {
2021+
error!("Bitcoin RPC failure: error generating block {e:?}");
2022+
panic!();
2023+
}
2024+
}
2025+
}
2026+
20032027
#[cfg(test)]
20042028
pub fn invalidate_block(&self, block: &BurnchainHeaderHash) {
20052029
info!("Invalidating block {block}");
@@ -2538,6 +2562,21 @@ impl BitcoinRPCRequest {
25382562
Ok(())
25392563
}
25402564

2565+
#[cfg(test)]
2566+
pub fn generate_empty_to_address(config: &Config, address: String) -> RPCResult<()> {
2567+
debug!("Generate empty block to {address}");
2568+
let payload = BitcoinRPCRequest {
2569+
method: "generateblock".to_string(),
2570+
params: vec![address.clone().into(), serde_json::Value::Array(vec![])],
2571+
id: "stacks".to_string(),
2572+
jsonrpc: "2.0".to_string(),
2573+
};
2574+
2575+
let res = BitcoinRPCRequest::send(config, payload)?;
2576+
debug!("Generated empty block to {address}: {res:?}");
2577+
Ok(())
2578+
}
2579+
25412580
pub fn list_unspent(
25422581
config: &Config,
25432582
addresses: Vec<String>,

0 commit comments

Comments
 (0)