Skip to content

Commit 3057547

Browse files
committed
test(signer): Add test with duplicate signer config
1 parent 2be69cb commit 3057547

File tree

3 files changed

+121
-33
lines changed

3 files changed

+121
-33
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ jobs:
9999
- tests::signer::v0::reloads_signer_set_in
100100
- tests::signer::v0::signers_broadcast_signed_blocks
101101
- tests::signer::v0::min_gap_between_blocks
102+
- tests::signer::v0::duplicate_signers
102103
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
103104
- tests::nakamoto_integrations::check_block_heights
104105
- tests::nakamoto_integrations::clarity_burn_state

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

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
125125
wait_on_signers,
126126
|_| {},
127127
|_| {},
128-
&[],
128+
None,
129+
None,
129130
)
130131
}
131132

@@ -138,12 +139,19 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
138139
wait_on_signers: Option<Duration>,
139140
mut signer_config_modifier: F,
140141
mut node_config_modifier: G,
141-
btc_miner_pubkeys: &[Secp256k1PublicKey],
142+
btc_miner_pubkeys: Option<Vec<Secp256k1PublicKey>>,
143+
signer_stacks_private_keys: Option<Vec<StacksPrivateKey>>,
142144
) -> Self {
143145
// Generate Signer Data
144-
let signer_stacks_private_keys = (0..num_signers)
145-
.map(|_| StacksPrivateKey::new())
146-
.collect::<Vec<StacksPrivateKey>>();
146+
let signer_stacks_private_keys = signer_stacks_private_keys
147+
.inspect(|keys| {
148+
assert_eq!(
149+
keys.len(),
150+
num_signers,
151+
"Number of private keys does not match number of signers"
152+
)
153+
})
154+
.unwrap_or_else(|| (0..num_signers).map(|_| StacksPrivateKey::new()).collect());
147155

148156
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
149157

@@ -159,11 +167,8 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
159167
// That's the kind of thing an idiot would have on his luggage!
160168
let password = "12345";
161169
naka_conf.connection_options.auth_token = Some(password.to_string());
162-
if let Some(wait_on_signers) = wait_on_signers {
163-
naka_conf.miner.wait_on_signers = wait_on_signers;
164-
} else {
165-
naka_conf.miner.wait_on_signers = Duration::from_secs(10);
166-
}
170+
naka_conf.miner.wait_on_signers =
171+
wait_on_signers.unwrap_or_else(|| Duration::from_secs(10));
167172
let run_stamp = rand::random();
168173

169174
// Setup the signer and coordinator configurations
@@ -195,19 +200,20 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
195200
.collect();
196201

197202
// Setup the nodes and deploy the contract to it
198-
let btc_miner_pubkeys = if btc_miner_pubkeys.is_empty() {
199-
let pk = Secp256k1PublicKey::from_hex(
200-
naka_conf
201-
.burnchain
202-
.local_mining_public_key
203-
.as_ref()
204-
.unwrap(),
205-
)
206-
.unwrap();
207-
vec![pk]
208-
} else {
209-
btc_miner_pubkeys.to_vec()
210-
};
203+
let btc_miner_pubkeys = btc_miner_pubkeys
204+
.filter(|keys| !keys.is_empty())
205+
.unwrap_or_else(|| {
206+
let pk = Secp256k1PublicKey::from_hex(
207+
naka_conf
208+
.burnchain
209+
.local_mining_public_key
210+
.as_ref()
211+
.unwrap(),
212+
)
213+
.unwrap();
214+
vec![pk]
215+
});
216+
211217
let node = setup_stx_btc_node(
212218
naka_conf,
213219
&signer_stacks_private_keys,
@@ -236,10 +242,10 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
236242
continue;
237243
}
238244
let port = 3000 + signer_ix;
239-
let endpoint = format!("http://localhost:{}", port);
245+
let endpoint = format!("http://localhost:{port}");
240246
let path = format!("{endpoint}/status");
241247

242-
debug!("Issue status request to {}", &path);
248+
debug!("Issue status request to {path}");
243249
let client = reqwest::blocking::Client::new();
244250
let response = client
245251
.get(path)

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

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,8 @@ fn reloads_signer_set_in() {
796796
Some(Duration::from_secs(15)),
797797
|_config| {},
798798
|_| {},
799-
&[],
799+
None,
800+
None,
800801
);
801802

802803
setup_epoch_3_reward_set(
@@ -925,7 +926,8 @@ fn forked_tenure_testing(
925926
config.broadcast_signed_blocks = false;
926927
},
927928
|_| {},
928-
&[],
929+
None,
930+
None,
929931
);
930932
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
931933

@@ -1429,7 +1431,8 @@ fn multiple_miners() {
14291431
false
14301432
})
14311433
},
1432-
&[btc_miner_1_pk.clone(), btc_miner_2_pk.clone()],
1434+
Some(vec![btc_miner_1_pk.clone(), btc_miner_2_pk.clone()]),
1435+
None,
14331436
);
14341437
let conf = signer_test.running_nodes.conf.clone();
14351438
let mut conf_node_2 = conf.clone();
@@ -1694,7 +1697,8 @@ fn miner_forking() {
16941697
false
16951698
})
16961699
},
1697-
&[btc_miner_1_pk.clone(), btc_miner_2_pk.clone()],
1700+
Some(vec![btc_miner_1_pk.clone(), btc_miner_2_pk.clone()]),
1701+
None,
16981702
);
16991703
let conf = signer_test.running_nodes.conf.clone();
17001704
let mut conf_node_2 = conf.clone();
@@ -2274,7 +2278,8 @@ fn empty_sortition() {
22742278
config.block_proposal_timeout = block_proposal_timeout;
22752279
},
22762280
|_| {},
2277-
&[],
2281+
None,
2282+
None,
22782283
);
22792284
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
22802285
let short_timeout = Duration::from_secs(20);
@@ -2455,7 +2460,8 @@ fn mock_sign_epoch_25() {
24552460
}
24562461
}
24572462
},
2458-
&[],
2463+
None,
2464+
None,
24592465
);
24602466

