@@ -321,6 +321,7 @@ impl SignerTest<SpawnedSigner> {
321
321
// Verify that the signers signed the proposed block
322
322
let mut signer_index = 0 ;
323
323
let mut signature_index = 0 ;
324
+ let mut signing_keys = HashSet :: new ( ) ;
324
325
let validated = loop {
325
326
// Since we've already checked `signature.len()`, this means we've
326
327
// validated all the signatures in this loop
@@ -331,6 +332,9 @@ impl SignerTest<SpawnedSigner> {
331
332
error ! ( "Failed to validate the mined nakamoto block: ran out of signers to try to validate signatures" ) ;
332
333
break false ;
333
334
} ;
335
+ if !signing_keys. insert ( signer. signing_key ) {
336
+ panic ! ( "Duplicate signing key detected: {:?}" , signer. signing_key) ;
337
+ }
334
338
let stacks_public_key = Secp256k1PublicKey :: from_slice ( signer. signing_key . as_slice ( ) )
335
339
. expect ( "Failed to convert signing key to StacksPublicKey" ) ;
336
340
let valid = stacks_public_key
@@ -488,11 +492,7 @@ fn block_proposal_rejection() {
488
492
while !found_signer_signature_hash_1 && !found_signer_signature_hash_2 {
489
493
std:: thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
490
494
let chunks = test_observer:: get_stackerdb_chunks ( ) ;
491
- for chunk in chunks
492
- . into_iter ( )
493
- . map ( |chunk| chunk. modified_slots )
494
- . flatten ( )
495
- {
495
+ for chunk in chunks. into_iter ( ) . flat_map ( |chunk| chunk. modified_slots ) {
496
496
let Ok ( message) = SignerMessage :: consensus_deserialize ( & mut chunk. data . as_slice ( ) )
497
497
else {
498
498
continue ;
@@ -796,7 +796,8 @@ fn reloads_signer_set_in() {
796
796
Some ( Duration :: from_secs ( 15 ) ) ,
797
797
|_config| { } ,
798
798
|_| { } ,
799
- & [ ] ,
799
+ None ,
800
+ None ,
800
801
) ;
801
802
802
803
setup_epoch_3_reward_set (
@@ -925,7 +926,8 @@ fn forked_tenure_testing(
925
926
config. broadcast_signed_blocks = false ;
926
927
} ,
927
928
|_| { } ,
928
- & [ ] ,
929
+ None ,
930
+ None ,
929
931
) ;
930
932
let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
931
933
@@ -1429,7 +1431,8 @@ fn multiple_miners() {
1429
1431
false
1430
1432
} )
1431
1433
} ,
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 ,
1433
1436
) ;
1434
1437
let conf = signer_test. running_nodes . conf . clone ( ) ;
1435
1438
let mut conf_node_2 = conf. clone ( ) ;
@@ -1694,7 +1697,8 @@ fn miner_forking() {
1694
1697
false
1695
1698
} )
1696
1699
} ,
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 ,
1698
1702
) ;
1699
1703
let conf = signer_test. running_nodes . conf . clone ( ) ;
1700
1704
let mut conf_node_2 = conf. clone ( ) ;
@@ -2274,7 +2278,8 @@ fn empty_sortition() {
2274
2278
config. block_proposal_timeout = block_proposal_timeout;
2275
2279
} ,
2276
2280
|_| { } ,
2277
- & [ ] ,
2281
+ None ,
2282
+ None ,
2278
2283
) ;
2279
2284
let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
2280
2285
let short_timeout = Duration :: from_secs ( 20 ) ;
@@ -2455,7 +2460,8 @@ fn mock_sign_epoch_25() {
2455
2460
}
2456
2461
}
2457
2462
} ,
2458
- & [ ] ,
2463
+ None ,
2464
+ None ,
2459
2465
) ;
2460
2466
2461
2467
let epochs = signer_test
@@ -2659,7 +2665,8 @@ fn signer_set_rollover() {
2659
2665
}
2660
2666
naka_conf. node . rpc_bind = rpc_bind. clone ( ) ;
2661
2667
} ,
2662
- & [ ] ,
2668
+ None ,
2669
+ None ,
2663
2670
) ;
2664
2671
assert_eq ! (
2665
2672
new_spawned_signers[ 0 ] . config. node_host,
@@ -2873,7 +2880,8 @@ fn min_gap_between_blocks() {
2873
2880
|config| {
2874
2881
config. miner . min_time_between_blocks_ms = time_between_blocks_ms;
2875
2882
} ,
2876
- & [ ] ,
2883
+ None ,
2884
+ None ,
2877
2885
) ;
2878
2886
2879
2887
let http_origin = format ! ( "http://{}" , & signer_test. running_nodes. conf. node. rpc_bind) ;
@@ -2945,3 +2953,108 @@ fn min_gap_between_blocks() {
2945
2953
2946
2954
signer_test. shutdown ( ) ;
2947
2955
}
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
+ let unique_signers = num_signers - 1 ;
2986
+ let duplicate_pubkey = Secp256k1PublicKey :: from_private ( & signer_stacks_private_keys[ 0 ] ) ;
2987
+ let duplicate_pubkey_from_copy =
2988
+ Secp256k1PublicKey :: from_private ( & signer_stacks_private_keys[ 1 ] ) ;
2989
+ assert_eq ! (
2990
+ duplicate_pubkey, duplicate_pubkey_from_copy,
2991
+ "Recovered pubkeys don't match"
2992
+ ) ;
2993
+
2994
+ let mut signer_test: SignerTest < SpawnedSigner > = SignerTest :: new_with_config_modifications (
2995
+ num_signers,
2996
+ vec ! [ ] ,
2997
+ None ,
2998
+ |_| { } ,
2999
+ |_| { } ,
3000
+ None ,
3001
+ Some ( signer_stacks_private_keys) ,
3002
+ ) ;
3003
+
3004
+ signer_test. boot_to_epoch_3 ( ) ;
3005
+ let timeout = Duration :: from_secs ( 30 ) ;
3006
+
3007
+ info ! ( "------------------------- Try mining one block -------------------------" ) ;
3008
+
3009
+ signer_test. mine_and_verify_confirmed_naka_block ( timeout, num_signers) ;
3010
+
3011
+ info ! ( "------------------------- Read all `BlockResponse::Accepted` messages -------------------------" ) ;
3012
+
3013
+ let mut signer_accepted_responses = vec ! [ ] ;
3014
+ let start_polling = Instant :: now ( ) ;
3015
+ while start_polling. elapsed ( ) <= timeout {
3016
+ std:: thread:: sleep ( Duration :: from_secs ( 1 ) ) ;
3017
+ let messages = test_observer:: get_stackerdb_chunks ( )
3018
+ . into_iter ( )
3019
+ . flat_map ( |chunk| chunk. modified_slots )
3020
+ . filter_map ( |chunk| {
3021
+ SignerMessage :: consensus_deserialize ( & mut chunk. data . as_slice ( ) ) . ok ( )
3022
+ } )
3023
+ . filter_map ( |message| match message {
3024
+ SignerMessage :: BlockResponse ( BlockResponse :: Accepted ( m) ) => {
3025
+ info ! ( "Message(accepted): {message:?}" ) ;
3026
+ Some ( m)
3027
+ }
3028
+ _ => {
3029
+ debug ! ( "Message(ignored): {message:?}" ) ;
3030
+ None
3031
+ }
3032
+ } ) ;
3033
+ signer_accepted_responses. extend ( messages) ;
3034
+ }
3035
+
3036
+ info ! ( "------------------------- Assert there are {unique_signers} unique signatures and recovered pubkeys -------------------------" ) ;
3037
+
3038
+ // Pick a message hash
3039
+ let ( selected_sighash, _) = signer_accepted_responses
3040
+ . iter ( )
3041
+ . min_by_key ( |( sighash, _) | * sighash)
3042
+ . copied ( )
3043
+ . expect ( "No `BlockResponse::Accepted` messages recieved" ) ;
3044
+
3045
+ // Filter only resonses for selected block and collect unique pubkeys and signatures
3046
+ let ( pubkeys, signatures) : ( HashSet < _ > , HashSet < _ > ) = signer_accepted_responses
3047
+ . into_iter ( )
3048
+ . filter ( |( hash, _) | * hash == selected_sighash)
3049
+ . map ( |( msg, sig) | {
3050
+ let pubkey = Secp256k1PublicKey :: recover_to_pubkey ( msg. bits ( ) , & sig)
3051
+ . expect ( "Failed to recover pubkey" ) ;
3052
+ ( pubkey, sig)
3053
+ } )
3054
+ . unzip ( ) ;
3055
+
3056
+ assert_eq ! ( pubkeys. len( ) , unique_signers) ;
3057
+ assert_eq ! ( signatures. len( ) , unique_signers) ;
3058
+
3059
+ signer_test. shutdown ( ) ;
3060
+ }
0 commit comments