@@ -66,12 +66,13 @@ use stacks::net::api::postblock_proposal::{
66
66
BlockValidateResponse, ValidateRejectCode, TEST_VALIDATE_DELAY_DURATION_SECS,
67
67
TEST_VALIDATE_STALL,
68
68
};
69
+ use stacks::net::api::poststackerdbchunk::StackerDBErrorCodes;
69
70
use stacks::net::relay::fault_injection::{clear_ignore_block, set_ignore_block};
70
71
use stacks::types::chainstate::{
71
72
BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, StacksPrivateKey,
72
73
StacksPublicKey,
73
74
};
74
- use stacks::types::PublicKey;
75
+ use stacks::types::{PrivateKey, PublicKey} ;
75
76
use stacks::util::get_epoch_time_secs;
76
77
use stacks::util::hash::{hex_bytes, Hash160, MerkleHashFunc, Sha512Trunc256Sum};
77
78
use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
@@ -18477,3 +18478,186 @@ fn signers_do_not_commit_unless_threshold_precommitted() {
18477
18478
info!("------------------------- Shutdown -------------------------");
18478
18479
signer_test.shutdown();
18479
18480
}
18481
+
18482
+ // Test to ensure a signer operating a two phase commit signer will treat
18483
+ // signatures from other signers as pre-commits if it has yet to see their pre-commits
18484
+ // for that block. This enables upgraded pre-commit signers to operate as they should
18485
+ // with unupgraded signers or if the pre-commit message was somehow dropped.
18486
+ #[test]
18487
+ #[ignore]
18488
+ fn signers_treat_signatures_as_precommits() {
18489
+ if env::var("BITCOIND_TEST") != Ok("1".into()) {
18490
+ return;
18491
+ }
18492
+
18493
+ info!("------------------------- Test Setup -------------------------");
18494
+ let num_signers = 3;
18495
+
18496
+ let signer_test: SignerTest<SpawnedSigner> = SignerTest::new(num_signers, vec![]);
18497
+ let miner_sk = signer_test
18498
+ .running_nodes
18499
+ .conf
18500
+ .miner
18501
+ .mining_key
18502
+ .clone()
18503
+ .unwrap();
18504
+ let miner_pk = StacksPublicKey::from_private(&miner_sk);
18505
+ let all_signers = signer_test.signer_test_pks();
18506
+
18507
+ signer_test.boot_to_epoch_3();
18508
+
18509
+ let operating_signer = all_signers[0].clone();
18510
+ let disabled_signers = all_signers[1..].to_vec();
18511
+
18512
+ // Disable a majority of signers so that we can inject our own custom signatures to simulate an un-upgraded signer.
18513
+
18514
+ info!(
18515
+ "------------------------- Disabling {} Signers -------------------------",
18516
+ disabled_signers.len()
18517
+ );
18518
+
18519
+ TEST_IGNORE_ALL_BLOCK_PROPOSALS.set(disabled_signers.clone());
18520
+ let peer_info = signer_test.get_peer_info();
18521
+
18522
+ info!(
18523
+ "------------------------- Trigger Tenure Change Block Proposal -------------------------"
18524
+ );
18525
+ signer_test.mine_bitcoin_block();
18526
+
18527
+ let block_proposal = wait_for_block_proposal(30, peer_info.stacks_tip_height + 1, &miner_pk)
18528
+ .expect("Failed to propose a new tenure block");
18529
+
18530
+ info!(
18531
+ "------------------------- Verify Only Operating Signer Issues Pre-Commit -------------------------"
18532
+ );
18533
+
18534
+ let signer_signature_hash = block_proposal.header.signer_signature_hash();
18535
+ wait_for_block_pre_commits_from_signers(
18536
+ 30,
18537
+ &signer_signature_hash,
18538
+ &[operating_signer.clone()],
18539
+ )
18540
+ .expect("Operating signer did not send a pre-commit");
18541
+ assert!(
18542
+ wait_for_block_pre_commits_from_signers(10, &signer_signature_hash, &disabled_signers)
18543
+ .is_err(),
18544
+ "Disabled signers should not have issued any pre-commits"
18545
+ );
18546
+
18547
+ test_observer::clear();
18548
+
18549
+ let reward_cycle = signer_test.get_current_reward_cycle();
18550
+ // Do not send a signature for the operating signer. Just for the disabled. The operating signer should then issue as signature only after the other 2 signers send their signature
18551
+ // Only the operating signer should send a block pre commit.
18552
+ for (i, signer_private_key) in signer_test
18553
+ .signer_stacks_private_keys
18554
+ .iter()
18555
+ .enumerate()
18556
+ .skip(1)
18557
+ {
18558
+ let signature = signer_private_key
18559
+ .sign(signer_signature_hash.bits())
18560
+ .expect("Failed to sign block");
18561
+ let accepted = BlockResponse::accepted(
18562
+ block_proposal.header.signer_signature_hash(),
18563
+ signature,
18564
+ get_epoch_time_secs().wrapping_add(u64::MAX),
18565
+ );
18566
+
18567
+ let signers_contract_id =
18568
+ MessageSlotID::BlockResponse.stacker_db_contract(false, reward_cycle);
18569
+ let mut session = StackerDBSession::new(
18570
+ &signer_test.running_nodes.conf.node.rpc_bind,
18571
+ signers_contract_id,
18572
+ );
18573
+ let message = SignerMessage::BlockResponse(accepted);
18574
+
18575
+ // Manually submit signature
18576
+ let mut accepted = false;
18577
+ let mut version = 0;
18578
+ let start = Instant::now();
18579
+ info!(
18580
+ "------------------------- Manually Submitting Signer {i} Block Approval ------------------------",
18581
+ );
18582
+ // Don't know which slot corresponds to which signer, so just try all of them :)
18583
+ let mut slot_id = 0;
18584
+ while !accepted {
18585
+ let mut chunk = StackerDBChunkData::new(slot_id, version, message.serialize_to_vec());
18586
+ chunk
18587
+ .sign(&signer_private_key)
18588
+ .expect("Failed to sign message chunk");
18589
+ debug!("Produced a signature: {:?}", chunk.sig);
18590
+ let result = session.put_chunk(&chunk).expect("Failed to put chunk");
18591
+ accepted = result.accepted;
18592
+ if !accepted && result.code.unwrap() == StackerDBErrorCodes::BadSigner as u32 {
18593
+ slot_id += 1;
18594
+ assert!(
18595
+ slot_id < num_signers as u32,
18596
+ "Failed to find a matching slot id"
18597
+ );
18598
+ continue;
18599
+ }
18600
+ version += 1;
18601
+ debug!("Test Put Chunk ACK: {result:?}");
18602
+ assert!(
18603
+ start.elapsed() < Duration::from_secs(30),
18604
+ "Timed out waiting for signer signature to be accepted"
18605
+ );
18606
+ }
18607
+ if i == 1 {
18608
+ // Signer will not have seen enough signatures (fake pre-commits) to reach threshold
18609
+ info!("------------------------- Verifying Operating Signer Does NOT Issue a Signature ------------------------");
18610
+ } else {
18611
+ // Signer will have seen enough signatures (fake pre-commits) to reach threshold
18612
+ info!("------------------------- Verifying Operating Signer Issues a Signature ------------------------");
18613
+ }
18614
+ let result = wait_for(20, || {
18615
+ for chunk in test_observer::get_stackerdb_chunks()
18616
+ .into_iter()
18617
+ .flat_map(|chunk| chunk.modified_slots)
18618
+ {
18619
+ let message = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
18620
+ .expect("Failed to deserialize SignerMessage");
18621
+ let SignerMessage::BlockResponse(BlockResponse::Accepted(accepted)) = message
18622
+ else {
18623
+ continue;
18624
+ };
18625
+ assert_eq!(
18626
+ accepted.signer_signature_hash, signer_signature_hash,
18627
+ "Got an acceptance message for an unknown proposal"
18628
+ );
18629
+ let signed_by_operating_signer = operating_signer
18630
+ .verify(signer_signature_hash.bits(), &accepted.signature)
18631
+ .unwrap();
18632
+ if i == 1 {
18633
+ assert!(!signed_by_operating_signer, "The operating signer should only issue a signature once it sees BOTH signatures from the other signers");
18634
+ } else if signed_by_operating_signer {
18635
+ return Ok(true);
18636
+ }
18637
+ }
18638
+ Ok(false)
18639
+ });
18640
+ // If this is the first iteration of the loop (which starts from 1 since we skipped), the operating signer should do nothing (has yet to reach the threshold)
18641
+ if i == 1 {
18642
+ assert!(
18643
+ result.is_err(),
18644
+ "We saw a signature from the operating signer before our other two signers issued their signatures!"
18645
+ );
18646
+ } else {
18647
+ assert!(
18648
+ result.is_ok(),
18649
+ "We never saw our operating signer issue a signature!"
18650
+ );
18651
+ }
18652
+ }
18653
+
18654
+ info!("------------------------- Ensure Chain Advances -------------------------");
18655
+
18656
+ wait_for(30, || {
18657
+ Ok(signer_test.get_peer_info().stacks_tip_height > peer_info.stacks_tip_height)
18658
+ })
18659
+ .expect("We failed to mine the tenure change block");
18660
+
18661
+ info!("------------------------- Shutdown -------------------------");
18662
+ signer_test.shutdown();
18663
+ }
0 commit comments