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: signer monitoring server #4719

Merged
merged 10 commits into from
May 7, 2024
5 changes: 5 additions & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions libsigner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ path = "./src/libsigner.rs"
[dependencies]
clarity = { path = "../clarity" }
hashbrown = { workspace = true }
lazy_static = "1.4.0"
libc = "0.2"
libstackerdb = { path = "../libstackerdb" }
prometheus = { version = "0.9", optional = true }
serde = "1"
serde_derive = "1"
serde_stacker = "0.1"
Expand Down Expand Up @@ -50,3 +52,6 @@ sha2 = { version = "0.10", features = ["asm"] }

[target.'cfg(any(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")), any(target_os = "windows")))'.dependencies]
sha2 = { version = "0.10" }

[features]
monitoring_prom = ["prometheus"]
6 changes: 6 additions & 0 deletions stacks-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ backoff = "0.4"
clarity = { path = "../clarity" }
clap = { version = "4.1.1", features = ["derive", "env"] }
hashbrown = { workspace = true }
lazy_static = "1.4.0"
libsigner = { path = "../libsigner" }
libstackerdb = { path = "../libstackerdb" }
prometheus = { version = "0.9", optional = true }
rand_core = "0.6"
reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde = "1"
Expand All @@ -37,6 +39,7 @@ slog-term = "2.6.0"
stacks-common = { path = "../stacks-common" }
stackslib = { path = "../stackslib" }
thiserror = "1.0"
tiny_http = { version = "0.12", optional = true }
toml = "0.5.6"
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
Expand All @@ -60,3 +63,6 @@ features = ["arbitrary_precision", "unbounded_depth"]
[dependencies.secp256k1]
version = "0.24.3"
features = ["serde", "recovery"]

