Skip to content
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

feat: store signer state in sqlite #4348

Merged
merged 12 commits into from Mar 7, 2024
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 10 additions & 16 deletions libsigner/src/runloop.rs
Expand Up @@ -93,12 +93,7 @@ pub trait SignerRunLoop<R: Send, CMD: Send> {
}

/// The top-level signer implementation
pub struct Signer<
CMD: Send,
R: Send,
SL: SignerRunLoop<R, CMD> + Send + Sync,
EV: EventReceiver + Send,
> {
pub struct Signer<CMD, R, SL, EV> {
/// the runloop itself
signer_loop: Option<SL>,
/// the event receiver to use
Expand All @@ -107,8 +102,6 @@ pub struct Signer<
command_receiver: Option<Receiver<CMD>>,
/// the result sender to use
result_sender: Option<Sender<R>>,
/// marker to permit the R type
_phantom: PhantomData<R>,
}

/// The running signer implementation
Expand Down Expand Up @@ -196,13 +189,7 @@ pub fn set_runloop_signal_handler<ST: EventStopSignaler + Send + 'static>(mut st
}).expect("FATAL: failed to set signal handler");
}

impl<
CMD: Send + 'static,
R: Send + 'static,
SL: SignerRunLoop<R, CMD> + Send + Sync + 'static,
EV: EventReceiver + Send + 'static,
> Signer<CMD, R, SL, EV>
{
impl<CMD, R, SL, EV> Signer<CMD, R, SL, EV> {
/// Create a new signer with the given runloop and event receiver.
pub fn new(
runloop: SL,
Expand All @@ -215,10 +202,17 @@ impl<
event_receiver: Some(event_receiver),
command_receiver: Some(command_receiver),
result_sender: Some(result_sender),
_phantom: PhantomData,
}
}
}

