Skip to content

Commit 8f9c5ed

Browse files
authored
Merge pull request #6001 from obycode/fix/considered_txs
fix: allow `considered_txs` insertion foreign key failures
2 parents 15f497b + 3c6b4a7 commit 8f9c5ed

File tree

5 files changed

+172
-5
lines changed

5 files changed

+172
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8-
## [Unreleased]
8+
## [3.1.0.0.8]
99

1010
### Added
1111

stacks-signer/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8-
## [Unreleased]
8+
## [3.1.0.0.8.0]
99

1010
### Changed
1111

stackslib/src/core/mempool.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2953,7 +2953,18 @@ pub fn try_flush_considered_txs(
29532953
let db_tx = conn.transaction()?;
29542954

29552955
for txid in considered_txs {
2956-
db_tx.execute(sql, params![txid])?;
2956+
match db_tx.execute(sql, params![txid]) {
2957+
Ok(_) => {}
2958+
Err(rusqlite::Error::SqliteFailure(err, _))
2959+
if err.code == rusqlite::ErrorCode::ConstraintViolation =>
2960+
{
2961+
// Ignore constraint violations (e.g., foreign key failure)
2962+
// This can happen if the txid was removed from the mempool DB
2963+
// before we could flush it to the considered_txs table.
2964+
continue;
2965+
}
2966+
Err(e) => return Err(e.into()),
2967+
}
29572968
}
29582969

29592970
db_tx.commit()?;

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

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12183,3 +12183,159 @@ fn v3_transaction_api_endpoint() {
1218312183

1218412184
run_loop_thread.join().unwrap();
1218512185
}
12186+
12187+
#[test]
12188+
#[ignore]
12189+
/// This test verifies that the miner can continue even if an insertion into
12190+
/// the `considered_txs` table fails due to a foreign key failure.
12191+
fn handle_considered_txs_foreign_key_failure() {
12192+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
12193+
return;
12194+
}
12195+
12196+
let (mut naka_conf, _miner_account) = naka_neon_integration_conf(None);
12197+
let prom_bind = "127.0.0.1:6000".to_string();
12198+
naka_conf.node.prometheus_bind = Some(prom_bind);
12199+
naka_conf.miner.nakamoto_attempt_time_ms = 5_000;
12200+
naka_conf.miner.tenure_cost_limit_per_block_percentage = None;
12201+
naka_conf.miner.mempool_walk_strategy = MemPoolWalkStrategy::NextNonceWithHighestFeeRate;
12202+
// setup senders
12203+
let send_amt = 1000;
12204+
let send_fee = 180;
12205+
let bad_sender_sk = Secp256k1PrivateKey::from_seed(&[30]);
12206+
let bad_sender_addr = tests::to_addr(&bad_sender_sk);
12207+
naka_conf.add_initial_balance(
12208+
PrincipalData::from(bad_sender_addr).to_string(),
12209+
send_amt + send_fee,
12210+
);
12211+
let good_sender_sk = Secp256k1PrivateKey::from_seed(&[31]);
12212+
let good_sender_addr = tests::to_addr(&good_sender_sk);
12213+
naka_conf.add_initial_balance(
12214+
PrincipalData::from(good_sender_addr).to_string(),
12215+
(send_amt + send_fee) * 2,
12216+
);
12217+
12218+
let sender_signer_sk = Secp256k1PrivateKey::random();
12219+
let sender_signer_addr = tests::to_addr(&sender_signer_sk);
12220+
let mut signers = TestSigners::new(vec![sender_signer_sk]);
12221+
naka_conf.add_initial_balance(PrincipalData::from(sender_signer_addr).to_string(), 100000);
12222+
let recipient = PrincipalData::from(StacksAddress::burn_address(false));
12223+
let stacker_sk = setup_stacker(&mut naka_conf);
12224+
let http_origin = format!("http://{}", &naka_conf.node.rpc_bind);
12225+
12226+
test_observer::spawn();
12227+
test_observer::register_any(&mut naka_conf);
12228+
12229+
let mut btcd_controller = BitcoinCoreController::new(naka_conf.clone());
12230+
btcd_controller
12231+
.start_bitcoind()
12232+
.expect("Failed starting bitcoind");
12233+
let mut btc_regtest_controller = BitcoinRegtestController::new(naka_conf.clone(), None);
12234+
btc_regtest_controller.bootstrap_chain(201);
12235+
12236+
let mut run_loop = boot_nakamoto::BootRunLoop::new(naka_conf.clone()).unwrap();
12237+
let run_loop_stopper = run_loop.get_termination_switch();
12238+
let Counters {
12239+
blocks_processed,
12240+
naka_submitted_commits: commits_submitted,
12241+
..
12242+
} = run_loop.counters();
12243+
let counters = run_loop.counters();
12244+
12245+
let coord_channel = run_loop.coordinator_channels();
12246+
12247+
let run_loop_thread = thread::spawn(move || run_loop.start(None, 0));
12248+
wait_for_runloop(&blocks_processed);
12249+
boot_to_epoch_3(
12250+
&naka_conf,
12251+
&blocks_processed,
12252+
&[stacker_sk],
12253+
&[sender_signer_sk],
12254+
&mut Some(&mut signers),
12255+
&mut btc_regtest_controller,
12256+
);
12257+
12258+
info!("Bootstrapped to Epoch-3.0 boundary, starting nakamoto miner");
12259+
12260+
info!("Nakamoto miner started...");
12261+
blind_signer(&naka_conf, &signers, &counters);
12262+
12263+
wait_for_first_naka_block_commit(60, &commits_submitted);
12264+
12265+
next_block_and_process_new_stacks_block(&mut btc_regtest_controller, 60, &coord_channel)
12266+
.unwrap();
12267+
12268+
let good_transfer_tx = make_stacks_transfer(
12269+
&good_sender_sk,
12270+
0,
12271+
send_fee,
12272+
naka_conf.burnchain.chain_id,
12273+
&recipient,
12274+
send_amt,
12275+
);
12276+
submit_tx(&http_origin, &good_transfer_tx);
12277+
12278+
wait_for(60, || {
12279+
let nonce = get_account(&http_origin, &good_sender_addr).nonce;
12280+
Ok(nonce == 1)
12281+
})
12282+
.expect("Timed out waiting for first block");
12283+
12284+
let height_before = get_chain_info(&naka_conf).stacks_tip_height;
12285+
12286+
// Initiate the transaction stall, then submit transactions.
12287+
TEST_MINE_STALL.set(true);
12288+
TEST_TX_STALL.set(true);
12289+
12290+
let bad_transfer_tx = make_stacks_transfer(
12291+
&bad_sender_sk,
12292+
0,
12293+
send_fee,
12294+
naka_conf.burnchain.chain_id,
12295+
&recipient,
12296+
send_amt,
12297+
);
12298+
let txid = submit_tx(&http_origin, &bad_transfer_tx);
12299+
info!("Bad transaction submitted: {txid}");
12300+
12301+
TEST_MINE_STALL.set(false);
12302+
12303+
// Sleep long enough to ensure that the miner has started processing the tx
12304+
sleep_ms(5_000);
12305+
12306+
info!("--------------------- Deleting tx from the mempool ---------------------");
12307+
// Delete the bad transaction from the mempool.
12308+
let mempool_db_path = format!(
12309+
"{}/nakamoto-neon/chainstate/mempool.sqlite",
12310+
naka_conf.node.working_dir
12311+
);
12312+
let conn = Connection::open(&mempool_db_path).unwrap();
12313+
conn.execute("DELETE FROM mempool WHERE txid = ?", [txid])
12314+
.unwrap();
12315+
12316+
// Unstall the transaction processing, so that the miner will resume.
12317+
TEST_TX_STALL.set(false);
12318+
12319+
info!("--------------------- Waiting for the block ---------------------");
12320+
12321+
// Now wait for the next block to be mined.
12322+
wait_for(30, || {
12323+
let height = get_chain_info(&naka_conf).stacks_tip_height;
12324+
Ok(height > height_before)
12325+
})
12326+
.expect("Timed out waiting for block");
12327+
12328+
let good_sender_nonce = get_account(&http_origin, &good_sender_addr).nonce;
12329+
let bad_sender_nonce = get_account(&http_origin, &bad_sender_addr).nonce;
12330+
12331+
assert_eq!(good_sender_nonce, 1);
12332+
assert_eq!(bad_sender_nonce, 1);
12333+
12334+
coord_channel
12335+
.lock()
12336+
.expect("Mutex poisoned")
12337+
.stop_chains_coordinator();
12338+
run_loop_stopper.store(false, Ordering::SeqCst);
12339+
12340+
run_loop_thread.join().unwrap();
12341+
}

versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Update these values when a new release is created.
22
# `stacks-common/build.rs` will automatically update `versions.rs` with these values.
3-
stacks_node_version = "3.1.0.0.7"
4-
stacks_signer_version = "3.1.0.0.7.0"
3+
stacks_node_version = "3.1.0.0.8-rc1"
4+
stacks_signer_version = "3.1.0.0.8.0-rc1"

0 commit comments

Comments
 (0)