Skip to content

Commit fda2e86

Browse files
committed
test: multi-miner & multi-signer scenario
* assert that both stacks-nodes have same chain height, and that they produced blocks in each bitcoin block of nakamoto * signers are distributed as event observers across 2 stacks-nodes
1 parent 409fb28 commit fda2e86

File tree

3 files changed

+224
-30
lines changed

3 files changed

+224
-30
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ jobs:
9090
- tests::signer::v0::forked_tenure_okay
9191
- tests::signer::v0::forked_tenure_invalid
9292
- tests::signer::v0::bitcoind_forking_test
93+
- tests::signer::v0::multiple_miners
9394
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
9495
- tests::nakamoto_integrations::check_block_heights
9596
- tests::nakamoto_integrations::clarity_burn_state

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

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ use stacks::chainstate::stacks::{StacksPrivateKey, ThresholdSignature};
4444
use stacks::core::StacksEpoch;
4545
use stacks::net::api::postblock_proposal::BlockValidateResponse;
4646
use stacks::types::chainstate::StacksAddress;
47-
use stacks::util::secp256k1::MessageSignature;
47+
use stacks::util::secp256k1::{MessageSignature, Secp256k1PublicKey};
4848
use stacks_common::codec::StacksMessageCodec;
4949
use stacks_common::consts::SIGNER_SLOTS_PER_USER;
5050
use stacks_common::types::StacksEpochId;
@@ -105,14 +105,26 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
105105
initial_balances: Vec<(StacksAddress, u64)>,
106106
wait_on_signers: Option<Duration>,
107107
) -> Self {
108-
Self::new_with_config_modifications(num_signers, initial_balances, wait_on_signers, |_| {})
108+
Self::new_with_config_modifications(
109+
num_signers,
110+
initial_balances,
111+
wait_on_signers,
112+
|_| {},
113+
|_| {},
114+
&[],
115+
)
109116
}
110117

111-
fn new_with_config_modifications<F: Fn(&mut SignerConfig) -> ()>(
118+
fn new_with_config_modifications<
119+
F: FnMut(&mut SignerConfig) -> (),
120+
G: FnMut(&mut NeonConfig) -> (),
121+
>(
112122
num_signers: usize,
113123
initial_balances: Vec<(StacksAddress, u64)>,
114124
wait_on_signers: Option<Duration>,
115-
modifier: F,
125+
mut signer_config_modifier: F,
126+
node_config_modifier: G,
127+
btc_miner_pubkeys: &[Secp256k1PublicKey],
116128
) -> Self {
117129
// Generate Signer Data
118130
let signer_stacks_private_keys = (0..num_signers)
@@ -136,11 +148,10 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
136148
} else {
137149
naka_conf.miner.wait_on_signers = Duration::from_secs(10);
138150
}
139-
140151
let run_stamp = rand::random();
141152

142153
// Setup the signer and coordinator configurations
143-
let signer_configs = build_signer_config_tomls(
154+
let signer_configs: Vec<_> = build_signer_config_tomls(
144155
&signer_stacks_private_keys,
145156
&naka_conf.node.rpc_bind,
146157
Some(Duration::from_millis(128)), // Timeout defaults to 5 seconds. Let's override it to 128 milliseconds.
@@ -151,23 +162,45 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
151162
Some(100_000),
152163
None,
153164
Some(9000),
154-
);
155-
156-
let spawned_signers: Vec<_> = (0..num_signers)
157-
.into_iter()
158-
.map(|i| {
159-
info!("spawning signer");
160-
let mut signer_config =
161-
SignerConfig::load_from_str(&signer_configs[i as usize]).unwrap();
162-
modifier(&mut signer_config);
163-
SpawnedSigner::new(signer_config)
164-
})
165+
)
166+
.into_iter()
167+
.map(|toml| {
168+
let mut signer_config = SignerConfig::load_from_str(&toml).unwrap();
169+
signer_config_modifier(&mut signer_config);
170+
signer_config
171+
})
172+
.collect();
173+
assert_eq!(signer_configs.len(), num_signers);
174+
175+
let spawned_signers = signer_configs
176+
.iter()
177+
.cloned()
178+
.map(SpawnedSigner::new)
165179
.collect();
166180

167181
// Setup the nodes and deploy the contract to it
168-
let node = setup_stx_btc_node(naka_conf, &signer_stacks_private_keys, &signer_configs);
169-
let config = SignerConfig::load_from_str(&signer_configs[0]).unwrap();
170-
let stacks_client = StacksClient::from(&config);
182+
let btc_miner_pubkeys = if btc_miner_pubkeys.is_empty() {
183+
let pk = Secp256k1PublicKey::from_hex(
184+
naka_conf
185+
.burnchain
186+
.local_mining_public_key
187+
.as_ref()
188+
.unwrap(),
189+
)
190+
.unwrap();
191+
&[pk]
192+
} else {
193+
btc_miner_pubkeys
194+
};
195+
let node = setup_stx_btc_node(
196+
naka_conf,
197+
&signer_stacks_private_keys,
198+
&signer_configs,
199+
btc_miner_pubkeys,
200+
node_config_modifier,
201+
);
202+
let config = signer_configs.first().unwrap();
203+
let stacks_client = StacksClient::from(config);
171204

172205
Self {
173206
running_nodes: node,
@@ -294,6 +327,33 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
294327
test_observer::get_mined_nakamoto_blocks().pop().unwrap()
295328
}
296329

330+
fn mine_block_wait_on_processing(&mut self, timeout: Duration) {
331+
let commits_submitted = self.running_nodes.commits_submitted.clone();
332+
let blocks_len = test_observer::get_blocks().len();
333+
let mined_block_time = Instant::now();
334+
next_block_and_mine_commit(
335+
&mut self.running_nodes.btc_regtest_controller,
336+
timeout.as_secs(),
337+
&self.running_nodes.coord_channel,
338+
&commits_submitted,
339+
)
340+
.unwrap();
341+
342+
let t_start = Instant::now();
343+
while test_observer::get_blocks().len() <= blocks_len {
344+
assert!(
345+
t_start.elapsed() < timeout,
346+
"Timed out while waiting for nakamoto block to be processed"
347+
);
348+
thread::sleep(Duration::from_secs(1));
349+
}
350+
let mined_block_elapsed_time = mined_block_time.elapsed();
351+
info!(
352+
"Nakamoto block mine time elapsed: {:?}",
353+
mined_block_elapsed_time
354+
);
355+
}
356+
297357
fn wait_for_confirmed_block_v1(
298358
&mut self,
299359
block_signer_sighash: &Sha512Trunc256Sum,
@@ -537,17 +597,17 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
537597
}
538598
}
539599