[features]
monitoring_prom = ["libsigner/monitoring_prom", "prometheus", "tiny_http"]
28 changes: 27 additions & 1 deletion stacks-signer/src/client/stacks_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ impl StacksClient {
estimated_len: Some(tx.tx_len()),
transaction_payload: to_hex(&tx.payload.serialize_to_vec()),
};
let timer =
crate::monitoring::new_rpc_call_timer(&self.fees_transaction_path(), &self.http_origin);
let send_request = || {
self.stacks_node_client
.post(self.fees_transaction_path())
Expand All @@ -219,6 +221,7 @@ impl StacksClient {
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
timer.stop_and_record();
let fee_estimate_response = response.json::<RPCFeeEstimateResponse>()?;
let fee = fee_estimate_response
.estimations
Expand Down Expand Up @@ -268,6 +271,8 @@ impl StacksClient {
block,
chain_id: self.chain_id,
};
let timer =
crate::monitoring::new_rpc_call_timer(&self.block_proposal_path(), &self.http_origin);
let send_request = || {
self.stacks_node_client
.post(self.block_proposal_path())
Expand All @@ -279,6 +284,7 @@ impl StacksClient {
};

let response = retry_with_exponential_backoff(send_request)?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down Expand Up @@ -355,13 +361,16 @@ impl StacksClient {
/// Get the current peer info data from the stacks node
pub fn get_peer_info(&self) -> Result<RPCPeerInfoData, ClientError> {
debug!("Getting stacks node info...");
let timer =
crate::monitoring::new_rpc_call_timer(&self.core_info_path(), &self.http_origin);
let send_request = || {
self.stacks_node_client
.get(self.core_info_path())
.send()
.map_err(backoff::Error::transient)
};
let response = retry_with_exponential_backoff(send_request)?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down Expand Up @@ -402,13 +411,18 @@ impl StacksClient {
reward_cycle: u64,
) -> Result<Option<Vec<NakamotoSignerEntry>>, ClientError> {
debug!("Getting reward set for reward cycle {reward_cycle}...");
let timer = crate::monitoring::new_rpc_call_timer(
&self.reward_set_path(reward_cycle),
&self.http_origin,
);
let send_request = || {
self.stacks_node_client
.get(self.reward_set_path(reward_cycle))
.send()
.map_err(backoff::Error::transient)
};
let response = retry_with_exponential_backoff(send_request)?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand All @@ -419,13 +433,17 @@ impl StacksClient {
/// Retreive the current pox data from the stacks node
pub fn get_pox_data(&self) -> Result<RPCPoxInfoData, ClientError> {
debug!("Getting pox data...");
#[cfg(feature = "monitoring_prom")]
let timer = crate::monitoring::new_rpc_call_timer(&self.pox_path(), &self.http_origin);
let send_request = || {
self.stacks_node_client
.get(self.pox_path())
.send()
.map_err(backoff::Error::transient)
};
let response = retry_with_exponential_backoff(send_request)?;
#[cfg(feature = "monitoring_prom")]
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down Expand Up @@ -458,18 +476,21 @@ impl StacksClient {
}

/// Helper function to retrieve the account info from the stacks node for a specific address
fn get_account_entry(
pub fn get_account_entry(
&self,
address: &StacksAddress,
) -> Result<AccountEntryResponse, ClientError> {
debug!("Getting account info...");
let timer =
crate::monitoring::new_rpc_call_timer(&self.accounts_path(address), &self.http_origin);
let send_request = || {
self.stacks_node_client
.get(self.accounts_path(address))
.send()
.map_err(backoff::Error::transient)
};
let response = retry_with_exponential_backoff(send_request)?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down Expand Up @@ -536,6 +557,8 @@ impl StacksClient {
pub fn submit_transaction(&self, tx: &StacksTransaction) -> Result<Txid, ClientError> {
let txid = tx.txid();
let tx = tx.serialize_to_vec();
let timer =
crate::monitoring::new_rpc_call_timer(&self.transaction_path(), &self.http_origin);
let send_request = || {
self.stacks_node_client
.post(self.transaction_path())
Expand All @@ -548,6 +571,7 @@ impl StacksClient {
})
};
let response = retry_with_exponential_backoff(send_request)?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down Expand Up @@ -576,12 +600,14 @@ impl StacksClient {
let body =
json!({"sender": self.stacks_address.to_string(), "arguments": args}).to_string();
let path = self.read_only_path(contract_addr, contract_name, function_name);
let timer = crate::monitoring::new_rpc_call_timer(&path, &self.http_origin);
let response = self
.stacks_node_client
.post(path)
.header("Content-Type", "application/json")
.body(body)
.send()?;
timer.stop_and_record();
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
Expand Down
45 changes: 44 additions & 1 deletion stacks-signer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ pub struct GlobalConfig {
pub auth_password: String,
/// The path to the signer's database file
pub db_path: PathBuf,
/// Metrics endpoint
pub metrics_endpoint: Option<SocketAddr>,
}

/// Internal struct for loading up the config file
Expand Down Expand Up @@ -221,6 +223,8 @@ struct RawConfigFile {
pub auth_password: String,
/// The path to the signer's database file or :memory: for an in-memory database
pub db_path: String,
/// Metrics endpoint
pub metrics_endpoint: Option<String>,
}

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

let metrics_endpoint = match raw_data.metrics_endpoint {
Some(endpoint) => Some(
endpoint
.to_socket_addrs()
.map_err(|_| ConfigError::BadField("endpoint".to_string(), endpoint.clone()))?
.next()
.ok_or_else(|| {
ConfigError::BadField("endpoint".to_string(), endpoint.clone())
})?,
),
None => None,
};

Ok(Self {
node_host: raw_data.node_host,
endpoint,
Expand All @@ -315,6 +332,7 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
max_tx_fee_ustx: raw_data.max_tx_fee_ustx,
auth_password: raw_data.auth_password,
db_path,
metrics_endpoint,
})
}
}
Expand Down Expand Up @@ -345,6 +363,10 @@ impl GlobalConfig {
0 => "default".to_string(),
_ => (self.tx_fee_ustx as f64 / 1_000_000.0).to_string(),
};
let metrics_endpoint = match &self.metrics_endpoint {
Some(endpoint) => endpoint.to_string(),
None => "None".to_string(),
};
format!(
r#"
Stacks node host: {node_host}
Expand All @@ -354,14 +376,16 @@ Public key: {public_key}
Network: {network}
Database path: {db_path}
DKG transaction fee: {tx_fee} uSTX
Metrics endpoint: {metrics_endpoint}
"#,
node_host = self.node_host,
endpoint = self.endpoint,
stacks_address = self.stacks_address,
public_key = StacksPublicKey::from_private(&self.stacks_private_key).to_hex(),
network = self.network,
db_path = self.db_path.to_str().unwrap_or_default(),
tx_fee = tx_fee
tx_fee = tx_fee,
metrics_endpoint = metrics_endpoint,
)
}
}
Expand All @@ -384,6 +408,7 @@ pub fn build_signer_config_tomls(
mut port_start: usize,
max_tx_fee_ustx: Option<u64>,
tx_fee_ustx: Option<u64>,
mut metrics_port_start: Option<usize>,
) -> Vec<String> {
let mut signer_config_tomls = vec![];

Expand Down Expand Up @@ -438,6 +463,17 @@ tx_fee_ustx = {tx_fee_ustx}
)
}

if let Some(metrics_port) = metrics_port_start {
let metrics_endpoint = format!("localhost:{}", metrics_port);
signer_config_toml = format!(
r#"
{signer_config_toml}
metrics_endpoint = "{metrics_endpoint}"
"#
);
metrics_port_start = Some(metrics_port + 1);
}

signer_config_tomls.push(signer_config_toml);
}

Expand Down Expand Up @@ -469,6 +505,7 @@ mod tests {
3000,
None,
None,
Some(4000),
);

let config =
Expand All @@ -477,6 +514,7 @@ mod tests {
assert_eq!(config.auth_password, "melon");
assert!(config.max_tx_fee_ustx.is_none());
assert!(config.tx_fee_ustx.is_none());
assert_eq!(config.metrics_endpoint, Some("localhost:4000".to_string()));
}

#[test]
Expand All @@ -501,6 +539,7 @@ mod tests {
3000,
None,
None,
None,
);

let config =
Expand All @@ -526,6 +565,7 @@ mod tests {
3000,
max_tx_fee_ustx,
tx_fee_ustx,
None,
);

let config =
Expand All @@ -546,6 +586,7 @@ mod tests {
3000,
max_tx_fee_ustx,
None,
None,
);

let config =
Expand All @@ -570,6 +611,7 @@ mod tests {
3000,
None,
tx_fee_ustx,
None,
);

let config =
Expand Down Expand Up @@ -598,6 +640,7 @@ Public key: 03bc489f27da3701d9f9e577c88de5567cf4023111b7577042d55cde4d823a3505
Network: testnet
Database path: :memory:
DKG transaction fee: 0.01 uSTX
Metrics endpoint: 0.0.0.0:9090
"#
)
);
Expand Down
3 changes: 3 additions & 0 deletions stacks-signer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ pub mod runloop;
pub mod signer;
/// The state module for the signer
pub mod signerdb;

/// The monitoring server for the signer
jferrant marked this conversation as resolved.
Show resolved Hide resolved
pub mod monitoring;