impl<
CMD: Send + 'static,
R: Send + 'static,
SL: SignerRunLoop<R, CMD> + Send + 'static,
EV: EventReceiver + Send + 'static,
> Signer<CMD, R, SL, EV>
{
/// This is a helper function to spawn both the runloop and event receiver in their own
/// threads. Advanced signers may not need this method, and instead opt to run the receiver
/// and runloop directly. However, this method is present to help signer developers to get
Expand Down
2 changes: 2 additions & 0 deletions libstackerdb/src/libstackerdb.rs
Expand Up @@ -104,6 +104,8 @@ pub struct StackerDBChunkAckData {
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<SlotMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<u32>,
}

impl SlotMetadata {
Expand Down
4 changes: 4 additions & 0 deletions stacks-signer/Cargo.toml
Expand Up @@ -47,6 +47,10 @@ url = "2.1.0"
[dev-dependencies]
clarity = { path = "../clarity", features = ["testing"] }

[dependencies.rusqlite]
version = "=0.24.2"
features = ["blob", "serde_json", "i128_blob", "bundled", "trace"]

[dependencies.serde_json]
version = "1.0"
features = ["arbitrary_precision", "unbounded_depth"]
Expand Down
1 change: 1 addition & 0 deletions stacks-signer/src/client/mod.rs
Expand Up @@ -533,6 +533,7 @@ pub(crate) mod tests {
nonce_timeout: config.nonce_timeout,
sign_timeout: config.sign_timeout,
tx_fee_ustx: config.tx_fee_ustx,
db_path: config.db_path.clone(),
}
}
}
33 changes: 20 additions & 13 deletions stacks-signer/src/client/stackerdb.rs
Expand Up @@ -16,6 +16,7 @@
//
use blockstack_lib::chainstate::nakamoto::signer_set::NakamotoSigners;
use blockstack_lib::chainstate::stacks::StacksTransaction;
use blockstack_lib::net::api::poststackerdbchunk::StackerDBErrorCodes;
use blockstack_lib::util_lib::boot::boot_code_addr;
use clarity::vm::types::QualifiedContractIdentifier;
use clarity::vm::ContractName;
Expand Down Expand Up @@ -161,20 +162,25 @@ impl StackerDB {
} else {
warn!("Chunk rejected by stackerdb: {chunk_ack:?}");
}
if let Some(reason) = chunk_ack.reason {
// TODO: fix this jankiness. Update stackerdb to use an error code mapping instead of just a string
// See: https://github.com/stacks-network/stacks-blockchain/issues/3917
if reason.contains("Data for this slot and version already exist") {
warn!("Failed to send message to stackerdb due to wrong version number {}. Incrementing and retrying...", slot_version);
if let Some(versions) = self.slot_versions.get_mut(&msg_id) {
// NOTE: per the above, this is always executed
versions.insert(slot_id, slot_version.saturating_add(1));
} else {
return Err(ClientError::NotConnected);
if let Some(code) = chunk_ack.code {
jferrant marked this conversation as resolved.
Show resolved Hide resolved
match StackerDBErrorCodes::from_code(code) {
Some(StackerDBErrorCodes::DataAlreadyExists) => {
warn!("Failed to send message to stackerdb due to wrong version number {}. Incrementing and retrying...", slot_version);
if let Some(versions) = self.slot_versions.get_mut(&msg_id) {
// NOTE: per the above, this is always executed
versions.insert(slot_id, slot_version.saturating_add(1));
} else {
return Err(ClientError::NotConnected);
}
}
_ => {
warn!("Failed to send message to stackerdb: {:?}", chunk_ack);
return Err(ClientError::PutChunkRejected(
chunk_ack
.reason
.unwrap_or_else(|| "No reason given".to_string()),
));
}
} else {
warn!("Failed to send message to stackerdb: {}", reason);
return Err(ClientError::PutChunkRejected(reason));
}
}
}
Expand Down Expand Up @@ -343,6 +349,7 @@ mod tests {
accepted: true,
reason: None,
metadata: None,
code: None,
};
let mock_server = mock_server_from_config(&config);
let h = spawn(move || stackerdb.send_message_with_retry(signer_message));
Expand Down
34 changes: 34 additions & 0 deletions stacks-signer/src/config.rs
Expand Up @@ -163,6 +163,8 @@ pub struct SignerConfig {
pub sign_timeout: Option<Duration>,
/// the STX tx fee to use in uSTX
pub tx_fee_ustx: u64,
/// The path to the signer's database file
pub db_path: PathBuf,
}

/// The parsed configuration for the signer
Expand Down Expand Up @@ -196,6 +198,8 @@ pub struct GlobalConfig {
pub tx_fee_ustx: u64,
/// the authorization password for the block proposal endpoint
pub auth_password: String,
/// The path to the signer's database file
pub db_path: PathBuf,
}

/// Internal struct for loading up the config file
Expand Down Expand Up @@ -226,6 +230,8 @@ struct RawConfigFile {
pub tx_fee_ustx: Option<u64>,
/// The authorization password for the block proposal endpoint
pub auth_password: String,
/// The path to the signer's database file or :memory: for an in-memory database
pub db_path: String,
}

impl RawConfigFile {
Expand Down Expand Up @@ -302,6 +308,8 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
let dkg_private_timeout = raw_data.dkg_private_timeout_ms.map(Duration::from_millis);
let nonce_timeout = raw_data.nonce_timeout_ms.map(Duration::from_millis);
let sign_timeout = raw_data.sign_timeout_ms.map(Duration::from_millis);
let db_path = raw_data.db_path.into();

Ok(Self {
node_host: raw_data.node_host,
endpoint,
Expand All @@ -317,6 +325,7 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
sign_timeout,
tx_fee_ustx: raw_data.tx_fee_ustx.unwrap_or(TX_FEE_USTX),
auth_password: raw_data.auth_password,
db_path,
})
}
}
Expand Down Expand Up @@ -363,6 +372,7 @@ node_host = "{node_host}"
endpoint = "{endpoint}"
network = "{network}"
auth_password = "{password}"
db_path = ":memory:"
"#
);

Expand All @@ -381,3 +391,27 @@ event_timeout = {event_timeout_ms}

signer_config_tomls
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn build_signer_config_tomls_should_produce_deserializable_strings() {
let pk = StacksPrivateKey::from_hex(
"eb05c83546fdd2c79f10f5ad5434a90dd28f7e3acb7c092157aa1bc3656b012c01",
)
.unwrap();

let node_host = "localhost";
let network = Network::Testnet;
let password = "melon";

let config_tomls = build_signer_config_tomls(&[pk], node_host, None, &network, password);

let config =
RawConfigFile::load_from_str(&config_tomls[0]).expect("Failed to parse config file");

assert_eq!(config.auth_password, "melon");
}
}
2 changes: 2 additions & 0 deletions stacks-signer/src/lib.rs
Expand Up @@ -32,3 +32,5 @@ pub mod coordinator;
pub mod runloop;
/// The signer module for processing events
pub mod signer;
/// The state module for the signer
pub mod signerdb;
1 change: 1 addition & 0 deletions stacks-signer/src/runloop.rs
Expand Up @@ -224,6 +224,7 @@ impl RunLoop {
nonce_timeout: self.config.nonce_timeout,
sign_timeout: self.config.sign_timeout,
tx_fee_ustx: self.config.tx_fee_ustx,
db_path: self.config.db_path.clone(),
})
}

Expand Down