Skip to content

Commit f394e64

Browse files
committed
feat: add 2 heuristics to miner for nakamoto
* for the first block in a tenure, just mine an empty block * estimate the time it takes to eval a tx, and see if it will interfere with block deadline
1 parent b47cfb8 commit f394e64

File tree

2 files changed

+96
-5
lines changed

2 files changed

+96
-5
lines changed

stackslib/src/chainstate/stacks/miner.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::collections::{HashMap, HashSet};
1818
use std::sync::atomic::{AtomicBool, Ordering};
1919
use std::sync::{Arc, Mutex};
2020
use std::thread::ThreadId;
21+
use std::time::Instant;
2122
use std::{cmp, fs, mem};
2223

2324
use clarity::vm::analysis::{CheckError, CheckErrors};
@@ -2211,6 +2212,15 @@ impl StacksBlockBuilder {
22112212
);
22122213
}
22132214

2215+
// nakamoto miner tenure start heuristic:
2216+
// mine an empty block so you can start your tenure quickly!
2217+
if let Some(tx) = initial_txs.first() {
2218+
if matches!(&tx.payload, TransactionPayload::TenureChange(_)) {
2219+
debug!("Nakamoto miner heuristic: during tenure change blocks, produce a fast short block to begin tenure");
2220+
return Ok((false, tx_events));
2221+
}
2222+
}
2223+
22142224
mempool.reset_nonce_cache()?;
22152225
mempool.estimate_tx_rates(100, &block_limit, &stacks_epoch_id)?;
22162226