24612467
let epochs = signer_test
@@ -2659,7 +2665,8 @@ fn signer_set_rollover() {
26592665
}
26602666
naka_conf.node.rpc_bind = rpc_bind.clone();
26612667
},
2662-
&[],
2668+
None,
2669+
None,
26632670
);
26642671
assert_eq!(
26652672
new_spawned_signers[0].config.node_host,
@@ -2873,7 +2880,8 @@ fn min_gap_between_blocks() {
28732880
|config| {
28742881
config.miner.min_time_between_blocks_ms = time_between_blocks_ms;
28752882
},
2876-
&[],
2883+
None,
2884+
None,
28772885
);
28782886

28792887
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
@@ -2945,3 +2953,76 @@ fn min_gap_between_blocks() {
29452953

29462954
signer_test.shutdown();
29472955
}
2956+
2957+
#[test]
2958+
#[ignore]
2959+
/// Test scenario where there are duplicate signers with the same private key
2960+
/// First submitted signature should take precedence
2961+
fn duplicate_signers() {
2962+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
2963+
return;
2964+
}
2965+
2966+
tracing_subscriber::registry()
2967+
.with(fmt::layer())
2968+
.with(EnvFilter::from_default_env())
2969+
.init();
2970+
2971+
// Disable p2p broadcast of the nakamoto blocks, so that we rely
2972+
// on the signer's using StackerDB to get pushed blocks
2973+
*nakamoto_node::miner::TEST_SKIP_P2P_BROADCAST
2974+
.lock()
2975+
.unwrap() = Some(true);
2976+
2977+
info!("------------------------- Test Setup -------------------------");
2978+
let num_signers = 5;
2979+
let mut signer_stacks_private_keys = (0..num_signers)
2980+
.map(|_| StacksPrivateKey::new())
2981+
.collect::<Vec<_>>();
2982+
2983+
// First two signers have same private key
2984+
signer_stacks_private_keys[1] = signer_stacks_private_keys[0];
2985+
2986+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
2987+
num_signers,
2988+
vec![],
2989+
None,
2990+
|_| {},
2991+
|_| {},
2992+
None,
2993+
Some(signer_stacks_private_keys),
2994+
);
2995+
let timeout = Duration::from_secs(30);
2996+
let mined_blocks = signer_test.running_nodes.nakamoto_blocks_mined.clone();
2997+
let blocks_mined_before = mined_blocks.load(Ordering::SeqCst);
2998+
2999+
signer_test.boot_to_epoch_3();
3000+
3001+
// give the system a chance to reach the Nakamoto start tip
3002+
// mine a Nakamoto block
3003+
wait_for(30, || {
3004+
let blocks_mined = mined_blocks.load(Ordering::SeqCst);
3005+
Ok(blocks_mined > blocks_mined_before)
3006+
})
3007+
.unwrap();
3008+
3009+
info!("------------------------- Test Mine and Verify Confirmed Nakamoto Block -------------------------");
3010+
signer_test.mine_and_verify_confirmed_naka_block(timeout, num_signers);
3011+
3012+
// Test prometheus metrics response
3013+
#[cfg(feature = "monitoring_prom")]
3014+
{
3015+
let metrics_response = signer_test.get_signer_metrics();
3016+
3017+
// Because 5 signers are running in the same process, the prometheus metrics
3018+
// are incremented once for every signer. This is why we expect the metric to be
3019+
// `5`, even though there is only one block proposed.
3020+
let expected_result = format!("stacks_signer_block_proposals_received {}", num_signers);
3021+
assert!(metrics_response.contains(&expected_result));
3022+
let expected_result = format!(
3023+
"stacks_signer_block_responses_sent{{response_type=\"accepted\"}} {}",
3024+
num_signers
3025+
);
3026+
assert!(metrics_response.contains(&expected_result));
3027+
}
3028+
}

0 commit comments

Comments
 (0)