Skip to content

Commit a567db2

Browse files
committed
tests: improve forking tests
1 parent ac99ec9 commit a567db2

File tree

2 files changed

+133
-78
lines changed

2 files changed

+133
-78
lines changed

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,10 @@ fn multiple_miners() {
16411641

16421642
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
16431643
naka_conf.node.local_peer_seed = vec![1, 1, 1, 1];
1644+
naka_conf.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1]));
1645+
1646+
let node_2_rpc = 51026;
1647+
let node_2_p2p = 51025;
16441648
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
16451649
naka_conf.miner.wait_on_interim_blocks = Duration::from_secs(1);
16461650
let sender_sk = Secp256k1PrivateKey::new();
@@ -1665,7 +1669,11 @@ fn multiple_miners() {
16651669
let stacker_sk = setup_stacker(&mut naka_conf);
16661670

16671671
let mut conf_node_2 = naka_conf.clone();
1668-
set_random_binds(&mut conf_node_2);
1672+
let localhost = "127.0.0.1";
1673+
conf_node_2.node.rpc_bind = format!("{}:{}", localhost, node_2_rpc);
1674+
conf_node_2.node.p2p_bind = format!("{}:{}", localhost, node_2_p2p);
1675+
conf_node_2.node.data_url = format!("http://{}:{}", localhost, node_2_rpc);
1676+
conf_node_2.node.p2p_address = format!("{}:{}", localhost, node_2_p2p);
16691677
conf_node_2.node.seed = vec![2, 2, 2, 2];
16701678
conf_node_2.burnchain.local_mining_public_key = Some(
16711679
Keychain::default(conf_node_2.node.seed.clone())
@@ -1674,6 +1682,8 @@ fn multiple_miners() {
16741682
);
16751683
conf_node_2.node.local_peer_seed = vec![2, 2, 2, 2];
16761684
conf_node_2.node.miner = true;
1685+
conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2]));
1686+
conf_node_2.events_observers.clear();
16771687

16781688
let node_1_sk = Secp256k1PrivateKey::from_seed(&naka_conf.node.local_peer_seed);
16791689
let node_1_pk = StacksPublicKey::from_private(&node_1_sk);
@@ -1813,16 +1823,14 @@ fn multiple_miners() {
18131823
make_stacks_transfer(&sender_sk, sender_nonce, send_fee, &recipient, send_amt);
18141824
submit_tx(&http_origin, &transfer_tx);
18151825

1816-
loop {
1826+
wait_for(20, || {
18171827
let blocks_processed = coord_channel
18181828
.lock()
18191829
.expect("Mutex poisoned")
18201830
.get_stacks_blocks_processed();
1821-
if blocks_processed > blocks_processed_before {
1822-
break;
1823-
}
1824-
thread::sleep(Duration::from_millis(100));
1825-
}
1831+
Ok(blocks_processed > blocks_processed_before)
1832+
})
1833+
.unwrap();
18261834

18271835
let info = get_chain_info_result(&naka_conf).unwrap();
18281836
assert_ne!(info.stacks_tip, last_tip);
@@ -1832,13 +1840,10 @@ fn multiple_miners() {
18321840
last_tip_height = info.stacks_tip_height;
18331841
}
18341842

1835-
let start_time = Instant::now();
1836-
while commits_submitted.load(Ordering::SeqCst) <= commits_before {
1837-
if start_time.elapsed() >= Duration::from_secs(20) {
1838-
panic!("Timed out waiting for block-commit");
1839-
}
1840-
thread::sleep(Duration::from_millis(100));
1841-
}
1843+
wait_for(20, || {
1844+
Ok(commits_submitted.load(Ordering::SeqCst) > commits_before)
1845+
})
1846+
.unwrap();
18421847
}
18431848

18441849
// load the chain tip, and assert that it is a nakamoto block and at least 30 blocks have advanced in epoch 3

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