@@ -2221,6 +2231,7 @@ impl StacksBlockBuilder {
22212231

22222232
let mut invalidated_txs = vec![];
22232233
let mut to_drop_and_blacklist = vec![];
2234+
let mut update_timings = vec![];
22242235

22252236
let deadline = ts_start + u128::from(max_miner_time_ms);
22262237
let mut num_txs = 0;
@@ -2250,10 +2261,27 @@ impl StacksBlockBuilder {
22502261
if block_limit_hit == BlockLimitFunction::LIMIT_REACHED {
22512262
return Ok(None);
22522263
}
2253-
if get_epoch_time_ms() >= deadline {
2264+
let time_now = get_epoch_time_ms();
2265+
if time_now >= deadline {
22542266
debug!("Miner mining time exceeded ({} ms)", max_miner_time_ms);
22552267
return Ok(None);
22562268
}
2269+
if let Some(time_estimate) = txinfo.metadata.time_estimate_ms {
2270+
if time_now.saturating_add(time_estimate.into()) > deadline {
2271+
debug!("Mining tx would cause us to exceed our deadline, skipping";
2272+
"txid" => %txinfo.tx.txid(),
2273+
"deadline" => deadline,
2274+
"now" => time_now,
2275+
"estimate" => time_estimate);
2276+
return Ok(Some(
2277+
TransactionResult::skipped(
2278+
&txinfo.tx,
2279+
"Transaction would exceed deadline.".into(),
2280+
)
2281+
.convert_to_event(),
2282+
));
2283+
}
2284+
}
22572285

22582286
// skip transactions early if we can
22592287
if considered.contains(&txinfo.tx.txid()) {
@@ -2303,6 +2331,7 @@ impl StacksBlockBuilder {
23032331
considered.insert(txinfo.tx.txid());
23042332
num_considered += 1;
23052333

2334+
let tx_start = Instant::now();
23062335
let tx_result = builder.try_mine_tx_with_len(
23072336
epoch_tx,
23082337
&txinfo.tx,
@@ -2314,6 +2343,21 @@ impl StacksBlockBuilder {
23142343
let result_event = tx_result.convert_to_event();
23152344
match tx_result {
23162345
TransactionResult::Success(TransactionSuccess { receipt, .. }) => {
2346+
if txinfo.metadata.time_estimate_ms.is_none() {
2347+
// use i64 to avoid running into issues when storing in
2348+
// rusqlite.
2349+
let time_estimate_ms: i64 = tx_start
2350+
.elapsed()
2351+
.as_millis()
2352+
.try_into()
2353+
.unwrap_or_else(|_| i64::MAX);
2354+
let time_estimate_ms: u64 = time_estimate_ms
2355+
.try_into()
2356+
// should be unreachable
2357+
.unwrap_or_else(|_| 0);
2358+
update_timings.push((txinfo.tx.txid(), time_estimate_ms));
2359+
}
2360+
23172361
num_txs += 1;
23182362
if update_estimator {
23192363
if let Err(e) = estimator.notify_event(
@@ -2386,6 +2430,12 @@ impl StacksBlockBuilder {
23862430
},
23872431
);
23882432

2433+
if !update_timings.is_empty() {
2434+
if let Err(e) = mempool.update_tx_time_estimates(&update_timings) {
2435+
warn!("Error while updating time estimates for mempool"; "err" => ?e);
2436+
}
2437+
}
2438+
23892439
if to_drop_and_blacklist.len() > 0 {
23902440
let _ = mempool.drop_and_blacklist_txs(&to_drop_and_blacklist);
23912441
}

stackslib/src/core/mempool.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ pub struct MemPoolTxMetadata {
460460
pub last_known_origin_nonce: Option<u64>,
461461
pub last_known_sponsor_nonce: Option<u64>,
462462
pub accept_time: u64,
463+
pub time_estimate_ms: Option<u64>,
463464
}
464465

465466
impl MemPoolTxMetadata {
@@ -594,6 +595,7 @@ impl FromRow<MemPoolTxMetadata> for MemPoolTxMetadata {
594595
let sponsor_nonce = u64::from_column(row, "sponsor_nonce")?;
595596
let last_known_sponsor_nonce = u64::from_column(row, "last_known_sponsor_nonce")?;
596597
let last_known_origin_nonce = u64::from_column(row, "last_known_origin_nonce")?;
598+
let time_estimate_ms: Option<u64> = row.get("time_estimate_ms")?;
597599

598600
Ok(MemPoolTxMetadata {
599601
txid,
@@ -609,6 +611,7 @@ impl FromRow<MemPoolTxMetadata> for MemPoolTxMetadata {
609611
last_known_origin_nonce,
610612
last_known_sponsor_nonce,
611613
accept_time,
614+
time_estimate_ms,
612615
})
613616
}
614617
}
@@ -624,10 +627,7 @@ impl FromRow<MemPoolTxInfo> for MemPoolTxInfo {
624627
return Err(db_error::ParseError);
625628
}
626629

627-
Ok(MemPoolTxInfo {
628-
tx: tx,
629-
metadata: md,
630-
})
630+
Ok(MemPoolTxInfo { tx, metadata: md })
631631
}
632632
}
633633

@@ -803,6 +803,16 @@ const MEMPOOL_SCHEMA_6_NONCES: &'static [&'static str] = &[
803803
"#,
804804
];
805805

806+
const MEMPOOL_SCHEMA_7_TIME_ESTIMATES: &'static [&'static str] = &[
807+
r#"
808+
-- ALLOW NULL
809+
ALTER TABLE mempool ADD COLUMN time_estimate_ms NUMBER;
810+
"#,
811+
r#"
812+
INSERT INTO schema_version (version) VALUES (7)
813+
"#,
814+
];
815+
806816
const MEMPOOL_INDEXES: &'static [&'static str] = &[
807817
"CREATE INDEX IF NOT EXISTS by_txid ON mempool(txid);",
808818
"CREATE INDEX IF NOT EXISTS by_height ON mempool(height);",
@@ -1287,6 +1297,9 @@ impl MemPoolDB {
12871297
MemPoolDB::instantiate_nonces(tx)?;
12881298
}
12891299
6 => {
1300+
MemPoolDB::instantiate_schema_7(tx)?;
1301+
}
1302+
7 => {
12901303
break;
12911304
}
12921305
_ => {
@@ -1363,6 +1376,16 @@ impl MemPoolDB {
13631376
Ok(())
13641377
}
13651378

1379+
/// Add the nonce table
1380+
#[cfg_attr(test, mutants::skip)]
1381+
fn instantiate_schema_7(tx: &DBTx) -> Result<(), db_error> {
1382+
for sql_exec in MEMPOOL_SCHEMA_7_TIME_ESTIMATES {
1383+
tx.execute_batch(sql_exec)?;
1384+
}
1385+
1386+
Ok(())
1387+
}
1388+
13661389
#[cfg_attr(test, mutants::skip)]
13671390
pub fn db_path(chainstate_root_path: &str) -> Result<String, db_error> {
13681391
let mut path = PathBuf::from(chainstate_root_path);
@@ -2650,6 +2673,24 @@ impl MemPoolDB {
26502673
Ok(())
26512674
}
26522675

2676+
/// Drop and blacklist transactions, so we don't re-broadcast them or re-fetch them.
2677+
/// Do *NOT* remove them from the bloom filter. This will cause them to continue to be
2678+
/// reported as present, which is exactly what we want because we don't want these transactions
2679+
/// to be seen again (so we don't want anyone accidentally "helpfully" pushing them to us, nor
2680+
/// do we want the mempool sync logic to "helpfully" re-discover and re-download them).
2681+
pub fn update_tx_time_estimates(&mut self, txs: &[(Txid, u64)]) -> Result<(), db_error> {
2682+
let sql = "UPDATE mempool SET time_estimate_ms = ? WHERE txid = ?";
2683+
let mempool_tx = self.tx_begin()?;
2684+
for (txid, time_estimate_ms) in txs.iter() {
2685+
mempool_tx
2686+
.tx
2687+
.execute(sql, params![time_estimate_ms, txid])?;
2688+
}
2689+
mempool_tx.commit()?;
2690+
2691+
Ok(())
2692+
}
2693+
26532694
/// Drop and blacklist transactions, so we don't re-broadcast them or re-fetch them.
26542695
/// Do *NOT* remove them from the bloom filter. This will cause them to continue to be
26552696
/// reported as present, which is exactly what we want because we don't want these transactions

0 commit comments

Comments
 (0)