diff --git a/CHANGELOG.md b/CHANGELOG.md index ada3a8dac03..0ce70dd514b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to the versioning scheme outlined in the [README.md](README.md). +## [Unreleased] + +### Added + +- Add `stackerdb_timeout_secs` to miner config for limiting duration of StackerDB HTTP requests. + ## [3.2.0.0.1] ### Added diff --git a/libsigner/src/session.rs b/libsigner/src/session.rs index 14fafe68ee5..a75b5d97d62 100644 --- a/libsigner/src/session.rs +++ b/libsigner/src/session.rs @@ -16,6 +16,7 @@ use std::net::TcpStream; use std::str; +use std::time::Duration; use clarity::vm::types::QualifiedContractIdentifier; use libstackerdb::{ @@ -103,22 +104,34 @@ pub struct StackerDBSession { pub stackerdb_contract_id: QualifiedContractIdentifier, /// connection to the replica sock: Option, + /// The timeout applied to HTTP read and write operations + socket_timeout: Duration, } impl StackerDBSession { /// instantiate but don't connect - pub fn new(host: &str, stackerdb_contract_id: QualifiedContractIdentifier) -> StackerDBSession { + pub fn new( + host: &str, + stackerdb_contract_id: QualifiedContractIdentifier, + socket_timeout: Duration, + ) -> StackerDBSession { StackerDBSession { host: host.to_owned(), stackerdb_contract_id, sock: None, + socket_timeout, } } /// connect or reconnect to the node fn connect_or_reconnect(&mut self) -> Result<(), RPCError> { debug!("connect to {}", &self.host); - self.sock = Some(TcpStream::connect(&self.host)?); + let sock = TcpStream::connect(&self.host)?; + // Make sure we don't hang forever if for some reason our node does not + // respond as expected such as failing to properly close the connection + sock.set_read_timeout(Some(self.socket_timeout))?; + sock.set_write_timeout(Some(self.socket_timeout))?; + self.sock = Some(sock); Ok(()) } @@ -251,11 +264,49 @@ impl SignerSession for StackerDBSession { /// upload a chunk fn put_chunk(&mut self, chunk: &StackerDBChunkData) -> Result { let body = - serde_json::to_vec(chunk).map_err(|e| RPCError::Deserialize(format!("{:?}", &e)))?; + serde_json::to_vec(chunk).map_err(|e| RPCError::Deserialize(format!("{e:?}")))?; let path = stackerdb_post_chunk_path(self.stackerdb_contract_id.clone()); let resp_bytes = self.rpc_request("POST", &path, Some("application/json"), &body)?; let ack: StackerDBChunkAckData = serde_json::from_slice(&resp_bytes) - .map_err(|e| RPCError::Deserialize(format!("{:?}", &e)))?; + .map_err(|e| RPCError::Deserialize(format!("{e:?}")))?; Ok(ack) } } + +#[cfg(test)] +mod tests { + use std::io::Write; + use std::net::TcpListener; + use std::thread; + + use super::*; + + #[test] + fn socket_timeout_works_as_expected() { + let listener = TcpListener::bind("127.0.0.1:0").expect("bind failed"); + let addr = listener.local_addr().unwrap(); + + let short_timeout = Duration::from_millis(200); + thread::spawn(move || { + if let Ok((mut stream, _)) = listener.accept() { + // Sleep long enough so the client should hit its timeout + std::thread::sleep(short_timeout * 2); + let _ = stream.write_all(b"HTTP/1.1 200 OK\r\n\r\n"); + } + }); + + let contract_id = QualifiedContractIdentifier::transient(); + let mut session = StackerDBSession::new(&addr.to_string(), contract_id, short_timeout); + + session.connect_or_reconnect().expect("connect failed"); + + // This should fail due to the timeout + let result = session.rpc_request("GET", "/", None, &[]); + match result { + Err(RPCError::IO(e)) => { + assert_eq!(e.kind(), std::io::ErrorKind::WouldBlock); + } + other => panic!("expected timeout error, got {other:?}"), + } + } +} diff --git a/stacks-node/src/nakamoto_node/miner.rs b/stacks-node/src/nakamoto_node/miner.rs index 5e39187f892..6d90ee7e572 100644 --- a/stacks-node/src/nakamoto_node/miner.rs +++ b/stacks-node/src/nakamoto_node/miner.rs @@ -1106,7 +1106,11 @@ impl BlockMinerThread { NakamotoNodeError::MinerConfigurationFailed("Failed to get RPC loopback socket") })?; let miners_contract_id = boot_code_id(MINERS_NAME, chain_state.mainnet); - let mut miners_session = StackerDBSession::new(&rpc_socket.to_string(), miners_contract_id); + let mut miners_session = StackerDBSession::new( + &rpc_socket.to_string(), + miners_contract_id, + self.config.miner.stackerdb_timeout, + ); if Self::fault_injection_skip_block_push() { warn!( diff --git a/stacks-node/src/nakamoto_node/signer_coordinator.rs b/stacks-node/src/nakamoto_node/signer_coordinator.rs index 0618b355315..c54e6109a5d 100644 --- a/stacks-node/src/nakamoto_node/signer_coordinator.rs +++ b/stacks-node/src/nakamoto_node/signer_coordinator.rs @@ -107,7 +107,11 @@ impl SignerCoordinator { .get_rpc_loopback() .ok_or_else(|| ChainstateError::MinerAborted)?; let miners_contract_id = boot_code_id(MINERS_NAME, is_mainnet); - let miners_session = StackerDBSession::new(&rpc_socket.to_string(), miners_contract_id); + let miners_session = StackerDBSession::new( + &rpc_socket.to_string(), + miners_contract_id, + config.miner.stackerdb_timeout, + ); // build a BTreeMap of the various timeout steps let mut block_rejection_timeout_steps = BTreeMap::::new(); diff --git a/stacks-node/src/nakamoto_node/stackerdb_listener.rs b/stacks-node/src/nakamoto_node/stackerdb_listener.rs index cfe23474db2..1effc9f6ac0 100644 --- a/stacks-node/src/nakamoto_node/stackerdb_listener.rs +++ b/stacks-node/src/nakamoto_node/stackerdb_listener.rs @@ -179,8 +179,11 @@ impl StackerDBListener { .node .get_rpc_loopback() .ok_or_else(|| ChainstateError::MinerAborted)?; - let mut signers_session = - StackerDBSession::new(&rpc_socket.to_string(), signers_contract_id.clone()); + let mut signers_session = StackerDBSession::new( + &rpc_socket.to_string(), + signers_contract_id.clone(), + config.miner.stackerdb_timeout, + ); let entries: Vec<_> = signer_entries.values().cloned().collect(); let parsed_entries = SignerEntries::parse(config.is_mainnet(), &entries) .expect("FATAL: could not parse retrieved signer entries"); diff --git a/stacks-node/src/neon_node.rs b/stacks-node/src/neon_node.rs index 9fddda7d7e9..bff4f293bb7 100644 --- a/stacks-node/src/neon_node.rs +++ b/stacks-node/src/neon_node.rs @@ -2302,8 +2302,11 @@ impl BlockMinerThread { /// Only used in mock signing to determine if the peer info view was already signed across fn mock_block_exists(&self, peer_info: &PeerInfo) -> bool { let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); - let mut miners_stackerdb = - StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); + let mut miners_stackerdb = StackerDBSession::new( + &self.config.node.rpc_bind, + miner_contract_id, + self.config.miner.stackerdb_timeout, + ); let miner_slot_ids: Vec<_> = (0..MINER_SLOT_COUNT * 2).collect(); if let Ok(messages) = miners_stackerdb.get_latest_chunks(&miner_slot_ids) { for message in messages.into_iter().flatten() { @@ -2379,8 +2382,11 @@ impl BlockMinerThread { let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), false) .map_err(|e| e.to_string())?; let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet()); - let mut miners_stackerdb = - StackerDBSession::new(&self.config.node.rpc_bind, miner_contract_id); + let mut miners_stackerdb = StackerDBSession::new( + &self.config.node.rpc_bind, + miner_contract_id, + self.config.miner.stackerdb_timeout, + ); let miner_db = MinerDB::open_with_config(&self.config).map_err(|e| e.to_string())?; SignerCoordinator::send_miners_message( diff --git a/stacks-node/src/tests/nakamoto_integrations.rs b/stacks-node/src/tests/nakamoto_integrations.rs index 92cca4f8cb9..715bb6c5c3c 100644 --- a/stacks-node/src/tests/nakamoto_integrations.rs +++ b/stacks-node/src/tests/nakamoto_integrations.rs @@ -486,7 +486,11 @@ pub fn get_latest_block_proposal( let miner_ranges = stackerdb_conf.signer_ranges(); let latest_miner = usize::from(miner_info.get_latest_winner_index()); let miner_contract_id = boot_code_id(MINERS_NAME, false); - let mut miners_stackerdb = StackerDBSession::new(&conf.node.rpc_bind, miner_contract_id); + let mut miners_stackerdb = StackerDBSession::new( + &conf.node.rpc_bind, + miner_contract_id, + Duration::from_secs(30), + ); let mut proposed_blocks: Vec<_> = stackerdb_conf .signers @@ -12660,14 +12664,16 @@ fn write_signer_update( ) { let signers_contract_id = MessageSlotID::StateMachineUpdate.stacker_db_contract(false, reward_cycle); - let mut session = StackerDBSession::new(&conf.node.rpc_bind, signers_contract_id); + let mut session = StackerDBSession::new( + &conf.node.rpc_bind, + signers_contract_id, + Duration::from_secs(30), + ); let message = SignerMessageV0::StateMachineUpdate(update); - // Submit the block proposal to the signers slot - let mut accepted = false; + // Submit the update to the signers slot let mut version = 0; - let start = Instant::now(); - while !accepted { + wait_for(timeout.as_secs(), || { let mut chunk = StackerDBChunkData::new(signer_slot_id, version, message.serialize_to_vec()); chunk @@ -12675,14 +12681,11 @@ fn write_signer_update( .expect("Failed to sign message chunk"); debug!("Produced a signature: {:?}", chunk.sig); let result = session.put_chunk(&chunk).expect("Failed to put chunk"); - accepted = result.accepted; version += 1; debug!("Test Put Chunk ACK: {result:?}"); - assert!( - start.elapsed() < timeout, - "Timed out waiting for signer state update to be accepted" - ); - } + Ok(result.accepted) + }) + .expect("Failed to accept signer state update"); } /// Test SIP-031 activation diff --git a/stacks-node/src/tests/signer/mod.rs b/stacks-node/src/tests/signer/mod.rs index 35652042ed4..93509f800cb 100644 --- a/stacks-node/src/tests/signer/mod.rs +++ b/stacks-node/src/tests/signer/mod.rs @@ -1462,6 +1462,7 @@ impl SignerTest { self.get_current_reward_cycle(), SignerSlotID(0), // We are just reading so again, don't care about index. SignerDb::new(":memory:").unwrap(), + Duration::from_secs(30), ); let mut latest_msgs = StackerDB::get_messages( stackerdb @@ -1526,6 +1527,7 @@ impl SignerTest { reward_cycle, SignerSlotID(0), // We are just reading so again, don't care about index. SignerDb::new(":memory:").unwrap(), // also don't care about the signer db for version tracking + Duration::from_secs(30), ) } @@ -1570,6 +1572,7 @@ impl SignerTest { .expect("Failed to get signer slot id") .expect("Signer does not have a slot id"), SignerDb::new(":memory:").unwrap(), + Duration::from_secs(30), ); let signature = private_key diff --git a/stacks-node/src/tests/signer/v0.rs b/stacks-node/src/tests/signer/v0.rs index cb5de28ba78..fe3e3c4b009 100644 --- a/stacks-node/src/tests/signer/v0.rs +++ b/stacks-node/src/tests/signer/v0.rs @@ -473,8 +473,11 @@ impl SignerTest { /// Propose a block to the signers fn propose_block(&self, block: NakamotoBlock, timeout: Duration) { let miners_contract_id = boot_code_id(MINERS_NAME, false); - let mut session = - StackerDBSession::new(&self.running_nodes.conf.node.rpc_bind, miners_contract_id); + let mut session = StackerDBSession::new( + &self.running_nodes.conf.node.rpc_bind, + miners_contract_id, + self.running_nodes.conf.miner.stackerdb_timeout, + ); let burn_height = self .running_nodes .btc_regtest_controller @@ -17774,8 +17777,11 @@ fn miner_stackerdb_version_rollover() { max_chunk.slot_version ); - let mut stackerdb = - StackerDBSession::new(&conf_2.node.rpc_bind, boot_code_id(MINERS_NAME, false)); + let mut stackerdb = StackerDBSession::new( + &conf_2.node.rpc_bind, + boot_code_id(MINERS_NAME, false), + conf_2.miner.stackerdb_timeout, + ); let proposals_before = miners.get_primary_proposals_submitted().get(); diff --git a/stacks-signer/CHANGELOG.md b/stacks-signer/CHANGELOG.md index 4a3f341afaa..7e6c54e31fd 100644 --- a/stacks-signer/CHANGELOG.md +++ b/stacks-signer/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to the versioning scheme outlined in the [README.md](README.md). +## [3.2.0.0.1.1] + +### Added + +- Introduced `stackerdb_timeout_secs`: config option to set the maximum time (in seconds) the signer will wait for StackerDB HTTP requests to complete. + ## [3.2.0.0.1.0] ### Changed diff --git a/stacks-signer/src/cli.rs b/stacks-signer/src/cli.rs index a0eed95de58..3306af680f5 100644 --- a/stacks-signer/src/cli.rs +++ b/stacks-signer/src/cli.rs @@ -84,6 +84,9 @@ pub struct StackerDBArgs { /// The stacker-db contract to use. Must be in the format of "STACKS_ADDRESS.CONTRACT_NAME" #[arg(short, long, value_parser = parse_contract)] pub contract: QualifiedContractIdentifier, + #[arg(long, default_value = "60")] + /// The HTTP timeout (in seconds) for read/write operations with StackerDB. + pub stackerdb_timeout_secs: u64, } /// Arguments for the get-chunk command @@ -246,6 +249,9 @@ pub struct MonitorSignersArgs { /// Max age in seconds before a signer message is considered stale. #[arg(long, short, default_value = "1200")] pub max_age: u64, + /// The HTTP timeout (in seconds) for read/write operations with StackerDB. + #[arg(long, short, default_value = "60")] + pub stackerdb_timeout_secs: u64, } #[derive(Clone, Debug, PartialEq)] diff --git a/stacks-signer/src/client/mod.rs b/stacks-signer/src/client/mod.rs index 974297cb9ed..133b075fc09 100644 --- a/stacks-signer/src/client/mod.rs +++ b/stacks-signer/src/client/mod.rs @@ -155,7 +155,9 @@ pub(crate) mod tests { use stacks_common::util::hash::{Hash160, Sha256Sum}; use super::*; - use crate::config::{GlobalConfig, SignerConfig, SignerConfigMode}; + use crate::config::{ + GlobalConfig, SignerConfig, SignerConfigMode, DEFAULT_STACKERDB_TIMEOUT_SECS, + }; #[cfg(any(test, feature = "testing"))] use crate::v0::signer_state::SUPPORTED_SIGNER_PROTOCOL_VERSION; @@ -437,6 +439,7 @@ pub(crate) mod tests { capitulate_miner_view_timeout: config.capitulate_miner_view_timeout, #[cfg(any(test, feature = "testing"))] supported_signer_protocol_version: SUPPORTED_SIGNER_PROTOCOL_VERSION, + stackerdb_timeout: Duration::from_secs(DEFAULT_STACKERDB_TIMEOUT_SECS), } } diff --git a/stacks-signer/src/client/stackerdb.rs b/stacks-signer/src/client/stackerdb.rs index 44905b6bad4..030bf99210c 100644 --- a/stacks-signer/src/client/stackerdb.rs +++ b/stacks-signer/src/client/stackerdb.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + // Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation // Copyright (C) 2020-2024 Stacks Open Internet Foundation // @@ -80,6 +82,7 @@ impl From<&SignerConfig> for StackerDB { config.reward_cycle, signer_db, mode, + config.stackerdb_timeout, ) } } @@ -94,6 +97,7 @@ impl StackerDB { reward_cycle: u64, signer_slot_id: SignerSlotID, signer_db: SignerDb, + socket_timeout: Duration, ) -> Self { Self::new( host, @@ -102,6 +106,7 @@ impl StackerDB { reward_cycle, signer_db, StackerDBMode::Normal { signer_slot_id }, + socket_timeout, ) } @@ -113,11 +118,15 @@ impl StackerDB { reward_cycle: u64, signer_db: SignerDb, signer_mode: StackerDBMode, + socket_timeout: Duration, ) -> Self { let mut signers_message_stackerdb_sessions = HashMap::new(); for msg_id in M::all() { - let session = - StackerDBSession::new(host, msg_id.stacker_db_contract(is_mainnet, reward_cycle)); + let session = StackerDBSession::new( + host, + msg_id.stacker_db_contract(is_mainnet, reward_cycle), + socket_timeout, + ); signers_message_stackerdb_sessions.insert(*msg_id, session); } diff --git a/stacks-signer/src/config.rs b/stacks-signer/src/config.rs index f1eb4cbb95d..d0bf68cc6a9 100644 --- a/stacks-signer/src/config.rs +++ b/stacks-signer/src/config.rs @@ -54,6 +54,8 @@ const DEFAULT_PROPOSAL_WAIT_TIME_FOR_PARENT_SECS: u64 = 15; /// Default time (in secs) to wait between updating our local state /// machine view point and capitulating to other signers tenure view const DEFAULT_CAPITULATE_MINER_VIEW_SECS: u64 = 20; +/// Default HTTP timeout (in seconds) for read/write operations with StackerDB. +pub const DEFAULT_STACKERDB_TIMEOUT_SECS: u64 = 120; #[derive(thiserror::Error, Debug)] /// An error occurred parsing the provided configuration @@ -191,6 +193,8 @@ pub struct SignerConfig { pub validate_with_replay_tx: bool, /// Time to wait between updating our local state machine view point and capitulating to other signers miner view pub capitulate_miner_view_timeout: Duration, + /// The HTTP timeout for read/write operations with StackerDB. + pub stackerdb_timeout: Duration, #[cfg(any(test, feature = "testing"))] /// Only used for testing purposes to enable overriding the signer version pub supported_signer_protocol_version: u64, @@ -249,6 +253,8 @@ pub struct GlobalConfig { pub validate_with_replay_tx: bool, /// Time to wait between updating our local state machine view point and capitulating to other signers miner view pub capitulate_miner_view_timeout: Duration, + /// The HTTP timeout for read/write operations with StackerDB. + pub stackerdb_timeout: Duration, #[cfg(any(test, feature = "testing"))] /// Only used for testing to enable specific signer protocol versions pub supported_signer_protocol_version: u64, @@ -305,6 +311,8 @@ struct RawConfigFile { pub validate_with_replay_tx: Option, /// Time to wait (in secs) between updating our local state machine view point and capitulating to other signers miner view pub capitulate_miner_view_timeout_secs: Option, + /// Time to wait (in secs) before timing out an HTTP request with StackerDB. + pub stackerdb_timeout_secs: Option, #[cfg(any(test, feature = "testing"))] /// Only used for testing to enable specific signer protocol versions pub supported_signer_protocol_version: Option, @@ -439,6 +447,11 @@ impl TryFrom for GlobalConfig { .unwrap_or(DEFAULT_CAPITULATE_MINER_VIEW_SECS), ); + let stackerdb_timeout = Duration::from_secs( + raw_data + .stackerdb_timeout_secs + .unwrap_or(DEFAULT_STACKERDB_TIMEOUT_SECS), + ); #[cfg(any(test, feature = "testing"))] let supported_signer_protocol_version = raw_data .supported_signer_protocol_version @@ -467,6 +480,7 @@ impl TryFrom for GlobalConfig { proposal_wait_for_parent_time, validate_with_replay_tx, capitulate_miner_view_timeout, + stackerdb_timeout, #[cfg(any(test, feature = "testing"))] supported_signer_protocol_version, }) diff --git a/stacks-signer/src/main.rs b/stacks-signer/src/main.rs index 75e1703854b..5d62cc61cce 100644 --- a/stacks-signer/src/main.rs +++ b/stacks-signer/src/main.rs @@ -27,6 +27,7 @@ extern crate serde_json; extern crate toml; use std::io::{self, Write}; +use std::time::Duration; use blockstack_lib::util_lib::signed_structured_data::pox4::make_pox_4_signer_key_signature; use clap::Parser; @@ -66,21 +67,33 @@ fn write_chunk_to_stdout(chunk_opt: Option>) { fn handle_get_chunk(args: GetChunkArgs) { debug!("Getting chunk..."); - let mut session = stackerdb_session(&args.db_args.host, args.db_args.contract); + let mut session = stackerdb_session( + &args.db_args.host, + args.db_args.contract, + Duration::from_secs(args.db_args.stackerdb_timeout_secs), + ); let chunk_opt = session.get_chunk(args.slot_id, args.slot_version).unwrap(); write_chunk_to_stdout(chunk_opt); } fn handle_get_latest_chunk(args: GetLatestChunkArgs) { debug!("Getting latest chunk..."); - let mut session = stackerdb_session(&args.db_args.host, args.db_args.contract); + let mut session = stackerdb_session( + &args.db_args.host, + args.db_args.contract, + Duration::from_secs(args.db_args.stackerdb_timeout_secs), + ); let chunk_opt = session.get_latest_chunk(args.slot_id).unwrap(); write_chunk_to_stdout(chunk_opt); } fn handle_list_chunks(args: StackerDBArgs) { debug!("Listing chunks..."); - let mut session = stackerdb_session(&args.host, args.contract); + let mut session = stackerdb_session( + &args.host, + args.contract, + Duration::from_secs(args.stackerdb_timeout_secs), + ); let chunk_list = session.list_chunks().unwrap(); let chunk_list_json = serde_json::to_string(&chunk_list).unwrap(); let hexed_json = to_hex(chunk_list_json.as_bytes()); @@ -89,7 +102,11 @@ fn handle_list_chunks(args: StackerDBArgs) { fn handle_put_chunk(args: PutChunkArgs) { debug!("Putting chunk..."); - let mut session = stackerdb_session(&args.db_args.host, args.db_args.contract); + let mut session = stackerdb_session( + &args.db_args.host, + args.db_args.contract, + Duration::from_secs(args.db_args.stackerdb_timeout_secs), + ); let mut chunk = StackerDBChunkData::new(args.slot_id, args.slot_version, args.data); chunk.sign(&args.private_key).unwrap(); let chunk_ack = session.put_chunk(&chunk).unwrap(); diff --git a/stacks-signer/src/monitor_signers.rs b/stacks-signer/src/monitor_signers.rs index bbd59f00ea0..782be261317 100644 --- a/stacks-signer/src/monitor_signers.rs +++ b/stacks-signer/src/monitor_signers.rs @@ -14,6 +14,7 @@ // along with this program. If not, see . use std::collections::HashMap; +use std::time::Duration; use clarity::codec::read_next; use clarity::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey}; @@ -233,7 +234,8 @@ impl SignerMonitor { "Monitoring signers stackerdb. Polling interval: {} secs, Max message age: {} secs, Reward cycle: {reward_cycle}, StackerDB contract: {contract}", self.args.interval, self.args.max_age ); - let mut session = stackerdb_session(&self.args.host, contract); + let stackerdb_timeout = Duration::from_secs(self.args.stackerdb_timeout_secs); + let mut session = stackerdb_session(&self.args.host, contract, stackerdb_timeout); info!("Confirming messages for {nmb_signers} registered signers"; "signer_addresses" => self.cycle_state.signers_addresses.values().map(|addr| format!("{addr}")).collect::>().join(", ") ); @@ -255,7 +257,7 @@ impl SignerMonitor { info!( "Reward cycle has changed to {reward_cycle}. Updating stacker db session to StackerDB contract {contract}.", ); - session = stackerdb_session(&self.args.host, contract); + session = stackerdb_session(&self.args.host, contract, stackerdb_timeout); // Clear the last messages and signer last update times. last_messages.clear(); last_updates.clear(); diff --git a/stacks-signer/src/runloop.rs b/stacks-signer/src/runloop.rs index 2ac46c93aca..033a69936ce 100644 --- a/stacks-signer/src/runloop.rs +++ b/stacks-signer/src/runloop.rs @@ -330,6 +330,7 @@ impl, T: StacksMessageCodec + Clone + Send + Debug> RunLo proposal_wait_for_parent_time: self.config.proposal_wait_for_parent_time, validate_with_replay_tx: self.config.validate_with_replay_tx, capitulate_miner_view_timeout: self.config.capitulate_miner_view_timeout, + stackerdb_timeout: self.config.stackerdb_timeout, #[cfg(any(test, feature = "testing"))] supported_signer_protocol_version: self.config.supported_signer_protocol_version, })) diff --git a/stacks-signer/src/utils.rs b/stacks-signer/src/utils.rs index 955177e02d7..8f5e462d32c 100644 --- a/stacks-signer/src/utils.rs +++ b/stacks-signer/src/utils.rs @@ -13,12 +13,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::time::Duration; + use clarity::vm::types::QualifiedContractIdentifier; use libsigner::{SignerSession, StackerDBSession}; /// Create a new stacker db session -pub fn stackerdb_session(host: &str, contract: QualifiedContractIdentifier) -> StackerDBSession { - let mut session = StackerDBSession::new(host, contract.clone()); +pub fn stackerdb_session( + host: &str, + contract: QualifiedContractIdentifier, + stackerdb_timeout: Duration, +) -> StackerDBSession { + let mut session = StackerDBSession::new(host, contract.clone(), stackerdb_timeout); session.connect(host.to_string(), contract).unwrap(); session } diff --git a/stackslib/src/config/mod.rs b/stackslib/src/config/mod.rs index 4f3c03c94f9..2ae69e10dd8 100644 --- a/stackslib/src/config/mod.rs +++ b/stackslib/src/config/mod.rs @@ -126,6 +126,8 @@ const DEFAULT_TENURE_EXTEND_COST_THRESHOLD: u64 = 50; /// Default number of milliseconds that the miner should sleep between mining /// attempts when the mempool is empty. const DEFAULT_EMPTY_MEMPOOL_SLEEP_MS: u64 = 2_500; +/// Default number of seconds that a miner should wait before timing out an HTTP request to StackerDB. +const DEFAULT_STACKERDB_TIMEOUT_SECS: u64 = 120; static HELIUM_DEFAULT_CONNECTION_OPTIONS: LazyLock = LazyLock::new(|| ConnectionOptions { @@ -3182,6 +3184,11 @@ pub struct MinerConfig { /// TODO: remove this option when its no longer a testing feature and it becomes default behaviour /// The miner will attempt to replay transactions that a threshold number of signers are expecting in the next block pub replay_transactions: bool, + /// Defines the socket timeout (in seconds) for stackerdb communcation. + /// --- + /// @default: [`DEFAULT_STACKERDB_TIMEOUT_SECS`] + /// @units: seconds. + pub stackerdb_timeout: Duration, } impl Default for MinerConfig { @@ -3235,6 +3242,7 @@ impl Default for MinerConfig { }, max_execution_time_secs: None, replay_transactions: false, + stackerdb_timeout: Duration::from_secs(DEFAULT_STACKERDB_TIMEOUT_SECS), } } } @@ -4177,6 +4185,7 @@ pub struct MinerConfigFile { pub max_execution_time_secs: Option, /// TODO: remove this config option once its no longer a testing feature pub replay_transactions: Option, + pub stackerdb_timeout_secs: Option, } impl MinerConfigFile { @@ -4353,6 +4362,7 @@ impl MinerConfigFile { max_execution_time_secs: self.max_execution_time_secs, replay_transactions: self.replay_transactions.unwrap_or_default(), + stackerdb_timeout: self.stackerdb_timeout_secs.map(Duration::from_secs).unwrap_or(miner_default_config.stackerdb_timeout), }) } } diff --git a/versions.toml b/versions.toml index 38df84be11f..9a0ae153416 100644 --- a/versions.toml +++ b/versions.toml @@ -1,4 +1,4 @@ # Update these values when a new release is created. # `stacks-common/build.rs` will automatically update `versions.rs` with these values. stacks_node_version = "3.2.0.0.1" -stacks_signer_version = "3.2.0.0.1.0" +stacks_signer_version = "3.2.0.0.1.1"