Skip to content

feat: add failsafe to transaction replay #6212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
983f9ce
feat: add failsafe to transaction replay
hstove Jun 19, 2025
4fa3499
fix: clippy
hstove Jun 20, 2025
d422eae
feat: wait for +2 blocks after previous fork tip to reset
hstove Jun 21, 2025
772798b
fix: use pending burn block in bitcoin_block_arrival
hstove Jun 25, 2025
bbea1c7
wip: update tx replay tests to work with failsafe
hstove Jun 25, 2025
2ac624a
Merge remote-tracking branch 'core/develop' into feat/tx-replay-failsafe
hstove Jun 25, 2025
991f010
fix: build warnings in test commands
hstove Jun 25, 2025
de8b6e6
fix: tx_replay_disagreement
hstove Jun 25, 2025
8f790dc
fix: btc_on_stx test
hstove Jun 26, 2025
d4d3917
fix: revert logic for setting `expected_burn_height`
hstove Jun 26, 2025
c6ca6b9
fix: dont rely on node burn block to be processed
hstove Jun 27, 2025
795b4a7
Revert "fix: dont rely on node burn block to be processed"
hstove Jun 30, 2025
4cc0758
fix: better descendency check
hstove Jun 30, 2025
d70d554
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Jun 30, 2025
fbce54a
fix: off-by-one in failsafe descendency check
hstove Jul 1, 2025
bdfed91
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Jul 1, 2025
66bfc13
fix: prevent panic in test setup
hstove Jul 1, 2025
78ac7cc
crc: review comments
hstove Jul 2, 2025
2499c16
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Jul 3, 2025
13a71b2
crc: code improvements from feedback
hstove Jul 3, 2025
3130e83
fix: return `bool` instead of `Result<bool>`
hstove Jul 3, 2025
8f4d08e
fix: use `DEFAULT_RESET_REPLAY_SET_AFTER_FORK_BLOCKS` in tests
hstove Jul 7, 2025
89920fe
fix: incorrect block wait logic, test logic ordering
hstove Jul 7, 2025
d63fb5d
fix: rename integration test name
hstove Jul 7, 2025
a9c9efa
feat: changelog for failsafe
hstove Jul 7, 2025
03ba279
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Jul 8, 2025
ddf733d
Merge remote-tracking branch 'core/develop' into feat/tx-replay-failsafe
hstove Jul 24, 2025
7f21a79
fix: check errors after merge
hstove Jul 24, 2025
96cf01d
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Jul 28, 2025
cb149b0
Merge branch 'develop' into feat/tx-replay-failsafe
hstove Aug 4, 2025
f52ab1f
fix: return type of `new_burn_block_fork_descendency_check`
hstove Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions stacks-node/src/burnchains/bitcoin_regtest_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2816,6 +2816,17 @@ impl BitcoinRPCRequest {
BitcoinRPCRequest::send(config, payload)
}

pub fn get_chain_tips(config: &Config) -> RPCResult<serde_json::Value> {
let payload = BitcoinRPCRequest {
method: "getchaintips".to_string(),
params: vec![],
id: "stacks".to_string(),
jsonrpc: "2.0".to_string(),
};

BitcoinRPCRequest::send(config, payload)
}

Comment on lines +2819 to +2829
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this method still used?

