Skip to content

Commit bc5e170

Browse files
committed
Merge branch 'develop' into feat/empty_mempool_sleep
2 parents 4fb916d + 8f9c5ed commit bc5e170

File tree

9 files changed

+442
-6
lines changed

9 files changed

+442
-6
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

stacks-signer/src/monitoring/mod.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,27 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616

17+
use stacks_common::define_named_enum;
18+
1719
#[cfg(feature = "monitoring_prom")]
1820
mod prometheus;
1921

2022
#[cfg(feature = "monitoring_prom")]
2123
mod server;
2224

25+
define_named_enum!(
26+
/// Represent different state change reason on signer agreement protocol
27+
SignerAgreementStateChangeReason {
28+
/// A new burn block has arrived
29+
BurnBlockArrival("burn_block_arrival"),
30+
/// A new stacks block has arrived
31+
StacksBlockArrival("stacks_block_arrival"),
32+
/// A miner is inactive when it should be starting its tenure
33+
InactiveMiner("inactive_miner"),
34+
/// Signer agreement protocol version has been upgraded
35+
ProtocolUpgrade("protocol_upgrade"),
36+
});
37+
2338
/// Actions for updating metrics
2439
#[cfg(feature = "monitoring_prom")]
2540
pub mod actions {
@@ -29,6 +44,7 @@ pub mod actions {
2944

3045
use crate::config::GlobalConfig;
3146
use crate::monitoring::prometheus::*;
47+
use crate::monitoring::SignerAgreementStateChangeReason;
3248
use crate::v0::signer_state::LocalStateMachine;
3349

3450
/// Update stacks tip height gauge
@@ -108,6 +124,16 @@ pub mod actions {
108124
.replace(state);
109125
}
110126

127+
/// Increment signer agreement state change reason counter
128+
pub fn increment_signer_agreement_state_change_reason(
129+
reason: SignerAgreementStateChangeReason,
130+
) {
131+
let label_value = reason.get_name();
132+
SIGNER_AGREEMENT_STATE_CHANGE_REASONS
133+
.with_label_values(&[&label_value])
134+
.inc();
135+
}
136+
111137
/// Start serving monitoring metrics.
112138
/// This will only serve the metrics if the `monitoring_prom` feature is enabled.
113139
pub fn start_serving_monitoring_metrics(config: GlobalConfig) -> Result<(), String> {
@@ -131,6 +157,7 @@ pub mod actions {
131157
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
132158
use stacks_common::info;
133159

160+
use crate::monitoring::SignerAgreementStateChangeReason;
134161
use crate::v0::signer_state::LocalStateMachine;
135162
use crate::GlobalConfig;
136163

@@ -179,6 +206,12 @@ pub mod actions {
179206
/// Record the current local state machine
180207
pub fn record_local_state(_state: LocalStateMachine) {}
181208

209+
/// Increment signer agreement state change reason counter
210+
pub fn increment_signer_agreement_state_change_reason(
211+
_reason: SignerAgreementStateChangeReason,
212+
) {
213+
}
214+
182215
/// Start serving monitoring metrics.
183216
/// This will only serve the metrics if the `monitoring_prom` feature is enabled.
184217
pub fn start_serving_monitoring_metrics(config: GlobalConfig) -> Result<(), String> {

stacks-signer/src/monitoring/prometheus.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ lazy_static! {
7979
vec![0.005, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 20.0, 30.0, 60.0, 120.0]
8080
), &[]).unwrap();
8181

82+
pub static ref SIGNER_AGREEMENT_STATE_CHANGE_REASONS: IntCounterVec = register_int_counter_vec!(
83+
"stacks_signer_agreement_state_change_reasons",
84+
"The number of state machine changes in signer agreement protocol. `reason` can be one of: 'burn_block_arrival', 'stacks_block_arrival', 'inactive_miner', 'protocol_upgrade'",
85+
&["reason"]
86+
).unwrap();
87+
8288
pub static ref SIGNER_LOCAL_STATE_MACHINE: Mutex<Option<LocalStateMachine>> = Mutex::new(None);
8389
}
8490

stacks-signer/src/v0/signer_state.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ impl LocalStateMachine {
271271
"inactive_tenure_ch" => %inactive_tenure_ch,
272272
"new_active_tenure_ch" => %new_active_tenure_ch
273273
);
274+
275+
crate::monitoring::actions::increment_signer_agreement_state_change_reason(
276+
crate::monitoring::SignerAgreementStateChangeReason::InactiveMiner,
277+
);
278+
274279
Ok(())
275280
} else {
276281
warn!("Current miner timed out due to inactivity, but prior miner is not valid. Allowing current miner to continue");
@@ -393,6 +398,11 @@ impl LocalStateMachine {
393398
*parent_tenure_last_block = *block_id;
394399
*parent_tenure_last_block_height = height;
395400
*self = LocalStateMachine::Initialized(prior_state_machine);
401+
402+
crate::monitoring::actions::increment_signer_agreement_state_change_reason(
403+
crate::monitoring::SignerAgreementStateChangeReason::StacksBlockArrival,
404+
);
405+
396406
Ok(())
397407
}
398408

@@ -447,7 +457,7 @@ impl LocalStateMachine {
447457
// set self to uninitialized so that if this function errors,
448458
// self is left as uninitialized.
449459
let prior_state = std::mem::replace(self, Self::Uninitialized);
450-
let prior_state_machine = match prior_state {
460+
let prior_state_machine = match prior_state.clone() {
451461
// if the local state machine was uninitialized, just initialize it
452462
LocalStateMachine::Uninitialized => Self::place_holder(),
453463
LocalStateMachine::Initialized(signer_state_machine) => signer_state_machine,
@@ -526,6 +536,12 @@ impl LocalStateMachine {
526536
active_signer_protocol_version: prior_state_machine.active_signer_protocol_version,
527537
});
528538

539+
if prior_state != *self {
540+
crate::monitoring::actions::increment_signer_agreement_state_change_reason(
541+
crate::monitoring::SignerAgreementStateChangeReason::BurnBlockArrival,
542+
);
543+
}
544+
529545
Ok(())
530546
}
531547
}

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
@@ -12188,6 +12188,162 @@ fn v3_transaction_api_endpoint() {
1218812188
run_loop_thread.join().unwrap();
1218912189
}
1219012190

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

0 commit comments

Comments
 (0)