Skip to content

Commit 9ad9ead

Browse files
committed
test: add regression test for #5858
1 parent 4d299ed commit 9ad9ead

File tree

2 files changed

+132
-6
lines changed

2 files changed

+132
-6
lines changed

testnet/stacks-node/src/event_dispatcher.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ lazy_static! {
8282
}
8383

8484
#[derive(Debug, Clone)]
85-
struct EventObserver {
85+
pub struct EventObserver {
8686
/// Path to the database where pending payloads are stored. If `None`, then
8787
/// the database is not used and events are not recoverable across restarts.
88-
db_path: Option<PathBuf>,
88+
pub db_path: Option<PathBuf>,
8989
/// URL to which events will be sent
90-
endpoint: String,
90+
pub endpoint: String,
9191
/// Timeout for sending events to this observer
92-
timeout: Duration,
92+
pub timeout: Duration,
9393
}
9494

9595
struct ReceiptPayloadInfo<'a> {
@@ -770,7 +770,7 @@ impl EventObserver {
770770
self.send_payload(payload, PATH_MINED_NAKAMOTO_BLOCK);
771771
}
772772

773-
fn send_stackerdb_chunks(&self, payload: &serde_json::Value) {
773+
pub fn send_stackerdb_chunks(&self, payload: &serde_json::Value) {
774774
self.send_payload(payload, PATH_STACKERDB_CHUNKS);
775775
}
776776

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

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ use tracing_subscriber::prelude::*;
7777
use tracing_subscriber::{fmt, EnvFilter};
7878

7979
use super::SignerTest;
80-
use crate::event_dispatcher::{MinedNakamotoBlockEvent, TEST_SKIP_BLOCK_ANNOUNCEMENT};
80+
use crate::event_dispatcher::{
81+
EventObserver, MinedNakamotoBlockEvent, TEST_SKIP_BLOCK_ANNOUNCEMENT,
82+
};
8183
use crate::nakamoto_node::miner::{
8284
TEST_BLOCK_ANNOUNCE_STALL, TEST_BROADCAST_PROPOSAL_STALL, TEST_MINE_STALL,
8385
TEST_P2P_BROADCAST_STALL,
@@ -1427,6 +1429,130 @@ fn mine_2_nakamoto_reward_cycles() {
14271429
signer_test.shutdown();
14281430
}
14291431

1432+
#[test]
1433+
#[ignore]
1434+
/// This test is a regression test for issue #5858 in which the signer runloop
1435+
/// used the signature from the stackerdb to determine the miner public key.
1436+
/// This does not work in cases where events get coalesced. The fix was to use
1437+
/// the signature in the proposal's block header instead.
1438+
///
1439+
/// This test covers the regression by adding a thread that interposes on the
1440+
/// stackerdb events sent to the test signers and mutating the signatures
1441+
/// so that the stackerdb chunks are signed by the wrong signer. After the
1442+
/// fix to #5848, signers are resilient to this behavior because they check
1443+
/// the signature on the block proposal (not the chunk).
1444+
fn regr_use_block_header_pk() {
1445+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
1446+
return;
1447+
}
1448+
1449+
tracing_subscriber::registry()
1450+
.with(fmt::layer())
1451+
.with(EnvFilter::from_default_env())
1452+
.init();
1453+
1454+
info!("------------------------- Test Setup -------------------------");
1455+
let num_signers = 5;
1456+
let signer_listeners: Mutex<Vec<String>> = Mutex::default();
1457+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
1458+
num_signers,
1459+
vec![],
1460+
|_| {},
1461+
|node_config| {
1462+
node_config.events_observers = node_config
1463+
.events_observers
1464+
.clone()
1465+
.into_iter()
1466+
.map(|mut event_observer| {
1467+
if event_observer
1468+
.endpoint
1469+
.ends_with(&test_observer::EVENT_OBSERVER_PORT.to_string())
1470+
{
1471+
event_observer
1472+
} else if event_observer
1473+
.events_keys
1474+
.contains(&EventKeyType::StackerDBChunks)
1475+
{
1476+
event_observer
1477+
.events_keys
1478+
.retain(|key| *key != EventKeyType::StackerDBChunks);
1479+
let mut listeners_lock = signer_listeners.lock().unwrap();
1480+
listeners_lock.push(event_observer.endpoint.clone());
1481+
event_observer
1482+
} else {
1483+
event_observer
1484+
}
1485+
})
1486+
.collect();
1487+
},
1488+
None,
1489+
None,
1490+
);
1491+
1492+
let signer_listeners: Vec<_> = signer_listeners
1493+
.lock()
1494+
.unwrap()
1495+
.drain(..)
1496+
.map(|endpoint| EventObserver {
1497+
endpoint,
1498+
db_path: None,
1499+
timeout: Duration::from_secs(120),
1500+
})
1501+
.collect();
1502+
1503+
let bad_signer = Secp256k1PrivateKey::from_seed(&[0xde, 0xad, 0xbe, 0xef]);
1504+
let bad_signer_pk = Secp256k1PublicKey::from_private(&bad_signer);
1505+
1506+
let broadcast_thread_stopper = Arc::new(AtomicBool::new(true));
1507+
let broadcast_thread_flag = broadcast_thread_stopper.clone();
1508+
let broadcast_thread = thread::Builder::new()
1509+
.name("rebroadcast-thread".into())
1510+
.spawn(move || {
1511+
let mut last_sent = 0;
1512+
while broadcast_thread_flag.load(Ordering::SeqCst) {
1513+
thread::sleep(Duration::from_secs(1));
1514+
let mut signerdb_chunks = test_observer::get_stackerdb_chunks();
1515+
if last_sent >= signerdb_chunks.len() {
1516+
continue;
1517+
}
1518+
let mut to_send = signerdb_chunks.split_off(last_sent);
1519+
last_sent = signerdb_chunks.len();
1520+
for event in to_send.iter_mut() {
1521+
// mutilate the signature
1522+
event.modified_slots.iter_mut().for_each(|chunk_data| {
1523+
let pk = chunk_data.recover_pk().unwrap();
1524+
assert_ne!(pk, bad_signer_pk);
1525+
chunk_data.sign(&bad_signer).unwrap();
1526+
});
1527+
1528+
let payload = serde_json::to_value(event).unwrap();
1529+
for signer_listener in signer_listeners.iter() {
1530+
signer_listener.send_stackerdb_chunks(&payload);
1531+
}
1532+
}
1533+
}
1534+
})
1535+
.unwrap();
1536+
1537+
let timeout = Duration::from_secs(200);
1538+
signer_test.boot_to_epoch_3();
1539+
1540+
let prior_stacks_height = signer_test.get_peer_info().stacks_tip_height;
1541+
1542+
let tenures_to_mine = 2;
1543+
for _i in 0..tenures_to_mine {
1544+
signer_test.mine_nakamoto_block(timeout, false);
1545+
}
1546+
1547+
let current_stacks_height = signer_test.get_peer_info().stacks_tip_height;
1548+
1549+
assert!(current_stacks_height >= prior_stacks_height + tenures_to_mine);
1550+
1551+
broadcast_thread_stopper.store(false, Ordering::SeqCst);
1552+
broadcast_thread.join().unwrap();
1553+
signer_test.shutdown();
1554+
}
1555+
14301556
#[test]
14311557
#[ignore]
14321558
fn forked_tenure_invalid() {

0 commit comments

Comments
 (0)