Skip to content

Commit 02fdee4

Browse files
authored
Merge pull request #5084 from stacks-network/bugfix/block-proposal-rejection
Fix block proposal rejection test
2 parents cd4e99c + 32e2875 commit 02fdee4

File tree

3 files changed

+96
-72
lines changed

3 files changed

+96
-72
lines changed

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

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ use stacks::chainstate::nakamoto::signer_set::NakamotoSigners;
4343
use stacks::chainstate::stacks::boot::{NakamotoSignerEntry, SIGNERS_NAME};
4444
use stacks::chainstate::stacks::{StacksPrivateKey, ThresholdSignature};
4545
use stacks::core::StacksEpoch;
46-
use stacks::net::api::postblock_proposal::BlockValidateResponse;
46+
use stacks::net::api::postblock_proposal::{
47+
BlockValidateOk, BlockValidateReject, BlockValidateResponse,
48+
};
4749
use stacks::types::chainstate::StacksAddress;
4850
use stacks::util::secp256k1::{MessageSignature, Secp256k1PublicKey};
4951
use stacks_common::codec::StacksMessageCodec;
@@ -99,6 +101,7 @@ pub struct SignerTest<S> {
99101
// The spawned signers and their threads
100102
pub spawned_signers: Vec<S>,
101103
// The spawned signers and their threads
104+
#[allow(dead_code)]
102105
pub signer_configs: Vec<SignerConfig>,
103106
// the private keys of the signers
104107
pub signer_stacks_private_keys: Vec<StacksPrivateKey>,
@@ -481,35 +484,47 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
481484
panic!("Timed out while waiting for confirmation of block with signer sighash = {block_signer_sighash}")
482485
}
483486