pub fn send(config: &Config, payload: BitcoinRPCRequest) -> RPCResult<serde_json::Value> {
let request = BitcoinRPCRequest::build_rpc_request(config, &payload);
let timeout = Duration::from_secs(u64::from(config.burnchain.timeout));
Expand Down
4 changes: 4 additions & 0 deletions stacks-node/src/tests/nakamoto_integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PrivateKey, Secp
use stacks_common::util::{get_epoch_time_secs, sleep_ms};
use stacks_signer::chainstate::v1::SortitionsView;
use stacks_signer::chainstate::ProposalEvalConfig;
use stacks_signer::config::DEFAULT_RESET_REPLAY_SET_AFTER_FORK_BLOCKS;
use stacks_signer::signerdb::{BlockInfo, BlockState, ExtraBlockInfo, SignerDb};
use stacks_signer::v0::SpawnedSigner;

Expand Down Expand Up @@ -6663,6 +6664,7 @@ fn signer_chainstate() {
tenure_idle_timeout: Duration::from_secs(300),
tenure_idle_timeout_buffer: Duration::from_secs(2),
reorg_attempts_activity_timeout: Duration::from_secs(30),
reset_replay_set_after_fork_blocks: DEFAULT_RESET_REPLAY_SET_AFTER_FORK_BLOCKS,
};
let mut sortitions_view =
SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
Expand Down Expand Up @@ -6773,6 +6775,7 @@ fn signer_chainstate() {
tenure_idle_timeout: Duration::from_secs(300),
tenure_idle_timeout_buffer: Duration::from_secs(2),
reorg_attempts_activity_timeout: Duration::from_secs(30),
reset_replay_set_after_fork_blocks: DEFAULT_RESET_REPLAY_SET_AFTER_FORK_BLOCKS,
};
let burn_block_height = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
.unwrap()
Expand Down Expand Up @@ -6845,6 +6848,7 @@ fn signer_chainstate() {
tenure_idle_timeout: Duration::from_secs(300),
tenure_idle_timeout_buffer: Duration::from_secs(2),
reorg_attempts_activity_timeout: Duration::from_secs(30),
reset_replay_set_after_fork_blocks: DEFAULT_RESET_REPLAY_SET_AFTER_FORK_BLOCKS,
};
let mut sortitions_view = SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
sortitions_view
Expand Down
49 changes: 48 additions & 1 deletion stacks-node/src/tests/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use libsigner::v0::messages::{
use libsigner::v0::signer_state::MinerState;
use libsigner::{BlockProposal, SignerEntries, SignerEventTrait};
use serde::{Deserialize, Serialize};
use stacks::burnchains::Txid;
use stacks::chainstate::coordinator::comm::CoordinatorChannels;
use stacks::chainstate::nakamoto::signer_set::NakamotoSigners;
use stacks::chainstate::nakamoto::NakamotoBlock;
Expand Down Expand Up @@ -239,6 +240,9 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
let (mut naka_conf, _miner_account) =
naka_neon_integration_conf(snapshot_name.map(|n| n.as_bytes()));

naka_conf.miner.activated_vrf_key_path =
Some(format!("{}/vrf_key", naka_conf.node.working_dir));

node_config_modifier(&mut naka_conf);

// Add initial balances to the config
Expand Down Expand Up @@ -364,7 +368,11 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
let metadata_path = snapshot_path.join("metadata.json");
if !metadata_path.clone().exists() {
warn!("Snapshot metadata file does not exist, not restoring snapshot");
return SetupSnapshotResult::NoSnapshot;
std::fs::remove_dir_all(snapshot_path.clone()).unwrap();
return SetupSnapshotResult::WithSnapshot(SnapshotSetupInfo {
snapshot_path: snapshot_path.clone(),
snapshot_exists: false,
});
}
let Ok(metadata) = serde_json::from_reader::<_, SnapshotMetadata>(
File::open(metadata_path.clone()).unwrap(),
Expand Down Expand Up @@ -1066,6 +1074,20 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
})
}

pub fn wait_for_replay_set_eq(&self, timeout: u64, expected_txids: Vec<String>) {
self.wait_for_signer_state_check(timeout, |state| {
let Some(replay_set) = state.get_tx_replay_set() else {
return Ok(false);
};
let txids = replay_set
.iter()
.map(|tx| tx.txid().to_hex())
.collect::<Vec<_>>();
Ok(txids == expected_txids)
})
.expect("Timed out waiting for replay set to be equal to expected txids");
}

/// Replace the test's configured signer st
pub fn replace_signers(
&mut self,
Expand Down Expand Up @@ -1584,6 +1606,31 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
.send_message_with_retry::<SignerMessage>(accepted.into())
.expect("Failed to send accept signature");
}

/// Get the txid of the parent block commit transaction for the given miner
pub fn get_parent_block_commit_txid(&self, miner_pk: &StacksPublicKey) -> Option<Txid> {
let Some(confirmed_utxo) = self
.running_nodes
.btc_regtest_controller
.get_all_utxos(&miner_pk)
.into_iter()
.find(|utxo| utxo.confirmations == 0)
else {
return None;
};
let unconfirmed_txid = Txid::from_bitcoin_tx_hash(&confirmed_utxo.txid);
let unconfirmed_tx = self
.running_nodes
.btc_regtest_controller
.get_raw_transaction(&unconfirmed_txid);
let parent_txid = unconfirmed_tx
.input
.get(0)
.expect("First input should exist")
.previous_output
.txid;
Some(Txid::from_bitcoin_tx_hash(&parent_txid))
}
}

fn setup_stx_btc_node<G: FnMut(&mut NeonConfig)>(
Expand Down
Loading
Loading