Skip to content

Commit c197579

Browse files
committed
Add block_proposal_timeout test
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 465c4fa commit c197579

File tree

2 files changed

+152
-24
lines changed

2 files changed

+152
-24
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ jobs:
139139
- tests::signer::v0::incoming_signers_ignore_block_proposals
140140
- tests::signer::v0::outgoing_signers_ignore_block_proposals
141141
- tests::signer::v0::injected_signatures_are_ignored_across_boundaries
142+
- tests::signer::v0::block_proposal_timeout
142143
- tests::nakamoto_integrations::burn_ops_integration_test
143144
- tests::nakamoto_integrations::check_block_heights
144145
- tests::nakamoto_integrations::clarity_burn_state

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

Lines changed: 151 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10854,12 +10854,11 @@ fn rejected_blocks_count_towards_miner_validity() {
1085410854
let sender_addr = tests::to_addr(&sender_sk);
1085510855
let send_amt = 100;
1085610856
let send_fee = 180;
10857-
let nmb_txs = 2;
1085810857
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
1085910858
let block_proposal_timeout = Duration::from_secs(20);
1086010859
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
1086110860
num_signers,
10862-
vec![(sender_addr, (send_amt + send_fee) * nmb_txs)],
10861+
vec![(sender_addr, send_amt + send_fee)],
1086310862
|config| {
1086410863
config.block_proposal_timeout = block_proposal_timeout;
1086510864
},
@@ -10955,30 +10954,31 @@ fn rejected_blocks_count_towards_miner_validity() {
1095510954
info!("------------------------- Wait for Block N' Rejection -------------------------");
1095610955
// TODO: need 429 handling enabled for this to pass here
1095710956
wait_for(30, || {
10958-
let chunks = test_observer::get_stackerdb_chunks();
10959-
for chunk in chunks.into_iter().flat_map(|chunk| chunk.modified_slots) {
10960-
let Ok(message) = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
10961-
else {
10962-
continue;
10963-
};
10964-
if let SignerMessage::BlockResponse(BlockResponse::Rejected(BlockRejection {
10965-
reason: _reason,
10966-
reason_code,
10967-
signer_signature_hash,
10968-
..
10969-
})) = message
10970-
{
10971-
if signer_signature_hash
10972-
== block_proposal_n_prime.block.header.signer_signature_hash()
10973-
{
10974-
assert_eq!(reason_code, RejectCode::SortitionViewMismatch);
10975-
return Ok(true);
10957+
let stackerdb_events = test_observer::get_stackerdb_chunks();
10958+
let block_rejections = stackerdb_events
10959+
.into_iter()
10960+
.flat_map(|chunk| chunk.modified_slots)
10961+
.filter_map(|chunk| {
10962+
let message = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
10963+
.expect("Failed to deserialize SignerMessage");
10964+
match message {
10965+
SignerMessage::BlockResponse(BlockResponse::Rejected(rejection)) => {
10966+
if rejection.signer_signature_hash
10967+
== block_proposal_n_prime.block.header.signer_signature_hash()
10968+
{
10969+
assert_eq!(rejection.reason_code, RejectCode::SortitionViewMismatch);
10970+
Some(rejection)
10971+
} else {
10972+
None
10973+
}
10974+
}
10975+
_ => None,
1097610976
}
10977-
}
10978-
}
10979-
Ok(false)
10977+
})
10978+
.collect::<Vec<_>>();
10979+
Ok(block_rejections.len() >= num_signers * 7 / 10)
1098010980
})
10981-
.expect("Timed out waiting for N' block rejection");
10981+
.expect("FAIL: Timed out waiting for block proposal rejections of N'");
1098210982

1098310983
info!("------------------------- Test Mine Block N+1 -------------------------");
1098410984
// The signer should automatically attempt to mine a new block once the signers eventually tell it to abandon the previous block
@@ -11002,3 +11002,130 @@ fn rejected_blocks_count_towards_miner_validity() {
1100211002
);
1100311003
signer_test.shutdown();
1100411004
}
11005+
11006+
#[test]
11007+
#[ignore]
11008+
/// Test that signers mark a miner malicious if it doesn't propose any blocks before the block proposal timeout
11009+
///
11010+
/// Test Setup:
11011+
/// The test spins up five stacks signers, one miner Nakamoto node, and a corresponding bitcoind.
11012+
/// The stacks node is then advanced to Epoch 3.0 boundary to allow block signing. The block proposal timeout is set to 20 seconds.
11013+
///
11014+
/// Test Execution:
11015+
/// Block proposals are paused for the miner.
11016+
/// Tenure A starts.
11017+
/// The test waits for the block proposal timeout + 1 second.
11018+
/// Block proposals are unpaused for the miner.
11019+
/// Miner propose a block N.
11020+
/// Signers reject the block and mark the miner as malicious.
11021+
///
11022+
//
11023+
/// Test Assertion:
11024+
/// Stacks tip does not advance to block N.
11025+
fn block_proposal_timeout() {
11026+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
11027+
return;
11028+
}
11029+
11030+
tracing_subscriber::registry()
11031+
.with(fmt::layer())
11032+
.with(EnvFilter::from_default_env())
11033+
.init();
11034+
11035+
info!("------------------------- Test Setup -------------------------");
11036+
let num_signers = 5;
11037+
let block_proposal_timeout = Duration::from_secs(20);
11038+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
11039+
num_signers,
11040+
vec![],
11041+
|config| {
11042+
config.block_proposal_timeout = block_proposal_timeout;
11043+
},
11044+
|_| {},
11045+
None,
11046+
None,
11047+
);
11048+
11049+
signer_test.boot_to_epoch_3();
11050+
11051+
// Pause the miner's block proposals
11052+
TEST_BROADCAST_STALL.set(true);
11053+
11054+
let wait_for_block_proposal = || {
11055+
let mut block_proposal = None;
11056+
let _ = wait_for(30, || {
11057+
block_proposal = test_observer::get_stackerdb_chunks()
11058+
.into_iter()
11059+
.flat_map(|chunk| chunk.modified_slots)
11060+
.find_map(|chunk| {
11061+
let message = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
11062+
.expect("Failed to deserialize SignerMessage");
11063+
if let SignerMessage::BlockProposal(proposal) = message {
11064+
return Some(proposal);
11065+
}
11066+
None
11067+
});
11068+
Ok(block_proposal.is_some())
11069+
});
11070+
block_proposal
11071+
};
11072+
11073+
info!("------------------------- Start Tenure A -------------------------");
11074+
let commits_before = signer_test
11075+
.running_nodes
11076+
.commits_submitted
11077+
.load(Ordering::SeqCst);
11078+
11079+
next_block_and(
11080+
&mut signer_test.running_nodes.btc_regtest_controller,
11081+
60,
11082+
|| {
11083+
let commits_count = signer_test
11084+
.running_nodes
11085+
.commits_submitted
11086+
.load(Ordering::SeqCst);
11087+
Ok(commits_count > commits_before)
11088+
},
11089+
)
11090+
.unwrap();
11091+
11092+
let chain_before = get_chain_info(&signer_test.running_nodes.conf);
11093+
std::thread::sleep(block_proposal_timeout.add(Duration::from_secs(1)));
11094+
test_observer::clear();
11095+
11096+
info!("------------------------- Attempt Mine Block N -------------------------");
11097+
TEST_BROADCAST_STALL.set(false);
11098+
11099+
let block_proposal_n = wait_for_block_proposal().expect("Failed to get block proposal N");
11100+
11101+
wait_for(30, || {
11102+
let stackerdb_events = test_observer::get_stackerdb_chunks();
11103+
let block_rejections = stackerdb_events
11104+
.into_iter()
11105+
.flat_map(|chunk| chunk.modified_slots)
11106+
.filter_map(|chunk| {
11107+
let message = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
11108+
.expect("Failed to deserialize SignerMessage");
11109+
match message {
11110+
SignerMessage::BlockResponse(BlockResponse::Rejected(rejection)) => {
11111+
if rejection.signer_signature_hash
11112+
== block_proposal_n.block.header.signer_signature_hash()
11113+
{
11114+
assert_eq!(rejection.reason_code, RejectCode::SortitionViewMismatch);
11115+
Some(rejection)
11116+
} else {
11117+
None
11118+
}
11119+
}
11120+
_ => None,
11121+
}
11122+
})
11123+
.collect::<Vec<_>>();
11124+
Ok(block_rejections.len() >= num_signers * 7 / 10)
11125+
})
11126+
.expect("FAIL: Timed out waiting for block proposal rejections");
11127+
11128+
let chain_after = get_chain_info(&signer_test.running_nodes.conf);
11129+
assert_eq!(chain_after, chain_before);
11130+
signer_test.shutdown();
11131+
}

0 commit comments

Comments
 (0)