Skip to content

Commit a584982

Browse files
committed
Add signers_treat_signatures_as_precommits test
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 04385f0 commit a584982

File tree

1 file changed

+185
-1
lines changed
  • stacks-node/src/tests/signer

1 file changed

+185
-1
lines changed

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

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ use stacks::net::api::postblock_proposal::{
6666
BlockValidateResponse, ValidateRejectCode, TEST_VALIDATE_DELAY_DURATION_SECS,
6767
TEST_VALIDATE_STALL,
6868
};
69+
use stacks::net::api::poststackerdbchunk::StackerDBErrorCodes;
6970
use stacks::net::relay::fault_injection::{clear_ignore_block, set_ignore_block};
7071
use stacks::types::chainstate::{
7172
BlockHeaderHash, BurnchainHeaderHash, StacksAddress, StacksBlockId, StacksPrivateKey,
7273
StacksPublicKey,
7374
};
74-
use stacks::types::PublicKey;
75+
use stacks::types::{PrivateKey, PublicKey};
7576
use stacks::util::get_epoch_time_secs;
7677
use stacks::util::hash::{hex_bytes, Hash160, MerkleHashFunc, Sha512Trunc256Sum};
7778
use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
@@ -18477,3 +18478,186 @@ fn signers_do_not_commit_unless_threshold_precommitted() {
1847718478
info!("------------------------- Shutdown -------------------------");
1847818479
signer_test.shutdown();
1847918480
}
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

Comments
 (0)