Skip to content

Commit 318fca2

Browse files
authored
Merge branch 'develop' into feat/signature-count-endpoint
2 parents b734407 + 1f0fba2 commit 318fca2

File tree

13 files changed

+481
-84
lines changed

13 files changed

+481
-84
lines changed

libsigner/src/v0/messages.rs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ pub struct PeerInfo {
275275
pub pox_consensus: ConsensusHash,
276276
/// The server version
277277
pub server_version: String,
278+
/// The network id
279+
pub network_id: u32,
278280
}
279281

280282
impl StacksMessageCodec for PeerInfo {
@@ -287,6 +289,7 @@ impl StacksMessageCodec for PeerInfo {
287289
fd.write_all(self.server_version.as_bytes())
288290
.map_err(CodecError::WriteError)?;
289291
write_next(fd, &self.pox_consensus)?;
292+
write_next(fd, &self.network_id)?;
290293
Ok(())
291294
}
292295

@@ -305,13 +308,15 @@ impl StacksMessageCodec for PeerInfo {
305308
)
306309
})?;
307310
let pox_consensus = read_next::<ConsensusHash, _>(fd)?;
311+
let network_id = read_next(fd)?;
308312
Ok(Self {
309313
burn_block_height,
310314
stacks_tip_consensus_hash,
311315
stacks_tip,
312316
stacks_tip_height,
313317
server_version,
314318
pox_consensus,
319+
network_id,
315320
})
316321
}
317322
}
@@ -321,18 +326,15 @@ impl StacksMessageCodec for PeerInfo {
321326
pub struct MockProposal {
322327
/// The view of the stacks node peer information at the time of the mock proposal
323328
pub peer_info: PeerInfo,
324-
/// The chain id for the mock proposal
325-
pub chain_id: u32,
326329
/// The miner's signature across the peer info
327330
signature: MessageSignature,
328331
}
329332

330333
impl MockProposal {
331334
/// Create a new mock proposal data struct from the provided peer info, chain id, and private key.
332-
pub fn new(peer_info: PeerInfo, chain_id: u32, stacks_private_key: &StacksPrivateKey) -> Self {
335+
pub fn new(peer_info: PeerInfo, stacks_private_key: &StacksPrivateKey) -> Self {
333336
let mut sig = Self {
334337
signature: MessageSignature::empty(),
335-
chain_id,
336338
peer_info,
337339
};
338340
sig.sign(stacks_private_key)
@@ -342,7 +344,8 @@ impl MockProposal {
342344

343345
/// The signature hash for the mock proposal
344346
pub fn miner_signature_hash(&self) -> Sha256Sum {
345-
let domain_tuple = make_structured_data_domain("mock-miner", "1.0.0", self.chain_id);
347+
let domain_tuple =
348+
make_structured_data_domain("mock-miner", "1.0.0", self.peer_info.network_id);
346349
let data_tuple = Value::Tuple(
347350
TupleData::from_data(vec![
348351
(
@@ -375,7 +378,8 @@ impl MockProposal {
375378

376379
/// The signature hash including the miner's signature. Used by signers.
377380
fn signer_signature_hash(&self) -> Sha256Sum {
378-
let domain_tuple = make_structured_data_domain("mock-signer", "1.0.0", self.chain_id);
381+
let domain_tuple =
382+
make_structured_data_domain("mock-signer", "1.0.0", self.peer_info.network_id);
379383
let data_tuple = Value::Tuple(
380384
TupleData::from_data(vec![
381385
(
@@ -413,18 +417,15 @@ impl MockProposal {
413417
impl StacksMessageCodec for MockProposal {
414418
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
415419
self.peer_info.consensus_serialize(fd)?;
416-
write_next(fd, &self.chain_id)?;
417420
write_next(fd, &self.signature)?;
418421
Ok(())
419422
}
420423

421424
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
422425
let peer_info = PeerInfo::consensus_deserialize(fd)?;
423-
let chain_id = read_next::<u32, _>(fd)?;
424426
let signature = read_next::<MessageSignature, _>(fd)?;
425427
Ok(Self {
426428
peer_info,
427-
chain_id,
428429
signature,
429430
})
430431
}
@@ -1024,26 +1025,26 @@ mod test {
10241025
let stacks_tip_height = thread_rng().next_u64();
10251026
let server_version = "0.0.0".to_string();
10261027
let pox_consensus_byte: u8 = thread_rng().gen();
1028+
let network_byte: u8 = thread_rng().gen_range(0..=1);
1029+
let network_id = if network_byte == 1 {
1030+
CHAIN_ID_TESTNET
1031+
} else {
1032+
CHAIN_ID_MAINNET
1033+
};
10271034
PeerInfo {
10281035
burn_block_height,
10291036
stacks_tip_consensus_hash: ConsensusHash([stacks_tip_consensus_byte; 20]),
10301037
stacks_tip: BlockHeaderHash([stacks_tip_byte; 32]),
10311038
stacks_tip_height,
10321039
server_version,
10331040
pox_consensus: ConsensusHash([pox_consensus_byte; 20]),
1041+
network_id,
10341042
}
10351043
}
10361044
fn random_mock_proposal() -> MockProposal {
1037-
let chain_byte: u8 = thread_rng().gen_range(0..=1);
1038-
let chain_id = if chain_byte == 1 {
1039-
CHAIN_ID_TESTNET
1040-
} else {
1041-
CHAIN_ID_MAINNET
1042-
};
10431045
let peer_info = random_peer_data();
10441046
MockProposal {
10451047
peer_info,
1046-
chain_id,
10471048
signature: MessageSignature::empty(),
10481049
}
10491050
}

stacks-signer/src/cli.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ pub enum Command {
9797
GenerateVote(GenerateVoteArgs),
9898
/// Verify the vote for a specified SIP against a public key and vote info
9999
VerifyVote(VerifyVoteArgs),
100+
/// Verify signer signatures by checking stackerdb slots contain the correct data
101+
MonitorSigners(MonitorSignersArgs),
100102
}
101103

102104
/// Basic arguments for all cyrptographic and stacker-db functionality
@@ -258,6 +260,20 @@ impl TryFrom<u8> for Vote {
258260
}
259261
}
260262

263+
#[derive(Parser, Debug, Clone)]
264+
/// Arguments for the MonitorSigners command
265+
pub struct MonitorSignersArgs {
266+
/// The Stacks node to connect to
267+
#[arg(long)]
268+
pub host: String,
269+
/// Set the polling interval in seconds.
270+
#[arg(long, short, default_value = "60")]
271+
pub interval: u64,
272+
/// Max age in seconds before a signer message is considered stale.
273+
#[arg(long, short, default_value = "1200")]
274+
pub max_age: u64,
275+
}
276+
261277
#[derive(Clone, Debug, PartialEq)]
262278
/// Wrapper around `Pox4SignatureTopic` to implement `ValueEnum`
263279
pub struct StackingSignatureMethod(Pox4SignatureTopic);

stacks-signer/src/client/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::time::Duration;
2323

2424
use clarity::vm::errors::Error as ClarityError;
2525
use clarity::vm::types::serialization::SerializationError;
26+
use libsigner::RPCError;
2627
use libstackerdb::Error as StackerDBError;
2728
use slog::slog_debug;
2829
pub use stackerdb::*;
@@ -94,6 +95,9 @@ pub enum ClientError {
9495
/// A successful sortition's info response should be parseable into a SortitionState
9596
#[error("A successful sortition's info response should be parseable into a SortitionState")]
9697
UnexpectedSortitionInfo,
98+
/// An RPC libsigner error occurred
99+
#[error("A libsigner RPC error occurred: {0}")]
100+
RPCError(#[from] RPCError),
97101
}
98102

99103
/// Retry a function F with an exponential backoff and notification on transient failure

stacks-signer/src/client/stacks_client.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
//
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/>.
16-
use std::collections::VecDeque;
17-
use std::net::SocketAddr;
16+
use std::collections::{HashMap, VecDeque};
1817

1918
use blockstack_lib::burnchains::Txid;
2019
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
@@ -57,6 +56,7 @@ use stacks_common::types::StacksEpochId;
5756
use stacks_common::{debug, warn};
5857
use wsts::curve::point::{Compressed, Point};
5958

59+
use super::SignerSlotID;
6060
use crate::client::{retry_with_exponential_backoff, ClientError};
6161
use crate::config::GlobalConfig;
6262
use crate::runloop::RewardCycleInfo;
@@ -75,7 +75,7 @@ pub struct StacksClient {
7575
/// The chain we are interacting with
7676
chain_id: u32,
7777
/// Whether we are mainnet or not
78-
mainnet: bool,
78+
pub mainnet: bool,
7979
/// The Client used to make HTTP connects
8080
stacks_node_client: reqwest::blocking::Client,
8181
/// the auth password for the stacks node
@@ -116,7 +116,7 @@ impl StacksClient {
116116
/// Create a new signer StacksClient with the provided private key, stacks node host endpoint, version, and auth password
117117
pub fn new(
118118
stacks_private_key: StacksPrivateKey,
119-
node_host: SocketAddr,
119+
node_host: String,
120120
auth_password: String,
121121
mainnet: bool,
122122
) -> Self {
@@ -144,6 +144,28 @@ impl StacksClient {
144144
}
145145
}
146146

147+
/// Create a new signer StacksClient and attempt to connect to the stacks node to determine the version
148+
pub fn try_from_host(
149+
stacks_private_key: StacksPrivateKey,
150+
node_host: String,
151+
auth_password: String,
152+
) -> Result<Self, ClientError> {
153+
let mut stacks_client = Self::new(stacks_private_key, node_host, auth_password, true);
154+
let pubkey = StacksPublicKey::from_private(&stacks_private_key);
155+
let info = stacks_client.get_peer_info()?;
156+
if info.network_id == CHAIN_ID_MAINNET {
157+
stacks_client.mainnet = true;
158+
stacks_client.chain_id = CHAIN_ID_MAINNET;
159+
stacks_client.tx_version = TransactionVersion::Mainnet;
160+
} else {
161+
stacks_client.mainnet = false;
162+
stacks_client.chain_id = CHAIN_ID_TESTNET;
163+
stacks_client.tx_version = TransactionVersion::Testnet;
164+
}
165+
stacks_client.stacks_address = StacksAddress::p2pkh(stacks_client.mainnet, &pubkey);
166+
Ok(stacks_client)
167+
}
168+
147169
/// Get our signer address
148170
pub const fn get_signer_address(&self) -> &StacksAddress {
149171
&self.stacks_address
@@ -204,7 +226,7 @@ impl StacksClient {
204226
}
205227

206228
/// Helper function that attempts to deserialize a clarity hext string as a list of signer slots and their associated number of signer slots
207-
pub fn parse_signer_slots(
229+
fn parse_signer_slots(
208230
&self,
209231
value: ClarityValue,
210232
) -> Result<Vec<(StacksAddress, u128)>, ClientError> {
@@ -226,6 +248,31 @@ impl StacksClient {
226248
Ok(signer_slots)
227249
}
228250

251+
/// Get the stackerdb signer slots for a specific reward cycle
252+
pub fn get_parsed_signer_slots(
253+
&self,
254+
reward_cycle: u64,
255+
) -> Result<HashMap<StacksAddress, SignerSlotID>, ClientError> {
256+
let signer_set =
257+
u32::try_from(reward_cycle % 2).expect("FATAL: reward_cycle % 2 exceeds u32::MAX");
258+
let signer_stackerdb_contract_id = boot_code_id(SIGNERS_NAME, self.mainnet);
259+
// Get the signer writers from the stacker-db to find the signer slot id
260+
let stackerdb_signer_slots =
261+
self.get_stackerdb_signer_slots(&signer_stackerdb_contract_id, signer_set)?;
262+
Ok(stackerdb_signer_slots
263+
.into_iter()
264+
.enumerate()
265+
.map(|(index, (address, _))| {
266+
(
267+
address,
268+
SignerSlotID(
269+
u32::try_from(index).expect("FATAL: number of signers exceeds u32::MAX"),
270+
),
271+
)
272+
})
273+
.collect())
274+
}
275+
229276
/// Get the vote for a given round, reward cycle, and signer address
230277
pub fn get_vote_for_aggregate_public_key(
231278
&self,

stacks-signer/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,16 @@ pub mod cli;
2929
pub mod client;
3030
/// The configuration module for the signer
3131
pub mod config;
32+
/// The signer monitor for observing signer behaviours in the network
33+
pub mod monitor_signers;
3234
/// The monitoring server for the signer
3335
pub mod monitoring;
3436
/// The primary runloop for the signer
3537
pub mod runloop;
3638
/// The signer state module
3739
pub mod signerdb;
40+
/// The util module for the signer
41+
pub mod utils;
3842
/// The v0 implementation of the signer. This does not include WSTS support
3943
pub mod v0;
4044
/// The v1 implementation of the singer. This includes WSTS support

stacks-signer/src/main.rs

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,25 @@ use std::io::{self, Write};
3131
use blockstack_lib::util_lib::signed_structured_data::pox4::make_pox_4_signer_key_signature;
3232
use clap::Parser;
3333
use clarity::types::chainstate::StacksPublicKey;
34-
use clarity::vm::types::QualifiedContractIdentifier;
35-
use libsigner::{SignerSession, StackerDBSession};
34+
use clarity::util::sleep_ms;
35+
use libsigner::SignerSession;
3636
use libstackerdb::StackerDBChunkData;
37-
use slog::slog_debug;
38-
use stacks_common::debug;
37+
use slog::{slog_debug, slog_error};
3938
use stacks_common::util::hash::to_hex;
4039
use stacks_common::util::secp256k1::MessageSignature;
40+
use stacks_common::{debug, error};
4141
use stacks_signer::cli::{
4242
Cli, Command, GenerateStackingSignatureArgs, GenerateVoteArgs, GetChunkArgs,
43-
GetLatestChunkArgs, PutChunkArgs, RunSignerArgs, StackerDBArgs, VerifyVoteArgs,
43+
GetLatestChunkArgs, MonitorSignersArgs, PutChunkArgs, RunSignerArgs, StackerDBArgs,
44+
VerifyVoteArgs,
4445
};
4546
use stacks_signer::config::GlobalConfig;
47+
use stacks_signer::monitor_signers::SignerMonitor;
48+
use stacks_signer::utils::stackerdb_session;
4649
use stacks_signer::v0::SpawnedSigner;
4750
use tracing_subscriber::prelude::*;
4851
use tracing_subscriber::{fmt, EnvFilter};
4952

50-
/// Create a new stacker db session
51-
fn stackerdb_session(host: &str, contract: QualifiedContractIdentifier) -> StackerDBSession {
52-
let mut session = StackerDBSession::new(host, contract.clone());
53-
session.connect(host.to_string(), contract).unwrap();
54-
session
55-
}
56-
5753
/// Write the chunk to stdout
5854
fn write_chunk_to_stdout(chunk_opt: Option<Vec<u8>>) {
5955
if let Some(chunk) = chunk_opt.as_ref() {
@@ -188,6 +184,20 @@ fn handle_verify_vote(args: VerifyVoteArgs, do_print: bool) -> bool {
188184
valid_vote
189185
}
190186

187+
fn handle_monitor_signers(args: MonitorSignersArgs) {
188+
// Verify that the host is a valid URL
189+
let mut signer_monitor = SignerMonitor::new(args);
190+
loop {
191+
if let Err(e) = signer_monitor.start() {
192+
error!(
193+
"Error occurred monitoring signers: {:?}. Waiting and trying again.",
194+
e
195+
);
196+
sleep_ms(1000);
197+
}
198+
}
199+
}
200+
191201
fn main() {
192202
let cli = Cli::parse();
193203

@@ -224,6 +234,9 @@ fn main() {
224234
Command::VerifyVote(args) => {
225235
handle_verify_vote(args, true);
226236
}
237+
Command::MonitorSigners(args) => {
238+
handle_monitor_signers(args);
239+
}
227240
}
228241
}
229242

0 commit comments

Comments
 (0)