484-
fn wait_for_block_validate_response(&mut self, timeout: Duration) -> BlockValidateResponse {
487+
fn wait_for_validate_ok_response(&mut self, timeout: Duration) -> BlockValidateOk {
485488
// Wait for the block to show up in the test observer
486489
let t_start = Instant::now();
487-
while test_observer::get_proposal_responses().is_empty() {
490+
loop {
491+
let responses = test_observer::get_proposal_responses();
492+
for response in responses {
493+
let BlockValidateResponse::Ok(validation) = response else {
494+
continue;
495+
};
496+
return validation;
497+
}
488498
assert!(
489499
t_start.elapsed() < timeout,
490-
"Timed out while waiting for block proposal response event"
500+
"Timed out while waiting for block proposal ok event"
491501
);
492502
thread::sleep(Duration::from_secs(1));
493503
}
494-
test_observer::get_proposal_responses()
495-
.pop()
496-
.expect("No block proposal")
497-
}
498-
499-
fn wait_for_validate_ok_response(&mut self, timeout: Duration) -> Sha512Trunc256Sum {
500-
let validate_response = self.wait_for_block_validate_response(timeout);
501-
match validate_response {
502-
BlockValidateResponse::Ok(block_validated) => block_validated.signer_signature_hash,
503-
_ => panic!("Unexpected response"),
504-
}
505504
}
506505

507-
fn wait_for_validate_reject_response(&mut self, timeout: Duration) -> Sha512Trunc256Sum {
506+
fn wait_for_validate_reject_response(
507+
&mut self,
508+
timeout: Duration,
509+
signer_signature_hash: Sha512Trunc256Sum,
510+
) -> BlockValidateReject {
508511
// Wait for the block to show up in the test observer
509-
let validate_response = self.wait_for_block_validate_response(timeout);
510-
match validate_response {
511-
BlockValidateResponse::Reject(block_rejection) => block_rejection.signer_signature_hash,
512-
_ => panic!("Unexpected response"),
512+
let t_start = Instant::now();
513+
loop {
514+
let responses = test_observer::get_proposal_responses();
515+
for response in responses {
516+
let BlockValidateResponse::Reject(rejection) = response else {
517+
continue;
518+
};
519+
if rejection.signer_signature_hash == signer_signature_hash {
520+
return rejection;
521+
}
522+
}
523+
assert!(
524+
t_start.elapsed() < timeout,
525+
"Timed out while waiting for block proposal reject event"
526+
);
527+
thread::sleep(Duration::from_secs(1));
513528
}
514529
}
515530

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

Lines changed: 52 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use std::{env, thread};
2323
use clarity::vm::types::PrincipalData;
2424
use clarity::vm::StacksEpoch;
2525
use libsigner::v0::messages::{
26-
BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerMessage,
26+
BlockRejection, BlockResponse, MessageSlotID, MinerSlotID, RejectCode, SignerMessage,
2727
};
2828
use libsigner::{BlockProposal, SignerSession, StackerDBSession};
2929
use rand::RngCore;
@@ -36,7 +36,7 @@ use stacks::chainstate::stacks::db::{StacksBlockHeaderTypes, StacksChainState, S
3636
use stacks::codec::StacksMessageCodec;
3737
use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET};
3838
use stacks::libstackerdb::StackerDBChunkData;
39-
use stacks::net::api::postblock_proposal::TEST_VALIDATE_STALL;
39+
use stacks::net::api::postblock_proposal::{ValidateRejectCode, TEST_VALIDATE_STALL};
4040
use stacks::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey};
4141
use stacks::types::PublicKey;
4242
use stacks::util::hash::MerkleHashFunc;
@@ -51,7 +51,6 @@ use stacks_common::util::sleep_ms;
5151
use stacks_signer::chainstate::{ProposalEvalConfig, SortitionsView};
5252
use stacks_signer::client::{SignerSlotID, StackerDB};
5353
use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerConfig, Network};
54-
use stacks_signer::runloop::State;
5554
use stacks_signer::v0::SpawnedSigner;
5655
use tracing_subscriber::prelude::*;
5756
use tracing_subscriber::{fmt, EnvFilter};
@@ -64,9 +63,8 @@ use crate::nakamoto_node::sign_coordinator::TEST_IGNORE_SIGNERS;
6463
use crate::neon::Counters;
6564
use crate::run_loop::boot_nakamoto;
6665
use crate::tests::nakamoto_integrations::{
67-
boot_to_epoch_25, boot_to_epoch_3_reward_set, boot_to_epoch_3_reward_set_calculation_boundary,
68-
next_block_and, setup_epoch_3_reward_set, wait_for, POX_4_DEFAULT_STACKER_BALANCE,
69-
POX_4_DEFAULT_STACKER_STX_AMT,
66+
boot_to_epoch_25, boot_to_epoch_3_reward_set, next_block_and, setup_epoch_3_reward_set,
67+
wait_for, POX_4_DEFAULT_STACKER_BALANCE, POX_4_DEFAULT_STACKER_STX_AMT,
7068
};
7169
use crate::tests::neon_integrations::{
7270
get_account, get_chain_info, next_block_and_wait, run_until_burnchain_height, submit_tx,
@@ -298,7 +296,9 @@ impl SignerTest<SpawnedSigner> {
298296
self.mine_nakamoto_block(timeout);
299297

300298
// Verify that the signers accepted the proposed block, sending back a validate ok response
301-
let proposed_signer_signature_hash = self.wait_for_validate_ok_response(timeout);
299+
let proposed_signer_signature_hash = self
300+
.wait_for_validate_ok_response(timeout)
301+
.signer_signature_hash;
302302
let message = proposed_signer_signature_hash.0;
303303

304304
info!("------------------------- Test Block Signed -------------------------");
@@ -368,7 +368,7 @@ impl SignerTest<SpawnedSigner> {
368368
}
369369

370370
/// Propose an invalid block to the signers
371-
fn propose_block(&mut self, slot_id: u32, version: u32, block: NakamotoBlock) {
371+
fn propose_block(&mut self, block: NakamotoBlock, timeout: Duration) {
372372
let miners_contract_id = boot_code_id(MINERS_NAME, false);
373373
let mut session =
374374
StackerDBSession::new(&self.running_nodes.conf.node.rpc_bind, miners_contract_id);
@@ -388,17 +388,26 @@ impl SignerTest<SpawnedSigner> {
388388
.miner
389389
.mining_key
390390
.expect("No mining key");
391-
392391
// Submit the block proposal to the miner's slot
393-
let mut chunk = StackerDBChunkData::new(slot_id, version, message.serialize_to_vec());
394-
chunk.sign(&miner_sk).expect("Failed to sign message chunk");
395-
debug!("Produced a signature: {:?}", chunk.sig);
396-
let result = session.put_chunk(&chunk).expect("Failed to put chunk");
397-
debug!("Test Put Chunk ACK: {result:?}");
398-
assert!(
399-
result.accepted,
400-
"Failed to submit block proposal to signers"
401-
);
392+
let mut accepted = false;
393+
let mut version = 0;
394+
let slot_id = MinerSlotID::BlockProposal.to_u8() as u32;
395+
let start = Instant::now();
396+
debug!("Proposing invalid block to signers");
397+
while !accepted {
398+
let mut chunk =
399+
StackerDBChunkData::new(slot_id * 2, version, message.serialize_to_vec());
400+
chunk.sign(&miner_sk).expect("Failed to sign message chunk");
401+
debug!("Produced a signature: {:?}", chunk.sig);
402+
let result = session.put_chunk(&chunk).expect("Failed to put chunk");
403+
accepted = result.accepted;
404+
version += 1;
405+
debug!("Test Put Chunk ACK: {result:?}");
406+
assert!(
407+
start.elapsed() < timeout,
408+
"Timed out waiting for block proposal to be accepted"
409+
);
410+
}
402411
}
403412
}
404413

@@ -434,12 +443,10 @@ fn block_proposal_rejection() {
434443
let short_timeout = Duration::from_secs(30);
435444

436445
info!("------------------------- Send Block Proposal To Signers -------------------------");
437-
let reward_cycle = signer_test.get_current_reward_cycle();
438446
let proposal_conf = ProposalEvalConfig {
439447
first_proposal_burn_block_timing: Duration::from_secs(0),
440448
block_proposal_timeout: Duration::from_secs(100),
441449
};
442-
let view = SortitionsView::fetch_view(proposal_conf, &signer_test.stacks_client).unwrap();
443450
let mut block = NakamotoBlock {
444451
header: NakamotoBlockHeader::empty(),
445452
txs: vec![],
@@ -448,48 +455,44 @@ fn block_proposal_rejection() {
448455
// First propose a block to the signers that does not have the correct consensus hash or BitVec. This should be rejected BEFORE
449456
// the block is submitted to the node for validation.
450457
let block_signer_signature_hash_1 = block.header.signer_signature_hash();
451-
signer_test.propose_block(0, 1, block.clone());
458+
signer_test.propose_block(block.clone(), short_timeout);
459+
460+
// Wait for the first block to be mined successfully so we have the most up to date sortition view
461+
signer_test.wait_for_validate_ok_response(short_timeout);
452462

453463
// Propose a block to the signers that passes initial checks but will be rejected by the stacks node
464+
let view = SortitionsView::fetch_view(proposal_conf, &signer_test.stacks_client).unwrap();
454465
block.header.pox_treatment = BitVec::ones(1).unwrap();
455466
block.header.consensus_hash = view.cur_sortition.consensus_hash;
467+
block.header.chain_length = 35; // We have mined 35 blocks so far.
456468

457469
let block_signer_signature_hash_2 = block.header.signer_signature_hash();
458-
signer_test.propose_block(0, 2, block);
470+
signer_test.propose_block(block, short_timeout);
459471

460472
info!("------------------------- Test Block Proposal Rejected -------------------------");
461473
// Verify the signers rejected the second block via the endpoint
462-
let rejected_block_hash = signer_test.wait_for_validate_reject_response(short_timeout);
463-
assert_eq!(rejected_block_hash, block_signer_signature_hash_2);
464-
465-
let mut stackerdb = StackerDB::new(
466-
&signer_test.running_nodes.conf.node.rpc_bind,
467-
StacksPrivateKey::new(), // We are just reading so don't care what the key is
468-
false,
469-
reward_cycle,
470-
SignerSlotID(0), // We are just reading so again, don't care about index.
471-
);
472-
473-
let signer_slot_ids: Vec<_> = signer_test
474-
.get_signer_indices(reward_cycle)
475-
.iter()
476-
.map(|id| id.0)
477-
.collect();
478-
assert_eq!(signer_slot_ids.len(), num_signers);
474+
let reject =
475+
signer_test.wait_for_validate_reject_response(short_timeout, block_signer_signature_hash_2);
476+
assert!(matches!(
477+
reject.reason_code,
478+
ValidateRejectCode::UnknownParent
479+
));
479480

480481
let start_polling = Instant::now();
481482
let mut found_signer_signature_hash_1 = false;
482483
let mut found_signer_signature_hash_2 = false;
483484
while !found_signer_signature_hash_1 && !found_signer_signature_hash_2 {
484485
std::thread::sleep(Duration::from_secs(1));
485-
let messages: Vec<SignerMessage> = StackerDB::get_messages(
486-
stackerdb
487-
.get_session_mut(&MessageSlotID::BlockResponse)
488-
.expect("Failed to get BlockResponse stackerdb session"),
489-
&signer_slot_ids,
490-
)
491-
.expect("Failed to get message from stackerdb");
492-
for message in messages {
486+
let chunks = test_observer::get_stackerdb_chunks();
487+
for chunk in chunks
488+
.into_iter()
489+
.map(|chunk| chunk.modified_slots)
490+
.flatten()
491+
{
492+
let Ok(message) = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
493+
else {
494+
continue;
495+
};
493496
if let SignerMessage::BlockResponse(BlockResponse::Rejected(BlockRejection {
494497
reason: _reason,
495498
reason_code,
@@ -503,10 +506,10 @@ fn block_proposal_rejection() {
503506
found_signer_signature_hash_2 = true;
504507
assert!(matches!(reason_code, RejectCode::ValidationFailed(_)));
505508
} else {
506-
panic!("Unexpected signer signature hash");
509+
continue;
507510
}
508511
} else {
509-
panic!("Unexpected message type");
512+
continue;
510513
}
511514
}
512515
assert!(

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,9 @@ fn block_proposal() {
893893

894894
info!("------------------------- Test Block Proposal -------------------------");
895895
// Verify that the signers accepted the proposed block, sending back a validate ok response
896-
let proposed_signer_signature_hash = signer_test.wait_for_validate_ok_response(short_timeout);
896+
let proposed_signer_signature_hash = signer_test
897+
.wait_for_validate_ok_response(short_timeout)
898+
.signer_signature_hash;
897899

898900
info!("------------------------- Test Block Signed -------------------------");
899901
// Verify that the signers signed the proposed block
@@ -1115,7 +1117,9 @@ fn sign_after_signer_reboot() {
11151117
info!("------------------------- Test Mine Block -------------------------");
11161118

11171119
signer_test.mine_nakamoto_block(timeout);
1118-
let proposed_signer_signature_hash = signer_test.wait_for_validate_ok_response(short_timeout);
1120+
let proposed_signer_signature_hash = signer_test
1121+
.wait_for_validate_ok_response(short_timeout)
1122+
.signer_signature_hash;
11191123
let signature =
11201124
signer_test.wait_for_confirmed_block_v1(&proposed_signer_signature_hash, short_timeout);
11211125

@@ -1136,7 +1140,9 @@ fn sign_after_signer_reboot() {
11361140
info!("------------------------- Test Mine Block after restart -------------------------");
11371141

11381142
let last_block = signer_test.mine_nakamoto_block(timeout);
1139-
let proposed_signer_signature_hash = signer_test.wait_for_validate_ok_response(short_timeout);
1143+
let proposed_signer_signature_hash = signer_test
1144+
.wait_for_validate_ok_response(short_timeout)
1145+
.signer_signature_hash;
11401146
let frost_signature =
11411147
signer_test.wait_for_confirmed_block_v1(&proposed_signer_signature_hash, short_timeout);
11421148

0 commit comments

Comments
 (0)