Skip to content

Commit f356db0

Browse files
authored
Merge pull request #5983 from obycode/feat/save-mempool-caches
feat: avoid clearing caches when block is successfully mined
2 parents f6da794 + 1169739 commit f356db0

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,9 @@ impl BlockMinerThread {
643643
"block_height" => new_block.header.chain_length,
644644
"consensus_hash" => %new_block.header.consensus_hash,
645645
);
646+
647+
// We successfully mined, so the mempool caches are valid.
648+
self.reset_nonce_cache = false;
646649
}
647650

648651
// update mined-block counters and mined-tenure counters

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

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13376,3 +13376,168 @@ fn signers_send_state_message_updates() {
1337613376
);
1337713377
miners.shutdown();
1337813378
}
13379+
13380+
#[test]
13381+
#[ignore]
13382+
/// Verify that the mempool caching is working as expected
13383+
///
13384+
/// This test will boot to epoch 3 then pause mining and:
13385+
/// 1. Set the signers to reject all blocks
13386+
/// 2. Submit a transfer from the sender to the recipient
13387+
/// 3. Wait for this block to be proposed
13388+
/// 4. Pause mining
13389+
/// 5. Wait for block rejection
13390+
/// 6. Check the nonce cache to see if it cached the nonce (it is paused before
13391+
/// the cache is cleared).
13392+
/// 7. Set the signers to accept blocks and unpause mining
13393+
/// 8. Wait for the block to be mined
13394+
/// 9. Check the nonce cache and verify that it has correctly cached the nonce
13395+
/// 10. Submit a second transfer and wait for it to be mined
13396+
fn verify_mempool_caches() {
13397+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
13398+
return;
13399+
}
13400+
tracing_subscriber::registry()
13401+
.with(fmt::layer())
13402+
.with(EnvFilter::from_default_env())
13403+
.init();
13404+
13405+
info!("------------------------- Test Setup -------------------------");
13406+
let num_signers = 5;
13407+
let sender_sk = Secp256k1PrivateKey::random();
13408+
let sender_addr = tests::to_addr(&sender_sk);
13409+
let send_amt = 100;
13410+
let send_fee = 180;
13411+
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
13412+
let mut signer_test: SignerTest<SpawnedSigner> =
13413+
SignerTest::new(num_signers, vec![(sender_addr, (send_amt + send_fee) * 3)]);
13414+
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
13415+
let miner_sk = signer_test.running_nodes.conf.miner.mining_key.unwrap();
13416+
let miner_pk = StacksPublicKey::from_private(&miner_sk);
13417+
13418+
signer_test.boot_to_epoch_3();
13419+
13420+
signer_test.mine_nakamoto_block(Duration::from_secs(60), true);
13421+
13422+
let info = get_chain_info(&signer_test.running_nodes.conf);
13423+
let block_height_before = info.stacks_tip_height;
13424+
13425+
// All signers reject all blocks
13426+
let rejecting_signers: Vec<_> = signer_test
13427+
.signer_stacks_private_keys
13428+
.iter()
13429+
.map(StacksPublicKey::from_private)
13430+
.collect();
13431+
TEST_REJECT_ALL_BLOCK_PROPOSAL.set(rejecting_signers);
13432+
13433+
// submit a tx so that the miner will mine a block
13434+
let transfer_tx = make_stacks_transfer(
13435+
&sender_sk,
13436+
0,
13437+
send_fee,
13438+
signer_test.running_nodes.conf.burnchain.chain_id,
13439+
&recipient,
13440+
send_amt,
13441+
);
13442+
submit_tx(&http_origin, &transfer_tx);
13443+
13444+
info!("Submitted transfer tx and waiting for block proposal");
13445+
let block = wait_for_block_proposal(30, block_height_before + 1, &miner_pk)
13446+
.expect("Timed out waiting for block proposal");
13447+
13448+
// Stall the miners so that this block is not re-proposed after being rejected
13449+
TEST_MINE_STALL.set(true);
13450+
13451+
// Wait for rejections
13452+
wait_for_block_rejections(30, block.header.signer_signature_hash(), num_signers)
13453+
.expect("Failed to get expected rejections for block");
13454+
13455+
// Check the nonce cache -- it should have the nonce cached because it will
13456+
// only be cleared after the miner is unpaused.
13457+
let mempool_db_path = format!(
13458+
"{}/nakamoto-neon/chainstate/mempool.sqlite",
13459+
signer_test.running_nodes.conf.node.working_dir
13460+
);
13461+
let conn = Connection::open(&mempool_db_path).unwrap();
13462+
let result = conn
13463+
.query_row(
13464+
"SELECT nonce FROM nonces WHERE address = ?1;",
13465+
[&sender_addr],
13466+
|row| {
13467+
let nonce: u64 = row.get(0)?;
13468+
Ok(nonce)
13469+
},
13470+
)
13471+
.expect("Failed to get nonce from cache");
13472+
assert_eq!(result, 1);
13473+
13474+
info!("Nonce cache has the expected nonce");
13475+
13476+
// Set signers to accept and unpause the miners
13477+
TEST_REJECT_ALL_BLOCK_PROPOSAL.set(vec![]);
13478+
TEST_MINE_STALL.set(false);
13479+
13480+
info!("Unpausing miners and waiting for block to be mined");
13481+
13482+
// Wait for the block to be mined
13483+
wait_for(60, || {
13484+
let is_next_block = test_observer::get_blocks()
13485+
.last()
13486+
.and_then(|block| block["block_height"].as_u64())
13487+
.map_or(false, |h| h == block_height_before + 1);
13488+
13489+
Ok(is_next_block)
13490+
})
13491+
.expect("Timed out waiting for block to be mined");
13492+
13493+
// Check the nonce cache again -- it should still have the nonce cached
13494+
let result = conn
13495+
.query_row(
13496+
"SELECT nonce FROM nonces WHERE address = ?1;",
13497+
[&sender_addr],
13498+
|row| {
13499+
let nonce: u64 = row.get(0)?;
13500+
Ok(nonce)
13501+
},
13502+
)
13503+
.expect("Failed to get nonce from cache");
13504+
assert_eq!(result, 1);
13505+
13506+
info!("Nonce cache has the expected nonce after successfully mining block");
13507+
13508+
let transfer_tx = make_stacks_transfer(
13509+
&sender_sk,
13510+
1,
13511+
send_fee,
13512+
signer_test.running_nodes.conf.burnchain.chain_id,
13513+
&recipient,
13514+
send_amt,
13515+
);
13516+
submit_tx(&http_origin, &transfer_tx);
13517+
13518+
info!("Waiting for the second block to be mined");
13519+
wait_for(60, || {
13520+
let blocks = test_observer::get_blocks();
13521+
13522+
// Look for a block with expected height
13523+
let Some(block) = blocks
13524+
.iter()
13525+
.find(|block| block["block_height"].as_u64() == Some(block_height_before + 2))
13526+
else {
13527+
return Ok(false); // Keep waiting if the block hasn't been observed yet
13528+
};
13529+
13530+
// This new block should have just the one new transfer
13531+
let transfers_included_in_block = transfers_in_block(block);
13532+
if transfers_included_in_block == 1 {
13533+
Ok(true)
13534+
} else {
13535+
Err(format!(
13536+
"Expected only one transfer in block, found {transfers_included_in_block}"
13537+
))
13538+
}
13539+
})
13540+
.expect("Timed out waiting for block");
13541+
13542+
signer_test.shutdown();
13543+
}

0 commit comments

Comments
 (0)