From fa22f584b3988155bf699f038a641dca1e68a3a4 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein <235161024+aaronb-stacks@users.noreply.github.com> Date: Tue, 19 May 2026 15:18:03 -0500 Subject: [PATCH 1/2] test: fix flake in non_blocking_minority_configured_to_favour_incoming_miner There was a race between the stale Pre-Miner-2-winning-sortition global state and the expected Miner-2-won-sortition-and-timed-out state. This change removes that race by clearing the observer. --- changelog.d/test-flake-tenure-extend.fixed | 1 + .../src/tests/signer/v0/tenure_extend.rs | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 changelog.d/test-flake-tenure-extend.fixed diff --git a/changelog.d/test-flake-tenure-extend.fixed b/changelog.d/test-flake-tenure-extend.fixed new file mode 100644 index 00000000000..ff3a67aaf86 --- /dev/null +++ b/changelog.d/test-flake-tenure-extend.fixed @@ -0,0 +1 @@ +Fixed flake in tests::signer::v0::tenure_extend::non_blocking_minority_configured_to_favour_incoming_miner \ No newline at end of file diff --git a/stacks-node/src/tests/signer/v0/tenure_extend.rs b/stacks-node/src/tests/signer/v0/tenure_extend.rs index aec86665cab..21ba9265afe 100644 --- a/stacks-node/src/tests/signer/v0/tenure_extend.rs +++ b/stacks-node/src/tests/signer/v0/tenure_extend.rs @@ -3553,23 +3553,49 @@ fn non_blocking_minority_configured_to_favour_test(variant: NonBlockingMinorityV // Sleep to let the short-timeout signers mark the incoming miner as invalid std::thread::sleep(tenure_extend_wait_timeout.add(Duration::from_secs(1))); - // Verify the short-timeout signers have updated their state machine. - // In the latest protocol, the minority reports miner 1 as the active miner (extending). - // In v1 / FavourIncoming, signers still report miner 2 as the winning miner. - let (expected_pkh, stacks_tip_height) = - if matches!(variant, NonBlockingMinorityVariant::FavourPrevMiner) { - (miner_pkh_1.clone(), stacks_height_before - 1) - } else { - (miner_pkh_2.clone(), stacks_height_before) - }; + // Drop stackerdb chunks accumulated before the switch-back so the wait below + // can only be satisfied by state machine updates broadcast after the + // short-timeout signers switch back to miner 1. Without this, stale chunks + // from before the switch-back satisfy the wait immediately and we race + // against the new broadcasts propagating to peers' GlobalStateEvaluators. + test_observer::clear(); + + // What we expect the short-timeout signers to broadcast after switching back + // to miner 1, and which signer set must reach 70% agreement for the wait to + // succeed. For FavourIncomingMiner the short-timeout signers are the + // majority, and we need their switched-back broadcasts to flip the *global* + // state to miner 1 BEFORE we unstall miner 2 - otherwise miner 2's + // BlockFound is evaluated against stale global state and gets accepted + // instead of rejected. The long-timeout minority signer never switches back + // within this window, so waiting against the full signer set (5) with the + // 70% threshold (>= 4) is exactly the threshold the GlobalStateEvaluator + // uses to flip the global state. + let all_signers_versions = miners.signer_test.signer_addresses_versions(); + let (expected_pkh, stacks_tip_height, expected_signers) = match variant { + NonBlockingMinorityVariant::FavourPrevMiner => ( + miner_pkh_1.clone(), + stacks_height_before - 1, + short_timeout_signers.as_slice(), + ), + NonBlockingMinorityVariant::FavourPrevMinerV1 => ( + miner_pkh_2.clone(), + stacks_height_before, + short_timeout_signers.as_slice(), + ), + NonBlockingMinorityVariant::FavourIncomingMiner => ( + miner_pkh_1.clone(), + stacks_height_before - 1, + all_signers_versions.as_slice(), + ), + }; wait_for_state_machine_update( 30, &get_chain_info(&conf_1).pox_consensus, burn_height_before + 1, Some((expected_pkh, stacks_tip_height)), - &short_timeout_signers, + expected_signers, ) - .expect("Short-timeout signers failed to update state machine"); + .expect("Signers failed to reach expected state machine update after timeout"); if minority_favours_incoming { // FavourIncomingMiner: miner 2 proposes first and gets rejected, From 4c80ebeed4f008f772da5f51287063be6caa9781 Mon Sep 17 00:00:00 2001 From: Aaron Blankstein <235161024+aaronb-stacks@users.noreply.github.com> Date: Fri, 5 Jun 2026 12:09:27 -0500 Subject: [PATCH 2/2] better flake removal --- .../src/tests/signer/v0/tenure_extend.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/stacks-node/src/tests/signer/v0/tenure_extend.rs b/stacks-node/src/tests/signer/v0/tenure_extend.rs index 21ba9265afe..99be64cb927 100644 --- a/stacks-node/src/tests/signer/v0/tenure_extend.rs +++ b/stacks-node/src/tests/signer/v0/tenure_extend.rs @@ -3550,16 +3550,19 @@ fn non_blocking_minority_configured_to_favour_test(variant: NonBlockingMinorityV "------------------------- Wait for Signers to Mark Incoming Miner as Invalid -------------------------" ); + // Drop stale stackerdb chunks BEFORE sleeping so the wait below can only be + // satisfied by state machine updates broadcast at or after the switch-back. + // The short-timeout signers switch back to miner 1 and broadcast DURING the + // sleep (their proposal timeout is shorter than the sleep by design), so + // clearing after the sleep would erase those broadcasts; with no new burn + // block or proposal to trigger re-broadcasts, the wait could then never + // reach the 70% threshold. Clearing here still drops the stale pre-switch + // chunks that would otherwise satisfy the wait immediately. + test_observer::clear(); + // Sleep to let the short-timeout signers mark the incoming miner as invalid std::thread::sleep(tenure_extend_wait_timeout.add(Duration::from_secs(1))); - // Drop stackerdb chunks accumulated before the switch-back so the wait below - // can only be satisfied by state machine updates broadcast after the - // short-timeout signers switch back to miner 1. Without this, stale chunks - // from before the switch-back satisfy the wait immediately and we race - // against the new broadcasts propagating to peers' GlobalStateEvaluators. - test_observer::clear(); - // What we expect the short-timeout signers to broadcast after switching back // to miner 1, and which signer set must reach 70% agreement for the wait to // succeed. For FavourIncomingMiner the short-timeout signers are the