540-
fn setup_stx_btc_node(
600+
fn setup_stx_btc_node<G: FnMut(&mut NeonConfig) -> ()>(
541601
mut naka_conf: NeonConfig,
542602
signer_stacks_private_keys: &[StacksPrivateKey],
543-
signer_config_tomls: &[String],
603+
signer_configs: &[SignerConfig],
604+
btc_miner_pubkeys: &[Secp256k1PublicKey],
605+
mut node_config_modifier: G,
544606
) -> RunningNodes {
545607
// Spawn the endpoints for observing signers
546-
for toml in signer_config_tomls {
547-
let signer_config = SignerConfig::load_from_str(toml).unwrap();
548-
608+
for signer_config in signer_configs {
549609
naka_conf.events_observers.insert(EventObserverConfig {
550-
endpoint: format!("{}", signer_config.endpoint),
610+
endpoint: signer_config.endpoint.to_string(),
551611
events_keys: vec![
552612
EventKeyType::StackerDBChunks,
553613
EventKeyType::BlockProposal,
@@ -593,6 +653,8 @@ fn setup_stx_btc_node(
593653
}
594654
}
595655
}
656+
node_config_modifier(&mut naka_conf);
657+
596658
info!("Make new BitcoinCoreController");
597659
let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone());
598660
btcd_controller
@@ -604,7 +666,7 @@ fn setup_stx_btc_node(
604666
let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None);
605667

606668
info!("Bootstraping...");
607-
btc_regtest_controller.bootstrap_chain(201);
669+
btc_regtest_controller.bootstrap_chain_to_pks(201, btc_miner_pubkeys);
608670

609671
info!("Chain bootstrapped...");
610672

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

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1515

16+
use std::str::FromStr;
1617
use std::sync::atomic::Ordering;
1718
use std::time::{Duration, Instant};
1819
use std::{env, thread};
@@ -28,7 +29,7 @@ use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo};
2829
use stacks::codec::StacksMessageCodec;
2930
use stacks::libstackerdb::StackerDBChunkData;
3031
use stacks::net::api::postblock_proposal::TEST_VALIDATE_STALL;
31-
use stacks::types::chainstate::{StacksAddress, StacksPrivateKey};
32+
use stacks::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey};
3233
use stacks::types::PublicKey;
3334
use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
3435
use stacks::util_lib::boot::boot_code_id;
@@ -44,6 +45,7 @@ use super::SignerTest;
4445
use crate::event_dispatcher::MinedNakamotoBlockEvent;
4546
use crate::nakamoto_node::miner::TEST_BROADCAST_STALL;
4647
use crate::nakamoto_node::relayer::TEST_SKIP_COMMIT_OP;
48+
use crate::run_loop::boot_nakamoto;
4749
use crate::tests::nakamoto_integrations::{boot_to_epoch_3_reward_set, next_block_and};
4850
use crate::tests::neon_integrations::{
4951
get_account, get_chain_info, next_block_and_wait, submit_tx, test_observer,
@@ -611,6 +613,8 @@ fn forked_tenure_testing(
611613
// make the duration long enough that the reorg attempt will definitely be accepted
612614
config.first_proposal_burn_block_timing = proposal_limit;
613615
},
616+
|_| {},
617+
&[],
614618
);
615619
let http_origin = format!("http://{}", &signer_test.running_nodes.conf.node.rpc_bind);
616620

@@ -799,11 +803,10 @@ fn bitcoind_forking_test() {
799803
let sender_addr = tests::to_addr(&sender_sk);
800804
let send_amt = 100;
801805
let send_fee = 180;
802-
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
806+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new(
803807
num_signers,
804808
vec![(sender_addr.clone(), send_amt + send_fee)],
805809
Some(Duration::from_secs(15)),
806-
|_config| {},
807810
);
808811
let conf = signer_test.running_nodes.conf.clone();
809812
let http_origin = format!("http://{}", &conf.node.rpc_bind);
@@ -936,6 +939,134 @@ fn bitcoind_forking_test() {
936939
signer_test.shutdown();
937940
}
938941

942+
#[test]
943+
#[ignore]
944+
fn multiple_miners() {
945+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
946+
return;
947+
}
948+
949+
let num_signers = 5;
950+
let sender_sk = Secp256k1PrivateKey::new();
951+
let sender_addr = tests::to_addr(&sender_sk);
952+
let send_amt = 100;
953+
let send_fee = 180;
954+
955+
let btc_miner_1_seed = vec![1, 1, 1, 1];
956+
let btc_miner_2_seed = vec![2, 2, 2, 2];
957+
let btc_miner_1_pk = Keychain::default(btc_miner_1_seed.clone()).get_pub_key();
958+
let btc_miner_2_pk = Keychain::default(btc_miner_2_seed.clone()).get_pub_key();
959+
960+
let node_1_rpc = 51024;
961+
let node_1_p2p = 51023;
962+
let node_2_rpc = 51026;
963+
let node_2_p2p = 51025;
964+
965+
let node_1_rpc_bind = format!("127.0.0.1:{}", node_1_rpc);
966+
let node_2_rpc_bind = format!("127.0.0.1:{}", node_2_rpc);
967+
let mut node_2_listeners = Vec::new();
968+
969+
// partition the signer set so that ~half are listening and using node 1 for RPC and events,
970+
// and the rest are using node 2
971+
972+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
973+
num_signers,
974+
vec![(sender_addr.clone(), send_amt + send_fee)],
975+
Some(Duration::from_secs(15)),
976+
|signer_config| {
977+
let node_host = if signer_config.endpoint.port() % 2 == 0 {
978+
&node_1_rpc_bind
979+
} else {
980+
&node_2_rpc_bind
981+
};
982+
signer_config.node_host = node_host.to_string();
983+
},
984+
|config| {
985+
let localhost = "127.0.0.1";
986+
config.node.rpc_bind = format!("{}:{}", localhost, node_1_rpc);
987+
config.node.p2p_bind = format!("{}:{}", localhost, node_1_p2p);
988+
config.node.data_url = format!("http://{}:{}", localhost, node_1_rpc);
989+
config.node.p2p_address = format!("{}:{}", localhost, node_1_p2p);
990+
991+
config.node.seed = btc_miner_1_seed.clone();
992+
config.node.local_peer_seed = btc_miner_1_seed.clone();
993+
config.burnchain.local_mining_public_key = Some(btc_miner_1_pk.to_hex());
994+
995+
config.events_observers.retain(|listener| {
996+
let Ok(addr) = std::net::SocketAddr::from_str(&listener.endpoint) else {
997+
warn!(
998+
"Cannot parse {} to a socket, assuming it isn't a signer-listener binding",
999+
listener.endpoint
1000+
);
1001+
return true;
1002+
};
1003+
if addr.port() % 2 == 0 || addr.port() == test_observer::EVENT_OBSERVER_PORT {
1004+
return true;
1005+
}
1006+
node_2_listeners.push(listener.clone());
1007+
false
1008+
})
1009+
},
1010+
&[btc_miner_1_pk.clone(), btc_miner_2_pk.clone()],
1011+
);
1012+
let conf = signer_test.running_nodes.conf.clone();
1013+
let mut conf_node_2 = conf.clone();
1014+
let localhost = "127.0.0.1";
1015+
conf_node_2.node.rpc_bind = format!("{}:{}", localhost, node_2_rpc);
1016+
conf_node_2.node.p2p_bind = format!("{}:{}", localhost, node_2_p2p);
1017+
conf_node_2.node.data_url = format!("http://{}:{}", localhost, node_2_rpc);
1018+
conf_node_2.node.p2p_address = format!("{}:{}", localhost, node_2_p2p);
1019+
conf_node_2.node.seed = btc_miner_2_seed.clone();
1020+
conf_node_2.burnchain.local_mining_public_key = Some(btc_miner_2_pk.to_hex());
1021+
conf_node_2.node.local_peer_seed = btc_miner_2_seed.clone();
1022+
conf_node_2.node.miner = true;
1023+
conf_node_2.events_observers.clear();
1024+
conf_node_2.events_observers.extend(node_2_listeners);
1025+
assert!(!conf_node_2.events_observers.is_empty());
1026+
1027+
let node_1_sk = Secp256k1PrivateKey::from_seed(&conf.node.local_peer_seed);
1028+
let node_1_pk = StacksPublicKey::from_private(&node_1_sk);
1029+
1030+
conf_node_2.node.working_dir = format!("{}-{}", conf_node_2.node.working_dir, "1");
1031+
1032+
conf_node_2.node.set_bootstrap_nodes(
1033+
format!("{}@{}", &node_1_pk.to_hex(), conf.node.p2p_bind),
1034+
conf.burnchain.chain_id,
1035+
conf.burnchain.peer_version,
1036+
);
1037+
1038+
let mut run_loop_2 = boot_nakamoto::BootRunLoop::new(conf_node_2.clone()).unwrap();
1039+
let _run_loop_2_thread = thread::Builder::new()
1040+
.name("run_loop_2".into())
1041+
.spawn(move || run_loop_2.start(None, 0))
1042+
.unwrap();
1043+
1044+
signer_test.boot_to_epoch_3();
1045+
let pre_nakamoto_peer_1_height = get_chain_info(&conf).stacks_tip_height;
1046+
1047+
info!("------------------------- Reached Epoch 3.0 -------------------------");
1048+
1049+
let nakamoto_tenures = 20;
1050+
for _i in 0..nakamoto_tenures {
1051+
let _mined_block = signer_test.mine_block_wait_on_processing(Duration::from_secs(30));
1052+
}
1053+
1054+
info!(
1055+
"New chain info: {:?}",
1056+
get_chain_info(&signer_test.running_nodes.conf)
1057+
);
1058+
1059+
info!("New chain info: {:?}", get_chain_info(&conf_node_2));
1060+
1061+
let peer_1_height = get_chain_info(&conf).stacks_tip_height;
1062+
let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height;
1063+
info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height, "pre_naka_height" => pre_nakamoto_peer_1_height);
1064+
assert_eq!(peer_1_height, peer_2_height);
1065+
assert_eq!(peer_1_height, pre_nakamoto_peer_1_height + nakamoto_tenures);
1066+
1067+
signer_test.shutdown();
1068+
}
1069+
9391070
#[test]
9401071
#[ignore]
9411072
/// This test checks the behavior at the end of a tenure. Specifically:

0 commit comments

Comments
 (0)