Lines changed: 114 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +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::collections::HashMap;
16+
use std::collections::{HashMap, HashSet};
1717
use std::ops::Add;
1818
use std::str::FromStr;
1919
use std::sync::atomic::Ordering;
@@ -28,9 +28,7 @@ use libsigner::v0::messages::{
2828
use libsigner::{BlockProposal, SignerSession, StackerDBSession};
2929
use stacks::address::AddressHashMode;
3030
use stacks::chainstate::burn::db::sortdb::SortitionDB;
31-
use stacks::chainstate::nakamoto::{
32-
NakamotoBlock, NakamotoBlockHeader, NakamotoBlockVote, NakamotoChainState,
33-
};
31+
use stacks::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader, NakamotoChainState};
3432
use stacks::chainstate::stacks::address::PoxAddress;
3533
use stacks::chainstate::stacks::boot::MINERS_NAME;
3634
use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo};
@@ -40,9 +38,7 @@ use stacks::libstackerdb::StackerDBChunkData;
4038
use stacks::net::api::postblock_proposal::TEST_VALIDATE_STALL;
4139
use stacks::types::chainstate::{StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey};
4240
use stacks::types::PublicKey;
43-
use stacks::util::get_epoch_time_secs;
44-
use stacks::util::hash::Sha512Trunc256Sum;
45-
use stacks::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp256k1PublicKey};
41+
use stacks::util::secp256k1::{Secp256k1PrivateKey, Secp256k1PublicKey};
4642
use stacks::util_lib::boot::boot_code_id;
4743
use stacks::util_lib::signed_structured_data::pox4::{
4844
make_pox_4_signer_key_signature, Pox4SignatureTopic,
@@ -51,7 +47,6 @@ use stacks_common::bitvec::BitVec;
5147
use stacks_signer::chainstate::{ProposalEvalConfig, SortitionsView};
5248
use stacks_signer::client::{SignerSlotID, StackerDB};
5349
use stacks_signer::runloop::State;
54-
use stacks_signer::signerdb::{BlockInfo, SignerDb};
5550
use stacks_signer::v0::SpawnedSigner;
5651
use tracing_subscriber::prelude::*;
5752
use tracing_subscriber::{fmt, EnvFilter};
@@ -70,7 +65,7 @@ use crate::tests::neon_integrations::{
7065
test_observer,
7166
};
7267
use crate::tests::{self, make_stacks_transfer};
73-
use crate::{nakamoto_node, BurnchainController, Keychain};
68+
use crate::{nakamoto_node, BurnchainController, Config, Keychain};
7469

7570
impl SignerTest<SpawnedSigner> {
7671
/// Run the test until the first epoch 2.5 reward cycle.
@@ -1197,6 +1192,7 @@ fn multiple_miners() {
11971192
config.node.seed = btc_miner_1_seed.clone();
11981193
config.node.local_peer_seed = btc_miner_1_seed.clone();
11991194
config.burnchain.local_mining_public_key = Some(btc_miner_1_pk.to_hex());
1195+
config.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[1]));
12001196

12011197
config.events_observers.retain(|listener| {
12021198
let Ok(addr) = std::net::SocketAddr::from_str(&listener.endpoint) else {
@@ -1225,6 +1221,7 @@ fn multiple_miners() {
12251221
conf_node_2.node.seed = btc_miner_2_seed.clone();
12261222
conf_node_2.burnchain.local_mining_public_key = Some(btc_miner_2_pk.to_hex());
12271223
conf_node_2.node.local_peer_seed = btc_miner_2_seed.clone();
1224+
conf_node_2.miner.mining_key = Some(Secp256k1PrivateKey::from_seed(&[2]));
12281225
conf_node_2.node.miner = true;
12291226
conf_node_2.events_observers.clear();
12301227
conf_node_2.events_observers.extend(node_2_listeners);
@@ -1252,9 +1249,59 @@ fn multiple_miners() {
12521249

12531250
info!("------------------------- Reached Epoch 3.0 -------------------------");
12541251

1255-
let nakamoto_tenures = 20;
1256-
for _i in 0..nakamoto_tenures {
1257-
let _mined_block = signer_test.mine_block_wait_on_processing(Duration::from_secs(30));
1252+
let max_nakamoto_tenures = 20;
1253+
1254+
// due to the random nature of mining sortitions, the way this test is structured
1255+
// is that we keep track of how many tenures each miner produced, and once enough sortitions
1256+
// have been produced such that each miner has produced 3 tenures, we stop and check the
1257+
// results at the end
1258+
1259+
let miner_1_pk = StacksPublicKey::from_private(conf.miner.mining_key.as_ref().unwrap());
1260+
let miner_2_pk = StacksPublicKey::from_private(conf_node_2.miner.mining_key.as_ref().unwrap());
1261+
let mut btc_blocks_mined = 0;
1262+
let mut miner_1_tenures = 0;
1263+
let mut miner_2_tenures = 0;
1264+
while !(miner_1_tenures >= 3 && miner_2_tenures >= 3) {
1265+
if btc_blocks_mined > max_nakamoto_tenures {
1266+
panic!("Produced {btc_blocks_mined} sortitions, but didn't cover the test scenarios, aborting");
1267+
}
1268+
signer_test.mine_block_wait_on_processing(Duration::from_secs(30));
1269+
btc_blocks_mined += 1;
1270+
let blocks = get_nakamoto_headers(&conf);
1271+
// for this test, there should be one block per tenure
1272+
let consensus_hash_set: HashSet<_> = blocks
1273+
.iter()
1274+
.map(|header| header.consensus_hash.clone())
1275+
.collect();
1276+
assert_eq!(
1277+
consensus_hash_set.len(),
1278+
blocks.len(),
1279+
"In this test, there should only be one block per tenure"
1280+
);
1281+
miner_1_tenures = blocks
1282+
.iter()
1283+
.filter(|header| {
1284+
let header = header.anchored_header.as_stacks_nakamoto().unwrap();
1285+
miner_1_pk
1286+
.verify(
1287+
header.miner_signature_hash().as_bytes(),
1288+
&header.miner_signature,
1289+
)
1290+
.unwrap()
1291+
})
1292+
.count();
1293+
miner_2_tenures = blocks
1294+
.iter()
1295+
.filter(|header| {
1296+
let header = header.anchored_header.as_stacks_nakamoto().unwrap();
1297+
miner_2_pk
1298+
.verify(
1299+
header.miner_signature_hash().as_bytes(),
1300+
&header.miner_signature,
1301+
)
1302+
.unwrap()
1303+
})
1304+
.count();
12581305
}
12591306

12601307
info!(
@@ -1268,11 +1315,61 @@ fn multiple_miners() {
12681315
let peer_2_height = get_chain_info(&conf_node_2).stacks_tip_height;
12691316
info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height, "pre_naka_height" => pre_nakamoto_peer_1_height);
12701317
assert_eq!(peer_1_height, peer_2_height);
1271-
assert_eq!(peer_1_height, pre_nakamoto_peer_1_height + nakamoto_tenures);
1318+
assert_eq!(peer_1_height, pre_nakamoto_peer_1_height + btc_blocks_mined);
1319+
assert_eq!(
1320+
btc_blocks_mined,
1321+
u64::try_from(miner_1_tenures + miner_2_tenures).unwrap()
1322+
);
12721323

12731324
signer_test.shutdown();
12741325
}
12751326

1327+
/// Read processed nakamoto block IDs from the test observer, and use `config` to open
1328+
/// a chainstate DB and returns their corresponding StacksHeaderInfos
1329+
fn get_nakamoto_headers(config: &Config) -> Vec<StacksHeaderInfo> {
1330+
let nakamoto_block_ids: Vec<_> = test_observer::get_blocks()
1331+
.into_iter()
1332+
.filter_map(|block_json| {
1333+
if block_json
1334+
.as_object()
1335+
.unwrap()
1336+
.get("miner_signature")
1337+
.is_none()
1338+
{
1339+
return None;
1340+
}
1341+
let block_id = StacksBlockId::from_hex(
1342+
&block_json
1343+
.as_object()
1344+
.unwrap()
1345+
.get("index_block_hash")
1346+
.unwrap()
1347+
.as_str()
1348+
.unwrap()[2..],
1349+
)
1350+
.unwrap();
1351+
Some(block_id)
1352+
})
1353+
.collect();
1354+
1355+
let (chainstate, _) = StacksChainState::open(
1356+
config.is_mainnet(),
1357+
config.burnchain.chain_id,
1358+
&config.get_chainstate_path_str(),
1359+
None,
1360+
)
1361+
.unwrap();
1362+
1363+
nakamoto_block_ids
1364+
.into_iter()
1365+
.map(|block_id| {
1366+
NakamotoChainState::get_block_header(chainstate.db(), &block_id)
1367+
.unwrap()
1368+
.unwrap()
1369+
})
1370+
.collect()
1371+
}
1372+
12761373
#[test]
12771374
#[ignore]
12781375
fn miner_forking() {
@@ -1470,47 +1567,9 @@ fn miner_forking() {
14701567
let (sortition_data, had_tenure) = run_sortition();
14711568
sortitions_seen.push((sortition_data.clone(), had_tenure));
14721569

1473-
let nakamoto_block_ids: Vec<_> = test_observer::get_blocks()
1474-
.into_iter()
1475-
.filter_map(|block_json| {
1476-
if block_json
1477-
.as_object()
1478-
.unwrap()
1479-
.get("miner_signature")
1480-
.is_none()
1481-
{
1482-
return None;
1483-
}
1484-
let block_id = StacksBlockId::from_hex(
1485-
&block_json
1486-
.as_object()
1487-
.unwrap()
1488-
.get("index_block_hash")
1489-
.unwrap()
1490-
.as_str()
1491-
.unwrap()[2..],
1492-
)
1493-
.unwrap();
1494-
Some(block_id)
1495-
})
1496-
.collect();
1497-
1498-
let (chainstate, _) = StacksChainState::open(
1499-
conf.is_mainnet(),
1500-
conf.burnchain.chain_id,
1501-
&conf.get_chainstate_path_str(),
1502-
None,
1503-
)
1504-
.unwrap();
1505-
1506-
let nakamoto_headers: HashMap<_, _> = nakamoto_block_ids
1570+
let nakamoto_headers: HashMap<_, _> = get_nakamoto_headers(&conf)
15071571
.into_iter()
1508-
.map(|block_id| {
1509-
let header_info = NakamotoChainState::get_block_header(chainstate.db(), &block_id)
1510-
.unwrap()
1511-
.unwrap();
1512-
(header_info.consensus_hash.clone(), header_info)
1513-
})
1572+
.map(|header| (header.consensus_hash.clone(), header))
15141573
.collect();
15151574

15161575
if had_tenure {
@@ -1562,20 +1621,11 @@ fn miner_forking() {
15621621
info!("Peer height information"; "peer_1" => peer_1_height, "peer_2" => peer_2_height, "pre_naka_height" => pre_nakamoto_peer_1_height);
15631622
assert_eq!(peer_1_height, peer_2_height);
15641623

1565-
let nakamoto_block_ids: Vec<_> = test_observer::get_blocks()
1566-
.into_iter()
1567-
.filter_map(|block_json| {
1568-
block_json
1569-
.as_object()
1570-
.unwrap()
1571-
.get("miner_signature")
1572-
.map(|x| x.as_str().unwrap().to_string())
1573-
})
1574-
.collect();
1624+
let nakamoto_blocks_count = get_nakamoto_headers(&conf).len();
15751625

15761626
assert_eq!(
15771627
peer_1_height - pre_nakamoto_peer_1_height,
1578-
u64::try_from(nakamoto_block_ids.len()).unwrap(),
1628+
u64::try_from(nakamoto_blocks_count).unwrap(),
15791629
"There should be no forks in this test"
15801630
);
15811631

0 commit comments

Comments
 (0)