diff --git a/libsigner/src/error.rs b/libsigner/src/error.rs
index 101a1b35e..7c4deadf1 100644
--- a/libsigner/src/error.rs
+++ b/libsigner/src/error.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -71,4 +71,7 @@ pub enum EventError {
/// Unrecognized stacker DB contract error
#[error("Unrecognized StackerDB contract: {0}")]
UnrecognizedStackerDBContract(QualifiedContractIdentifier),
+ /// Empty chunks event
+ #[error("Empty chunks event")]
+ EmptyChunksEvent,
}
diff --git a/libsigner/src/events.rs b/libsigner/src/events.rs
index 67eb97057..c603db7f0 100644
--- a/libsigner/src/events.rs
+++ b/libsigner/src/events.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
+use std::fmt::Debug;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -51,11 +52,19 @@ use wsts::net::{
use wsts::state_machine::signer;
use crate::http::{decode_http_body, decode_http_request};
-use crate::{EventError, SignerMessage};
+use crate::EventError;
+
+/// Define the trait for the event processor
+pub trait SignerEventTrait:
+ StacksMessageCodec + Clone + Debug + Send
+{
+}
+
+impl SignerEventTrait for T {}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
/// BlockProposal sent to signers
-pub struct BlockProposalSigners {
+pub struct BlockProposal {
/// The block itself
pub block: NakamotoBlock,
/// The burn height the block is mined during
@@ -64,25 +73,7 @@ pub struct BlockProposalSigners {
pub reward_cycle: u64,
}
-/// Event enum for newly-arrived signer subscribed events
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub enum SignerEvent {
- /// A miner sent a message over .miners
- /// The `Vec` will contain any signer WSTS messages made by the miner while acting as a coordinator.
- /// The `Option` will contain the message sender's public key if the vec is non-empty.
- MinerMessages(Vec, Option),
- /// The signer messages for other signers and miners to observe
- /// The u32 is the signer set to which the message belongs (either 0 or 1)
- SignerMessages(u32, Vec),
- /// A new block proposal validation response from the node
- BlockValidationResponse(BlockValidateResponse),
- /// Status endpoint request
- StatusCheck,
- /// A new burn block event was received with the given burnchain block height
- NewBurnBlock(u64),
-}
-
-impl StacksMessageCodec for BlockProposalSigners {
+impl StacksMessageCodec for BlockProposal {
fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
self.block.consensus_serialize(fd)?;
self.burn_height.consensus_serialize(fd)?;
@@ -94,7 +85,7 @@ impl StacksMessageCodec for BlockProposalSigners {
let block = NakamotoBlock::consensus_deserialize(fd)?;
let burn_height = u64::consensus_deserialize(fd)?;
let reward_cycle = u64::consensus_deserialize(fd)?;
- Ok(BlockProposalSigners {
+ Ok(BlockProposal {
block,
burn_height,
reward_cycle,
@@ -102,6 +93,24 @@ impl StacksMessageCodec for BlockProposalSigners {
}
}
+/// Event enum for newly-arrived signer subscribed events
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum SignerEvent {
+ /// A miner sent a message over .miners
+ /// The `Vec` will contain any signer messages made by the miner.
+ /// The `StacksPublicKey` is the message sender's public key.
+ MinerMessages(Vec, StacksPublicKey),
+ /// The signer messages for other signers and miners to observe
+ /// The u32 is the signer set to which the message belongs (either 0 or 1)
+ SignerMessages(u32, Vec),
+ /// A new block proposal validation response from the node
+ BlockValidationResponse(BlockValidateResponse),
+ /// Status endpoint request
+ StatusCheck,
+ /// A new burn block event was received with the given burnchain block height
+ NewBurnBlock(u64),
+}
+
/// Trait to implement a stop-signaler for the event receiver thread.
/// The caller calls `send()` and the event receiver loop (which lives in a separate thread) will
/// terminate.
@@ -111,7 +120,7 @@ pub trait EventStopSignaler {
}
/// Trait to implement to handle signer specific events sent by the Stacks node
-pub trait EventReceiver {
+pub trait EventReceiver {
/// The implementation of ST will ensure that a call to ST::send() will cause
/// the call to `is_stopped()` below to return true.
type ST: EventStopSignaler + Send + Sync;
@@ -119,11 +128,11 @@ pub trait EventReceiver {
/// Open a server socket to the given socket address.
fn bind(&mut self, listener: SocketAddr) -> Result;
/// Return the next event
- fn next_event(&mut self) -> Result;
+ fn next_event(&mut self) -> Result, EventError>;
/// Add a downstream event consumer
- fn add_consumer(&mut self, event_out: Sender);
+ fn add_consumer(&mut self, event_out: Sender>);
/// Forward the event to downstream consumers
- fn forward_event(&mut self, ev: SignerEvent) -> bool;
+ fn forward_event(&mut self, ev: SignerEvent) -> bool;
/// Determine if the receiver should hang up
fn is_stopped(&self) -> bool;
/// Get a stop signal instance that, when sent, will cause this receiver to stop accepting new
@@ -164,23 +173,23 @@ pub trait EventReceiver {
}
/// Event receiver for Signer events
-pub struct SignerEventReceiver {
+pub struct SignerEventReceiver {
/// Address we bind to
local_addr: Option,
/// server socket that listens for HTTP POSTs from the node
http_server: Option,
/// channel into which to write newly-discovered data
- out_channels: Vec>,
+ out_channels: Vec>>,
/// inter-thread stop variable -- if set to true, then the `main_loop` will exit
stop_signal: Arc,
/// Whether the receiver is running on mainnet
is_mainnet: bool,
}
-impl SignerEventReceiver {
+impl SignerEventReceiver {
/// Make a new Signer event receiver, and return both the receiver and the read end of a
/// channel into which node-received data can be obtained.
- pub fn new(is_mainnet: bool) -> SignerEventReceiver {
+ pub fn new(is_mainnet: bool) -> SignerEventReceiver {
SignerEventReceiver {
http_server: None,
local_addr: None,
@@ -193,7 +202,7 @@ impl SignerEventReceiver {
/// Do something with the socket
pub fn with_server(&mut self, todo: F) -> Result
where
- F: FnOnce(&SignerEventReceiver, &mut HttpServer, bool) -> R,
+ F: FnOnce(&SignerEventReceiver, &mut HttpServer, bool) -> R,
{
let mut server = if let Some(s) = self.http_server.take() {
s
@@ -225,6 +234,7 @@ impl SignerStopSignaler {
}
impl EventStopSignaler for SignerStopSignaler {
+ #[cfg_attr(test, mutants::skip)]
fn send(&mut self) {
self.stop_signal.store(true, Ordering::SeqCst);
// wake up the thread so the atomicbool can be checked
@@ -238,15 +248,14 @@ impl EventStopSignaler for SignerStopSignaler {
body.len(),
body
);
- match stream.write_all(req.as_bytes()) {
- Err(e) => error!("Failed to send shutdown request: {}", e),
- _ => (),
- };
+ if let Err(e) = stream.write_all(req.as_bytes()) {
+ error!("Failed to send shutdown request: {}", e);
+ }
}
}
}
-impl EventReceiver for SignerEventReceiver {
+impl EventReceiver for SignerEventReceiver {
type ST = SignerStopSignaler;
/// Start listening on the given socket address.
@@ -261,7 +270,7 @@ impl EventReceiver for SignerEventReceiver {
/// Wait for the node to post something, and then return it.
/// Errors are recoverable -- the caller should call this method again even if it returns an
/// error.
- fn next_event(&mut self) -> Result {
+ fn next_event(&mut self) -> Result, EventError> {
self.with_server(|event_receiver, http_server, _is_mainnet| {
// were we asked to terminate?
if event_receiver.is_stopped() {
@@ -318,7 +327,7 @@ impl EventReceiver for SignerEventReceiver {
/// Forward an event
/// Return true on success; false on error.
/// Returning false terminates the event receiver.
- fn forward_event(&mut self, ev: SignerEvent) -> bool {
+ fn forward_event(&mut self, ev: SignerEvent) -> bool {
if self.out_channels.is_empty() {
// nothing to do
error!("No channels connected to event receiver");
@@ -342,7 +351,7 @@ impl EventReceiver for SignerEventReceiver {
}
/// Add an event consumer. A received event will be forwarded to this Sender.
- fn add_consumer(&mut self, out_channel: Sender) {
+ fn add_consumer(&mut self, out_channel: Sender>) {
self.out_channels.push(out_channel);
}
@@ -367,10 +376,10 @@ fn ack_dispatcher(request: HttpRequest) {
}
/// Process a stackerdb event from the node
-fn process_stackerdb_event(
+fn process_stackerdb_event(
local_addr: Option,
mut request: HttpRequest,
-) -> Result {
+) -> Result, EventError> {
debug!("Got stackerdb_chunks event");
let mut body = String::new();
if let Err(e) = request.as_reader().read_to_string(&mut body) {
@@ -395,7 +404,7 @@ fn process_stackerdb_event(
event_contract_id
);
ack_dispatcher(request);
- return Err(e.into());
+ return Err(e);
}
Ok(x) => x,
};
@@ -405,7 +414,7 @@ fn process_stackerdb_event(
Ok(signer_event)
}
-impl TryFrom for SignerEvent {
+impl TryFrom for SignerEvent {
type Error = EventError;
fn try_from(event: StackerDBChunksEvent) -> Result {
@@ -415,18 +424,18 @@ impl TryFrom for SignerEvent {
let mut messages = vec![];
let mut miner_pk = None;
for chunk in event.modified_slots {
+ let Ok(msg) = T::consensus_deserialize(&mut chunk.data.as_slice()) else {
+ continue;
+ };
+
miner_pk = Some(chunk.recover_pk().map_err(|e| {
EventError::MalformedRequest(format!(
"Failed to recover PK from StackerDB chunk: {e}"
))
})?);
- let Ok(msg) = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
- else {
- continue;
- };
messages.push(msg);
}
- SignerEvent::MinerMessages(messages, miner_pk)
+ SignerEvent::MinerMessages(messages, miner_pk.ok_or(EventError::EmptyChunksEvent)?)
} else if event.contract_id.name.starts_with(SIGNERS_NAME) && event.contract_id.is_boot() {
let Some((signer_set, _)) =
get_signers_db_signer_set_message_id(event.contract_id.name.as_str())
@@ -434,10 +443,10 @@ impl TryFrom for SignerEvent {
return Err(EventError::UnrecognizedStackerDBContract(event.contract_id));
};
// signer-XXX-YYY boot contract
- let signer_messages: Vec = event
+ let signer_messages: Vec = event
.modified_slots
.iter()
- .filter_map(|chunk| read_next::(&mut &chunk.data[..]).ok())
+ .filter_map(|chunk| read_next::(&mut &chunk.data[..]).ok())
.collect();
SignerEvent::SignerMessages(signer_set, signer_messages)
} else {
@@ -448,7 +457,9 @@ impl TryFrom for SignerEvent {
}
/// Process a proposal response from the node
-fn process_proposal_response(mut request: HttpRequest) -> Result {
+fn process_proposal_response(
+ mut request: HttpRequest,
+) -> Result, EventError> {
debug!("Got proposal_response event");
let mut body = String::new();
if let Err(e) = request.as_reader().read_to_string(&mut body) {
@@ -474,7 +485,9 @@ fn process_proposal_response(mut request: HttpRequest) -> Result Result {
+fn process_new_burn_block_event(
+ mut request: HttpRequest,
+) -> Result, EventError> {
debug!("Got burn_block event");
let mut body = String::new();
if let Err(e) = request.as_reader().read_to_string(&mut body) {
diff --git a/libsigner/src/http.rs b/libsigner/src/http.rs
index fe841415a..adb1f509f 100644
--- a/libsigner/src/http.rs
+++ b/libsigner/src/http.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/libsigner/src/libsigner.rs b/libsigner/src/libsigner.rs
index 0b16e97e1..874ebad1f 100644
--- a/libsigner/src/libsigner.rs
+++ b/libsigner/src/libsigner.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -39,18 +39,18 @@ mod tests;
mod error;
mod events;
mod http;
-mod messages;
mod runloop;
mod session;
mod signer_set;
+/// v0 signer related code
+pub mod v0;
+/// v1 signer related code
+pub mod v1;
pub use crate::error::{EventError, RPCError};
pub use crate::events::{
- BlockProposalSigners, EventReceiver, EventStopSignaler, SignerEvent, SignerEventReceiver,
- SignerStopSignaler,
-};
-pub use crate::messages::{
- BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerMessage,
+ BlockProposal, EventReceiver, EventStopSignaler, SignerEvent, SignerEventReceiver,
+ SignerEventTrait, SignerStopSignaler,
};
pub use crate::runloop::{RunningSigner, Signer, SignerRunLoop};
pub use crate::session::{SignerSession, StackerDBSession};
diff --git a/libsigner/src/runloop.rs b/libsigner/src/runloop.rs
index 0b7eb2dbc..b0f026f35 100644
--- a/libsigner/src/runloop.rs
+++ b/libsigner/src/runloop.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -24,11 +24,12 @@ use std::thread;
use std::thread::JoinHandle;
use std::time::Duration;
+use clarity::codec::StacksMessageCodec;
use stacks_common::deps_common::ctrlc as termination;
use stacks_common::deps_common::ctrlc::SignalId;
use crate::error::EventError;
-use crate::events::{EventReceiver, EventStopSignaler, SignerEvent};
+use crate::events::{EventReceiver, EventStopSignaler, SignerEvent, SignerEventTrait};
/// Some libcs, like musl, have a very small stack size.
/// Make sure it's big enough.
@@ -40,7 +41,7 @@ const STDERR: i32 = 2;
/// Trait describing the needful components of a top-level runloop.
/// This is where the signer business logic would go.
/// Implement this, and you get all the multithreaded setup for free.
-pub trait SignerRunLoop {
+pub trait SignerRunLoop {
/// Hint to set how long to wait for new events
fn set_event_timeout(&mut self, timeout: Duration);
/// Getter for the event poll timeout
@@ -50,7 +51,7 @@ pub trait SignerRunLoop {
/// Returns None to keep running.
fn run_one_pass(
&mut self,
- event: Option,
+ event: Option>,
cmd: Option,
res: Sender,
) -> Option;
@@ -64,7 +65,7 @@ pub trait SignerRunLoop {
/// This would run in a separate thread from the event receiver.
fn main_loop(
&mut self,
- event_recv: Receiver,
+ event_recv: Receiver>,
command_recv: Receiver,
result_send: Sender,
mut event_stop_signaler: EVST,
@@ -93,7 +94,7 @@ pub trait SignerRunLoop {
}
/// The top-level signer implementation
-pub struct Signer {
+pub struct Signer {
/// the runloop itself
signer_loop: Option,
/// the event receiver to use
@@ -102,10 +103,12 @@ pub struct Signer {
command_receiver: Option>,
/// the result sender to use
result_sender: Option>,
+ /// phantom data for the codec
+ phantom_data: PhantomData,
}
/// The running signer implementation
-pub struct RunningSigner {
+pub struct RunningSigner, R, T: SignerEventTrait> {
/// join handle for signer runloop
signer_join: JoinHandle>,
/// join handle for event receiver
@@ -114,7 +117,7 @@ pub struct RunningSigner {
stop_signal: EV::ST,
}
-impl RunningSigner {
+impl, R, T: SignerEventTrait> RunningSigner {
/// Stop the signer, and get the final state
pub fn stop(mut self) -> Option {
// kill event receiver
@@ -189,19 +192,20 @@ pub fn set_runloop_signal_handler(mut st
}).expect("FATAL: failed to set signal handler");
}
-impl Signer {
+impl Signer {
/// Create a new signer with the given runloop and event receiver.
pub fn new(
runloop: SL,
event_receiver: EV,
command_receiver: Receiver,
result_sender: Sender,
- ) -> Signer {
+ ) -> Signer {
Signer {
signer_loop: Some(runloop),
event_receiver: Some(event_receiver),
command_receiver: Some(command_receiver),
result_sender: Some(result_sender),
+ phantom_data: PhantomData,
}
}
}
@@ -209,9 +213,10 @@ impl Signer {
impl<
CMD: Send + 'static,
R: Send + 'static,
- SL: SignerRunLoop + Send + 'static,
- EV: EventReceiver + Send + 'static,
- > Signer
+ T: SignerEventTrait + 'static,
+ SL: SignerRunLoop + Send + 'static,
+ EV: EventReceiver + Send + 'static,
+ > Signer
{
/// 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
@@ -223,7 +228,7 @@ impl<
///
/// On success, this method consumes the Signer and returns a RunningSigner with the relevant
/// inter-thread communication primitives for the caller to shut down the system.
- pub fn spawn(&mut self, bind_addr: SocketAddr) -> Result, EventError> {
+ pub fn spawn(&mut self, bind_addr: SocketAddr) -> Result, EventError> {
let mut event_receiver = self
.event_receiver
.take()
diff --git a/libsigner/src/session.rs b/libsigner/src/session.rs
index 307801695..c13621392 100644
--- a/libsigner/src/session.rs
+++ b/libsigner/src/session.rs
@@ -1,5 +1,5 @@
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
-// Copyright (C) 2020-2023 Stacks Open Internet Foundation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/libsigner/src/signer_set.rs b/libsigner/src/signer_set.rs
index 119873fd1..fdcb857fa 100644
--- a/libsigner/src/signer_set.rs
+++ b/libsigner/src/signer_set.rs
@@ -86,7 +86,7 @@ impl SignerEntries {
weight_end = weight_start + entry.weight;
let key_ids: HashSet = (weight_start..weight_end).collect();
for key_id in key_ids.iter() {
- wsts_key_ids.insert(*key_id, ecdsa_pk.clone());
+ wsts_key_ids.insert(*key_id, ecdsa_pk);
}
signer_key_ids.insert(signer_id, (weight_start..weight_end).collect());
coordinator_key_ids.insert(signer_id, key_ids);
diff --git a/libsigner/src/tests/http.rs b/libsigner/src/tests/http.rs
index d2b052fae..d0f3887b4 100644
--- a/libsigner/src/tests/http.rs
+++ b/libsigner/src/tests/http.rs
@@ -264,7 +264,7 @@ fn test_run_http_request_with_body() {
let result_chunked = run_http_request(
&mut msock_chunked,
- &"127.0.0.1:20443",
+ "127.0.0.1:20443",
verb,
path,
content_type,
@@ -275,7 +275,7 @@ fn test_run_http_request_with_body() {
let result_plain = run_http_request(
&mut msock_plain,
- &"127.0.0.1:20443",
+ "127.0.0.1:20443",
verb,
path,
content_type,
@@ -321,7 +321,7 @@ fn test_run_http_request_no_body() {
let result_chunked = run_http_request(
&mut msock_chunked,
- &"127.0.0.1:20443",
+ "127.0.0.1:20443",
verb,
path,
content_type,
@@ -330,7 +330,7 @@ fn test_run_http_request_no_body() {
.unwrap();
let result_plain = run_http_request(
&mut msock_plain,
- &"127.0.0.1:20443",
+ "127.0.0.1:20443",
verb,
path,
content_type,
diff --git a/libsigner/src/tests/mod.rs b/libsigner/src/tests/mod.rs
index 1d3e1f3cc..c584572ba 100644
--- a/libsigner/src/tests/mod.rs
+++ b/libsigner/src/tests/mod.rs
@@ -16,6 +16,7 @@
mod http;
+use std::fmt::Debug;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::sync::mpsc::{channel, Receiver, Sender};
@@ -36,20 +37,20 @@ use stacks_common::util::secp256k1::Secp256k1PrivateKey;
use stacks_common::util::sleep_ms;
use wsts::net::{DkgBegin, Packet};
-use crate::events::SignerEvent;
-use crate::messages::SignerMessage;
+use crate::events::{SignerEvent, SignerEventTrait};
+use crate::v1::messages::SignerMessage;
use crate::{Signer, SignerEventReceiver, SignerRunLoop};
/// Simple runloop implementation. It receives `max_events` events and returns `events` from the
/// last call to `run_one_pass` as its final state.
-struct SimpleRunLoop {
+struct SimpleRunLoop {
poll_timeout: Duration,
- events: Vec,
+ events: Vec>,
max_events: usize,
}
-impl SimpleRunLoop {
- pub fn new(max_events: usize) -> SimpleRunLoop {
+impl SimpleRunLoop {
+ pub fn new(max_events: usize) -> SimpleRunLoop {
SimpleRunLoop {
poll_timeout: Duration::from_millis(100),
events: vec![],
@@ -62,7 +63,7 @@ enum Command {
Empty,
}
-impl SignerRunLoop, Command> for SimpleRunLoop {
+impl SignerRunLoop>, Command, T> for SimpleRunLoop {
fn set_event_timeout(&mut self, timeout: Duration) {
self.poll_timeout = timeout;
}
@@ -73,10 +74,10 @@ impl SignerRunLoop, Command> for SimpleRunLoop {
fn run_one_pass(
&mut self,
- event: Option,
+ event: Option>,
_cmd: Option,
- _res: Sender>,
- ) -> Option> {
+ _res: Sender>>,
+ ) -> Option>> {
debug!("Got event: {:?}", &event);
if let Some(event) = event {
self.events.push(event);
@@ -161,7 +162,7 @@ fn test_simple_signer() {
.unwrap()
});
- let sent_events: Vec = chunks
+ let sent_events: Vec> = chunks
.iter()
.map(|chunk| {
let msg = chunk.modified_slots[0].data.clone();
@@ -211,7 +212,7 @@ fn test_status_endpoint() {
sleep_ms(3000);
let accepted_events = running_signer.stop().unwrap();
- let sent_events: Vec = vec![SignerEvent::StatusCheck];
+ let sent_events: Vec> = vec![SignerEvent::StatusCheck];
assert_eq!(sent_events, accepted_events);
mock_stacks_node.join().unwrap();
diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs
new file mode 100644
index 000000000..4b9cd74dc
--- /dev/null
+++ b/libsigner/src/v0/messages.rs
@@ -0,0 +1,572 @@
+// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+//! Messages in the signer-miner interaction have a multi-level hierarchy.
+//! Signers send messages to each other through Packet messages. These messages,
+//! as well as `BlockResponse`, `Transactions`, and `DkgResults` messages are stored
+//! StackerDBs based on the `MessageSlotID` for the particular message type. This is a
+//! shared identifier space between the four message kinds and their subtypes.
+//!
+//! These four message kinds are differentiated with a `SignerMessageTypePrefix`
+//! and the `SignerMessage` enum.
+
+use std::fmt::{Debug, Display};
+use std::io::{Read, Write};
+use std::net::{SocketAddr, TcpListener, TcpStream};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::mpsc::Sender;
+use std::sync::Arc;
+
+use blockstack_lib::chainstate::nakamoto::signer_set::NakamotoSigners;
+use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
+use blockstack_lib::chainstate::stacks::events::StackerDBChunksEvent;
+use blockstack_lib::chainstate::stacks::StacksTransaction;
+use blockstack_lib::net::api::postblock_proposal::{
+ BlockValidateReject, BlockValidateResponse, ValidateRejectCode,
+};
+use blockstack_lib::util_lib::boot::boot_code_id;
+use clarity::util::retry::BoundReader;
+use clarity::util::secp256k1::MessageSignature;
+use clarity::vm::types::serialization::SerializationError;
+use clarity::vm::types::QualifiedContractIdentifier;
+use hashbrown::{HashMap, HashSet};
+use serde::{Deserialize, Serialize};
+use stacks_common::codec::{
+ read_next, read_next_at_most, read_next_exact, write_next, Error as CodecError,
+ StacksMessageCodec,
+};
+use stacks_common::consts::SIGNER_SLOTS_PER_USER;
+use stacks_common::util::hash::Sha512Trunc256Sum;
+use tiny_http::{
+ Method as HttpMethod, Request as HttpRequest, Response as HttpResponse, Server as HttpServer,
+};
+
+use crate::http::{decode_http_body, decode_http_request};
+use crate::{BlockProposal, EventError};
+
+define_u8_enum!(
+/// Enum representing the stackerdb message identifier: this is
+/// the contract index in the signers contracts (i.e., X in signers-0-X)
+MessageSlotID {
+ /// Block Proposal message from miners
+ BlockProposal = 0,
+ /// Block Response message from signers
+ BlockResponse = 1
+});
+
+define_u8_enum!(
+/// Enum representing the SignerMessage type prefix
+SignerMessageTypePrefix {
+ /// Block Proposal message from miners
+ BlockProposal = 0,
+ /// Block Response message from signers
+ BlockResponse = 1
+});
+
+#[cfg_attr(test, mutants::skip)]
+impl MessageSlotID {
+ /// Return the StackerDB contract corresponding to messages of this type
+ pub fn stacker_db_contract(
+ &self,
+ mainnet: bool,
+ reward_cycle: u64,
+ ) -> QualifiedContractIdentifier {
+ NakamotoSigners::make_signers_db_contract_id(reward_cycle, self.to_u32(), mainnet)
+ }
+
+ /// Return the u32 identifier for the message slot (used to index the contract that stores it)
+ pub fn to_u32(self) -> u32 {
+ self.to_u8().into()
+ }
+}
+
+#[cfg_attr(test, mutants::skip)]
+impl Display for MessageSlotID {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}({})", self, self.to_u8())
+ }
+}
+
+impl TryFrom for SignerMessageTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown signer message type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&SignerMessage> for SignerMessageTypePrefix {
+ #[cfg_attr(test, mutants::skip)]
+ fn from(message: &SignerMessage) -> Self {
+ match message {
+ SignerMessage::BlockProposal(_) => SignerMessageTypePrefix::BlockProposal,
+ SignerMessage::BlockResponse(_) => SignerMessageTypePrefix::BlockResponse,
+ }
+ }
+}
+
+/// The messages being sent through the stacker db contracts
+#[derive(Clone, PartialEq, Serialize, Deserialize)]
+pub enum SignerMessage {
+ /// The block proposal from miners for signers to observe and sign
+ BlockProposal(BlockProposal),
+ /// The block response from signers for miners to observe
+ BlockResponse(BlockResponse),
+}
+
+impl Debug for SignerMessage {
+ #[cfg_attr(test, mutants::skip)]
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::BlockProposal(b) => Debug::fmt(b, f),
+ Self::BlockResponse(b) => Debug::fmt(b, f),
+ }
+ }
+}
+
+impl SignerMessage {
+ /// Helper function to determine the slot ID for the provided stacker-db writer id
+ #[cfg_attr(test, mutants::skip)]
+ pub fn msg_id(&self) -> MessageSlotID {
+ match self {
+ Self::BlockProposal(_) => MessageSlotID::BlockProposal,
+ Self::BlockResponse(_) => MessageSlotID::BlockResponse,
+ }
+ }
+}
+
+impl StacksMessageCodec for SignerMessage {
+ fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &(SignerMessageTypePrefix::from(self) as u8))?;
+ match self {
+ SignerMessage::BlockProposal(block_proposal) => {
+ write_next(fd, block_proposal)?;
+ }
+ SignerMessage::BlockResponse(block_response) => {
+ write_next(fd, block_response)?;
+ }
+ };
+ Ok(())
+ }
+
+ #[cfg_attr(test, mutants::skip)]
+ fn consensus_deserialize(fd: &mut R) -> Result {
+ let type_prefix_byte = read_next::(fd)?;
+ let type_prefix = SignerMessageTypePrefix::try_from(type_prefix_byte)?;
+ let message = match type_prefix {
+ SignerMessageTypePrefix::BlockProposal => {
+ let block_proposal = read_next::(fd)?;
+ SignerMessage::BlockProposal(block_proposal)
+ }
+ SignerMessageTypePrefix::BlockResponse => {
+ let block_response = read_next::(fd)?;
+ SignerMessage::BlockResponse(block_response)
+ }
+ };
+ Ok(message)
+ }
+}
+
+/// Work around for the fact that a lot of the structs being desierialized are not defined in messages.rs
+pub trait StacksMessageCodecExtensions: Sized {
+ /// Serialize the struct to the provided writer
+ fn inner_consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError>;
+ /// Deserialize the struct from the provided reader
+ fn inner_consensus_deserialize(fd: &mut R) -> Result;
+}
+
+define_u8_enum!(
+/// Enum representing the reject code type prefix
+RejectCodeTypePrefix {
+ /// The block was rejected due to validation issues
+ ValidationFailed = 0,
+ /// The block was rejected due to connectivity issues with the signer
+ ConnectivityIssues = 1
+});
+
+impl TryFrom for RejectCodeTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown reject code type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&RejectCode> for RejectCodeTypePrefix {
+ fn from(reject_code: &RejectCode) -> Self {
+ match reject_code {
+ RejectCode::ValidationFailed(_) => RejectCodeTypePrefix::ValidationFailed,
+ RejectCode::ConnectivityIssues => RejectCodeTypePrefix::ConnectivityIssues,
+ }
+ }
+}
+
+/// This enum is used to supply a `reason_code` for block rejections
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub enum RejectCode {
+ /// RPC endpoint Validation failed
+ ValidationFailed(ValidateRejectCode),
+ /// The block was rejected due to connectivity issues with the signer
+ ConnectivityIssues,
+}
+
+define_u8_enum!(
+/// Enum representing the BlockResponse type prefix
+BlockResponseTypePrefix {
+ /// An accepted block response
+ Accepted = 0,
+ /// A rejected block response
+ Rejected = 1
+});
+
+impl TryFrom for BlockResponseTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown block response type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&BlockResponse> for BlockResponseTypePrefix {
+ fn from(block_response: &BlockResponse) -> Self {
+ match block_response {
+ BlockResponse::Accepted(_) => BlockResponseTypePrefix::Accepted,
+ BlockResponse::Rejected(_) => BlockResponseTypePrefix::Rejected,
+ }
+ }
+}
+
+/// The response that a signer sends back to observing miners
+/// either accepting or rejecting a Nakamoto block with the corresponding reason
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum BlockResponse {
+ /// The Nakamoto block was accepted and therefore signed
+ Accepted((Sha512Trunc256Sum, MessageSignature)),
+ /// The Nakamoto block was rejected and therefore not signed
+ Rejected(BlockRejection),
+}
+
+#[cfg_attr(test, mutants::skip)]
+impl std::fmt::Display for BlockResponse {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ BlockResponse::Accepted(a) => {
+ write!(
+ f,
+ "BlockAccepted: signer_sighash = {}, signature = {}",
+ a.0, a.1
+ )
+ }
+ BlockResponse::Rejected(r) => {
+ write!(
+ f,
+ "BlockRejected: signer_sighash = {}, code = {}, reason = {}",
+ r.reason_code, r.reason, r.signer_signature_hash
+ )
+ }
+ }
+ }
+}
+
+impl BlockResponse {
+ /// Create a new accepted BlockResponse for the provided block signer signature hash and signature
+ pub fn accepted(hash: Sha512Trunc256Sum, sig: MessageSignature) -> Self {
+ Self::Accepted((hash, sig))
+ }
+
+ /// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code
+ pub fn rejected(hash: Sha512Trunc256Sum, reject_code: RejectCode) -> Self {
+ Self::Rejected(BlockRejection::new(hash, reject_code))
+ }
+}
+
+impl StacksMessageCodec for BlockResponse {
+ fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &(BlockResponseTypePrefix::from(self) as u8))?;
+ match self {
+ BlockResponse::Accepted((hash, sig)) => {
+ write_next(fd, hash)?;
+ write_next(fd, sig)?;
+ }
+ BlockResponse::Rejected(rejection) => {
+ write_next(fd, rejection)?;
+ }
+ };
+ Ok(())
+ }
+
+ fn consensus_deserialize(fd: &mut R) -> Result {
+ let type_prefix_byte = read_next::(fd)?;
+ let type_prefix = BlockResponseTypePrefix::try_from(type_prefix_byte)?;
+ let response = match type_prefix {
+ BlockResponseTypePrefix::Accepted => {
+ let hash = read_next::(fd)?;
+ let sig = read_next::(fd)?;
+ BlockResponse::Accepted((hash, sig))
+ }
+ BlockResponseTypePrefix::Rejected => {
+ let rejection = read_next::(fd)?;
+ BlockResponse::Rejected(rejection)
+ }
+ };
+ Ok(response)
+ }
+}
+
+/// A rejection response from a signer for a proposed block
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct BlockRejection {
+ /// The reason for the rejection
+ pub reason: String,
+ /// The reason code for the rejection
+ pub reason_code: RejectCode,
+ /// The signer signature hash of the block that was rejected
+ pub signer_signature_hash: Sha512Trunc256Sum,
+}
+
+impl BlockRejection {
+ /// Create a new BlockRejection for the provided block and reason code
+ pub fn new(signer_signature_hash: Sha512Trunc256Sum, reason_code: RejectCode) -> Self {
+ Self {
+ reason: reason_code.to_string(),
+ reason_code,
+ signer_signature_hash,
+ }
+ }
+}
+
+impl StacksMessageCodec for BlockRejection {
+ fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &self.reason.as_bytes().to_vec())?;
+ write_next(fd, &self.reason_code)?;
+ write_next(fd, &self.signer_signature_hash)?;
+ Ok(())
+ }
+
+ fn consensus_deserialize(fd: &mut R) -> Result {
+ let reason_bytes = read_next::, _>(fd)?;
+ let reason = String::from_utf8(reason_bytes).map_err(|e| {
+ CodecError::DeserializeError(format!("Failed to decode reason string: {:?}", &e))
+ })?;
+ let reason_code = read_next::(fd)?;
+ let signer_signature_hash = read_next::(fd)?;
+ Ok(Self {
+ reason,
+ reason_code,
+ signer_signature_hash,
+ })
+ }
+}
+
+impl From for BlockRejection {
+ fn from(reject: BlockValidateReject) -> Self {
+ Self {
+ reason: reject.reason,
+ reason_code: RejectCode::ValidationFailed(reject.reason_code),
+ signer_signature_hash: reject.signer_signature_hash,
+ }
+ }
+}
+
+impl StacksMessageCodec for RejectCode {
+ fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &(RejectCodeTypePrefix::from(self) as u8))?;
+ // Do not do a single match here as we may add other variants in the future and don't want to miss adding it
+ match self {
+ RejectCode::ValidationFailed(code) => write_next(fd, &(*code as u8))?,
+ RejectCode::ConnectivityIssues => {
+ // No additional data to serialize / deserialize
+ }
+ };
+ Ok(())
+ }
+
+ fn consensus_deserialize(fd: &mut R) -> Result {
+ let type_prefix_byte = read_next::(fd)?;
+ let type_prefix = RejectCodeTypePrefix::try_from(type_prefix_byte)?;
+ let code = match type_prefix {
+ RejectCodeTypePrefix::ValidationFailed => RejectCode::ValidationFailed(
+ ValidateRejectCode::try_from(read_next::(fd)?).map_err(|e| {
+ CodecError::DeserializeError(format!(
+ "Failed to decode validation reject code: {:?}",
+ &e
+ ))
+ })?,
+ ),
+ RejectCodeTypePrefix::ConnectivityIssues => RejectCode::ConnectivityIssues,
+ };
+ Ok(code)
+ }
+}
+
+#[cfg_attr(test, mutants::skip)]
+impl std::fmt::Display for RejectCode {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ RejectCode::ValidationFailed(code) => write!(f, "Validation failed: {:?}", code),
+ RejectCode::ConnectivityIssues => write!(
+ f,
+ "The block was rejected due to connectivity issues with the signer."
+ ),
+ }
+ }
+}
+
+impl From for SignerMessage {
+ fn from(block_response: BlockResponse) -> Self {
+ Self::BlockResponse(block_response)
+ }
+}
+
+impl From for SignerMessage {
+ fn from(block_rejection: BlockRejection) -> Self {
+ Self::BlockResponse(BlockResponse::Rejected(block_rejection))
+ }
+}
+
+impl From for SignerMessage {
+ fn from(rejection: BlockValidateReject) -> Self {
+ Self::BlockResponse(BlockResponse::Rejected(rejection.into()))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use blockstack_lib::chainstate::nakamoto::NakamotoBlockHeader;
+ use blockstack_lib::chainstate::stacks::{
+ ThresholdSignature, TransactionAnchorMode, TransactionAuth, TransactionPayload,
+ TransactionPostConditionMode, TransactionSmartContract, TransactionVersion,
+ };
+ use blockstack_lib::util_lib::strings::StacksString;
+ use clarity::types::chainstate::{ConsensusHash, StacksBlockId, TrieHash};
+ use clarity::util::hash::MerkleTree;
+ use clarity::util::secp256k1::MessageSignature;
+ use rand::{thread_rng, Rng, RngCore};
+ use rand_core::OsRng;
+ use stacks_common::bitvec::BitVec;
+ use stacks_common::consts::CHAIN_ID_TESTNET;
+ use stacks_common::types::chainstate::StacksPrivateKey;
+
+ use super::{StacksMessageCodecExtensions, *};
+
+ #[test]
+ fn signer_slots_count_is_sane() {
+ let slot_identifiers_len = MessageSlotID::ALL.len();
+ assert!(
+ SIGNER_SLOTS_PER_USER as usize >= slot_identifiers_len,
+ "stacks_common::SIGNER_SLOTS_PER_USER ({}) must be >= slot identifiers ({})",
+ SIGNER_SLOTS_PER_USER,
+ slot_identifiers_len,
+ );
+ }
+
+ #[test]
+ fn serde_reject_code() {
+ let code = RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock);
+ let serialized_code = code.serialize_to_vec();
+ let deserialized_code = read_next::(&mut &serialized_code[..])
+ .expect("Failed to deserialize RejectCode");
+ assert_eq!(code, deserialized_code);
+
+ let code = RejectCode::ConnectivityIssues;
+ let serialized_code = code.serialize_to_vec();
+ let deserialized_code = read_next::(&mut &serialized_code[..])
+ .expect("Failed to deserialize RejectCode");
+ assert_eq!(code, deserialized_code);
+ }
+
+ #[test]
+ fn serde_block_rejection() {
+ let rejection = BlockRejection::new(
+ Sha512Trunc256Sum([0u8; 32]),
+ RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
+ );
+ let serialized_rejection = rejection.serialize_to_vec();
+ let deserialized_rejection = read_next::(&mut &serialized_rejection[..])
+ .expect("Failed to deserialize BlockRejection");
+ assert_eq!(rejection, deserialized_rejection);
+
+ let rejection =
+ BlockRejection::new(Sha512Trunc256Sum([1u8; 32]), RejectCode::ConnectivityIssues);
+ let serialized_rejection = rejection.serialize_to_vec();
+ let deserialized_rejection = read_next::(&mut &serialized_rejection[..])
+ .expect("Failed to deserialize BlockRejection");
+ assert_eq!(rejection, deserialized_rejection);
+ }
+
+ #[test]
+ fn serde_block_response() {
+ let response =
+ BlockResponse::Accepted((Sha512Trunc256Sum([0u8; 32]), MessageSignature::empty()));
+ let serialized_response = response.serialize_to_vec();
+ let deserialized_response = read_next::(&mut &serialized_response[..])
+ .expect("Failed to deserialize BlockResponse");
+ assert_eq!(response, deserialized_response);
+
+ let response = BlockResponse::Rejected(BlockRejection::new(
+ Sha512Trunc256Sum([1u8; 32]),
+ RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
+ ));
+ let serialized_response = response.serialize_to_vec();
+ let deserialized_response = read_next::(&mut &serialized_response[..])
+ .expect("Failed to deserialize BlockResponse");
+ assert_eq!(response, deserialized_response);
+ }
+
+ #[test]
+ fn serde_signer_message() {
+ let signer_message = SignerMessage::BlockResponse(BlockResponse::Accepted((
+ Sha512Trunc256Sum([2u8; 32]),
+ MessageSignature::empty(),
+ )));
+ let serialized_signer_message = signer_message.serialize_to_vec();
+ let deserialized_signer_message =
+ read_next::(&mut &serialized_signer_message[..])
+ .expect("Failed to deserialize SignerMessage");
+ assert_eq!(signer_message, deserialized_signer_message);
+
+ let header = NakamotoBlockHeader::empty();
+ let mut block = NakamotoBlock {
+ header,
+ txs: vec![],
+ };
+ let tx_merkle_root = {
+ let txid_vecs = block
+ .txs
+ .iter()
+ .map(|tx| tx.txid().as_bytes().to_vec())
+ .collect();
+
+ MerkleTree::::new(&txid_vecs).root()
+ };
+ block.header.tx_merkle_root = tx_merkle_root;
+
+ let block_proposal = BlockProposal {
+ block,
+ burn_height: thread_rng().next_u64(),
+ reward_cycle: thread_rng().next_u64(),
+ };
+ let signer_message = SignerMessage::BlockProposal(block_proposal);
+ let serialized_signer_message = signer_message.serialize_to_vec();
+ let deserialized_signer_message =
+ read_next::(&mut &serialized_signer_message[..])
+ .expect("Failed to deserialize SignerMessage");
+ assert_eq!(signer_message, deserialized_signer_message);
+ }
+}
diff --git a/libsigner/src/v0/mod.rs b/libsigner/src/v0/mod.rs
new file mode 100644
index 000000000..703acb85f
--- /dev/null
+++ b/libsigner/src/v0/mod.rs
@@ -0,0 +1,17 @@
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/// Messages for the v0 signer
+pub mod messages;
diff --git a/libsigner/src/messages.rs b/libsigner/src/v1/messages.rs
similarity index 92%
rename from libsigner/src/messages.rs
rename to libsigner/src/v1/messages.rs
index 388207a77..a84ba98c6 100644
--- a/libsigner/src/messages.rs
+++ b/libsigner/src/v1/messages.rs
@@ -100,14 +100,22 @@ MessageSlotID {
EncryptedSignerState = 13
});
-define_u8_enum!(SignerMessageTypePrefix {
+define_u8_enum!(
+/// Enum representing the signer message type prefix
+SignerMessageTypePrefix {
+ /// A block response message
BlockResponse = 0,
+ /// A wsts packet message
Packet = 1,
+ /// A list of transactions that a signer cares about
Transactions = 2,
+ /// The results of a successful DKG
DkgResults = 3,
+ /// The encrypted state of the signer to be persisted
EncryptedSignerState = 4
});
+#[cfg_attr(test, mutants::skip)]
impl MessageSlotID {
/// Return the StackerDB contract corresponding to messages of this type
pub fn stacker_db_contract(
@@ -152,16 +160,28 @@ impl From<&SignerMessage> for SignerMessageTypePrefix {
}
}
-define_u8_enum!(MessageTypePrefix {
+define_u8_enum!(
+/// Enum representing the message type prefix
+MessageTypePrefix {
+ /// DkgBegin message
DkgBegin = 0,
+ /// DkgPrivateBegin message
DkgPrivateBegin = 1,
+ /// DkgEndBegin message
DkgEndBegin = 2,
+ /// DkgEnd message
DkgEnd = 3,
+ /// DkgPublicShares message
DkgPublicShares = 4,
+ /// DkgPrivateShares message
DkgPrivateShares = 5,
+ /// NonceRequest message
NonceRequest = 6,
+ /// NonceResponse message
NonceResponse = 7,
+ /// SignatureShareRequest message
SignatureShareRequest = 8,
+ /// SignatureShareResponse message
SignatureShareResponse = 9
});
@@ -191,13 +211,22 @@ impl TryFrom for MessageTypePrefix {
}
}
-define_u8_enum!(RejectCodeTypePrefix{
+define_u8_enum!(
+/// Enum representing the reject code type prefix
+RejectCodeTypePrefix {
+ /// Validation failed
ValidationFailed = 0,
+ /// Signed rejection
SignedRejection = 1,
+ /// Insufficient signers
InsufficientSigners = 2,
+ /// Missing transactions
MissingTransactions = 3,
+ /// Connectivity issues
ConnectivityIssues = 4,
+ /// Nonce timeout
NonceTimeout = 5,
+ /// Aggregator error
AggregatorError = 6
});
@@ -417,7 +446,9 @@ impl StacksMessageCodec for SignerMessage {
/// Work around for the fact that a lot of the structs being desierialized are not defined in messages.rs
pub trait StacksMessageCodecExtensions: Sized {
+ /// Serialize the struct to the provided writer
fn inner_consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError>;
+ /// Deserialize the struct from the provided reader
fn inner_consensus_deserialize(fd: &mut R) -> Result;
}
@@ -537,50 +568,87 @@ impl StacksMessageCodecExtensions for HashSet {
}
}
+define_u8_enum!(
+/// Enum representing the DKG failure type prefix
+DkgFailureTypePrefix {
+ /// Bad state
+ BadState = 0,
+ /// Missing public shares
+ MissingPublicShares = 1,
+ /// Bad public shares
+ BadPublicShares = 2,
+ /// Missing private shares
+ MissingPrivateShares = 3,
+ /// Bad private shares
+ BadPrivateShares = 4
+});
+
+impl TryFrom for DkgFailureTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown DKG failure type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&DkgFailure> for DkgFailureTypePrefix {
+ fn from(failure: &DkgFailure) -> Self {
+ match failure {
+ DkgFailure::BadState => DkgFailureTypePrefix::BadState,
+ DkgFailure::MissingPublicShares(_) => DkgFailureTypePrefix::MissingPublicShares,
+ DkgFailure::BadPublicShares(_) => DkgFailureTypePrefix::BadPublicShares,
+ DkgFailure::MissingPrivateShares(_) => DkgFailureTypePrefix::MissingPrivateShares,
+ DkgFailure::BadPrivateShares(_) => DkgFailureTypePrefix::BadPrivateShares,
+ }
+ }
+}
+
impl StacksMessageCodecExtensions for DkgFailure {
fn inner_consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &(DkgFailureTypePrefix::from(self) as u8))?;
match self {
- DkgFailure::BadState => write_next(fd, &0u8),
+ DkgFailure::BadState => {
+ // No additional data to serialize
+ }
DkgFailure::MissingPublicShares(shares) => {
- write_next(fd, &1u8)?;
- shares.inner_consensus_serialize(fd)
+ shares.inner_consensus_serialize(fd)?;
}
DkgFailure::BadPublicShares(shares) => {
- write_next(fd, &2u8)?;
- shares.inner_consensus_serialize(fd)
+ shares.inner_consensus_serialize(fd)?;
}
DkgFailure::MissingPrivateShares(shares) => {
- write_next(fd, &3u8)?;
- shares.inner_consensus_serialize(fd)
+ shares.inner_consensus_serialize(fd)?;
}
DkgFailure::BadPrivateShares(shares) => {
- write_next(fd, &4u8)?;
write_next(fd, &(shares.len() as u32))?;
for (id, share) in shares {
write_next(fd, id)?;
share.inner_consensus_serialize(fd)?;
}
- Ok(())
}
}
+ Ok(())
}
+
fn inner_consensus_deserialize(fd: &mut R) -> Result {
- let failure_type_prefix = read_next::(fd)?;
+ let failure_type_prefix_byte = read_next::(fd)?;
+ let failure_type_prefix = DkgFailureTypePrefix::try_from(failure_type_prefix_byte)?;
let failure_type = match failure_type_prefix {
- 0 => DkgFailure::BadState,
- 1 => {
+ DkgFailureTypePrefix::BadState => DkgFailure::BadState,
+ DkgFailureTypePrefix::MissingPublicShares => {
let set = HashSet::::inner_consensus_deserialize(fd)?;
DkgFailure::MissingPublicShares(set)
}
- 2 => {
+ DkgFailureTypePrefix::BadPublicShares => {
let set = HashSet::::inner_consensus_deserialize(fd)?;
DkgFailure::BadPublicShares(set)
}
- 3 => {
+ DkgFailureTypePrefix::MissingPrivateShares => {
let set = HashSet::::inner_consensus_deserialize(fd)?;
DkgFailure::MissingPrivateShares(set)
}
- 4 => {
+ DkgFailureTypePrefix::BadPrivateShares => {
let mut map = HashMap::new();
let len = read_next::(fd)?;
for _ in 0..len {
@@ -590,12 +658,6 @@ impl StacksMessageCodecExtensions for DkgFailure {
}
DkgFailure::BadPrivateShares(map)
}
- _ => {
- return Err(CodecError::DeserializeError(format!(
- "Unknown DkgFailure type prefix: {}",
- failure_type_prefix
- )))
- }
};
Ok(failure_type)
}
@@ -647,34 +709,60 @@ impl StacksMessageCodecExtensions for DkgEndBegin {
}
}
+define_u8_enum!(
+/// Enum representing the DKG status type prefix
+DkgStatusTypePrefix {
+ /// Success
+ Success = 0,
+ /// Failure
+ Failure = 1
+});
+
+impl TryFrom for DkgStatusTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown DKG status type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&DkgStatus> for DkgStatusTypePrefix {
+ fn from(status: &DkgStatus) -> Self {
+ match status {
+ DkgStatus::Success => DkgStatusTypePrefix::Success,
+ DkgStatus::Failure(_) => DkgStatusTypePrefix::Failure,
+ }
+ }
+}
+
impl StacksMessageCodecExtensions for DkgEnd {
fn inner_consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
write_next(fd, &self.dkg_id)?;
write_next(fd, &self.signer_id)?;
+ write_next(fd, &(DkgStatusTypePrefix::from(&self.status) as u8))?;
match &self.status {
- DkgStatus::Success => write_next(fd, &0u8),
+ DkgStatus::Success => {
+ // No additional data to serialize
+ }
DkgStatus::Failure(failure) => {
- write_next(fd, &1u8)?;
- failure.inner_consensus_serialize(fd)
+ failure.inner_consensus_serialize(fd)?;
}
}
+ Ok(())
}
+
fn inner_consensus_deserialize(fd: &mut R) -> Result {
let dkg_id = read_next::(fd)?;
let signer_id = read_next::(fd)?;
- let status_type_prefix = read_next::(fd)?;
+ let status_type_prefix_byte = read_next::(fd)?;
+ let status_type_prefix = DkgStatusTypePrefix::try_from(status_type_prefix_byte)?;
let status = match status_type_prefix {
- 0 => DkgStatus::Success,
- 1 => {
+ DkgStatusTypePrefix::Success => DkgStatus::Success,
+ DkgStatusTypePrefix::Failure => {
let failure = DkgFailure::inner_consensus_deserialize(fd)?;
DkgStatus::Failure(failure)
}
- _ => {
- return Err(CodecError::DeserializeError(format!(
- "Unknown DKG status type prefix: {}",
- status_type_prefix
- )))
- }
};
Ok(DkgEnd {
dkg_id,
@@ -1035,6 +1123,33 @@ impl StacksMessageCodecExtensions for Packet {
}
}
+define_u8_enum!(
+/// Enum representing the block response type prefix
+BlockResponseTypePrefix {
+ /// Accepted
+ Accepted = 0,
+ /// Rejected
+ Rejected = 1
+});
+
+impl TryFrom for BlockResponseTypePrefix {
+ type Error = CodecError;
+ fn try_from(value: u8) -> Result {
+ Self::from_u8(value).ok_or_else(|| {
+ CodecError::DeserializeError(format!("Unknown block response type prefix: {value}"))
+ })
+ }
+}
+
+impl From<&BlockResponse> for BlockResponseTypePrefix {
+ fn from(block_response: &BlockResponse) -> Self {
+ match block_response {
+ BlockResponse::Accepted(_) => BlockResponseTypePrefix::Accepted,
+ BlockResponse::Rejected(_) => BlockResponseTypePrefix::Rejected,
+ }
+ }
+}
+
/// The response that a signer sends back to observing miners
/// either accepting or rejecting a Nakamoto block with the corresponding reason
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -1083,14 +1198,13 @@ impl BlockResponse {
impl StacksMessageCodec for BlockResponse {
fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> {
+ write_next(fd, &(BlockResponseTypePrefix::from(self) as u8))?;
match self {
BlockResponse::Accepted((hash, sig)) => {
- write_next(fd, &0u8)?;
write_next(fd, hash)?;
write_next(fd, sig)?;
}
BlockResponse::Rejected(rejection) => {
- write_next(fd, &1u8)?;
write_next(fd, rejection)?;
}
};
@@ -1098,27 +1212,23 @@ impl StacksMessageCodec for BlockResponse {
}
fn consensus_deserialize(fd: &mut R) -> Result {
- let type_prefix = read_next::(fd)?;
+ let type_prefix_byte = read_next::(fd)?;
+ let type_prefix = BlockResponseTypePrefix::try_from(type_prefix_byte)?;
let response = match type_prefix {
- 0 => {
+ BlockResponseTypePrefix::Accepted => {
let hash = read_next::(fd)?;
let sig = read_next::(fd)?;
BlockResponse::Accepted((hash, sig))
}
- 1 => {
+ BlockResponseTypePrefix::Rejected => {
let rejection = read_next::(fd)?;
BlockResponse::Rejected(rejection)
}
- _ => {
- return Err(CodecError::DeserializeError(format!(
- "Unknown block response type prefix: {}",
- type_prefix
- )))
- }
};
Ok(response)
}
}
+
/// A rejection response from a signer for a proposed block
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BlockRejection {
diff --git a/libsigner/src/v1/mod.rs b/libsigner/src/v1/mod.rs
new file mode 100644
index 000000000..e5a691efb
--- /dev/null
+++ b/libsigner/src/v1/mod.rs
@@ -0,0 +1,17 @@
+// Copyright (C) 2020-2024 Stacks Open Internet Foundation
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+/// Messages for the v1 signer
+pub mod messages;
diff --git a/stacks-signer/src/client/stackerdb.rs b/stacks-signer/src/client/stackerdb.rs
index e1edf3aec..f23679b09 100644
--- a/stacks-signer/src/client/stackerdb.rs
+++ b/stacks-signer/src/client/stackerdb.rs
@@ -17,7 +17,8 @@
use blockstack_lib::chainstate::stacks::StacksTransaction;
use blockstack_lib::net::api::poststackerdbchunk::StackerDBErrorCodes;
use hashbrown::HashMap;
-use libsigner::{MessageSlotID, SignerMessage, SignerSession, StackerDBSession};
+use libsigner::v1::messages::{MessageSlotID, SignerMessage};
+use libsigner::{SignerSession, StackerDBSession};
use libstackerdb::{StackerDBChunkAckData, StackerDBChunkData};
use slog::{slog_debug, slog_error, slog_warn};
use stacks_common::codec::{read_next, StacksMessageCodec};
diff --git a/stacks-signer/src/client/stacks_client.rs b/stacks-signer/src/client/stacks_client.rs
index 9cef3da9a..6f07bb362 100644
--- a/stacks-signer/src/client/stacks_client.rs
+++ b/stacks-signer/src/client/stacks_client.rs
@@ -732,18 +732,13 @@ mod tests {
use blockstack_lib::chainstate::stacks::boot::{
NakamotoSignerEntry, PoxStartCycleInfo, RewardSet,
};
- use blockstack_lib::chainstate::stacks::ThresholdSignature;
use clarity::vm::types::{
ListData, ListTypeData, ResponseData, SequenceData, TupleData, TupleTypeSignature,
TypeSignature,
};
use rand::thread_rng;
use rand_core::RngCore;
- use stacks_common::bitvec::BitVec;
use stacks_common::consts::{CHAIN_ID_TESTNET, SIGNER_SLOTS_PER_USER};
- use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId, TrieHash};
- use stacks_common::util::hash::Sha512Trunc256Sum;
- use stacks_common::util::secp256k1::MessageSignature;
use wsts::curve::scalar::Scalar;
use super::*;
@@ -1229,18 +1224,7 @@ mod tests {
#[test]
fn submit_block_for_validation_should_succeed() {
let mock = MockServerClient::new();
- let header = NakamotoBlockHeader {
- version: 1,
- chain_length: 2,
- burn_spent: 3,
- consensus_hash: ConsensusHash([0x04; 20]),
- parent_block_id: StacksBlockId([0x05; 32]),
- tx_merkle_root: Sha512Trunc256Sum([0x06; 32]),
- state_index_root: TrieHash([0x07; 32]),
- miner_signature: MessageSignature::empty(),
- signer_signature: ThresholdSignature::empty(),
- signer_bitvec: BitVec::zeros(1).unwrap(),
- };
+ let header = NakamotoBlockHeader::empty();
let block = NakamotoBlock {
header,
txs: vec![],
@@ -1253,18 +1237,7 @@ mod tests {
#[test]
fn submit_block_for_validation_should_fail() {
let mock = MockServerClient::new();
- let header = NakamotoBlockHeader {
- version: 1,
- chain_length: 2,
- burn_spent: 3,
- consensus_hash: ConsensusHash([0x04; 20]),
- parent_block_id: StacksBlockId([0x05; 32]),
- tx_merkle_root: Sha512Trunc256Sum([0x06; 32]),
- state_index_root: TrieHash([0x07; 32]),
- miner_signature: MessageSignature::empty(),
- signer_signature: ThresholdSignature::empty(),
- signer_bitvec: BitVec::zeros(1).unwrap(),
- };
+ let header = NakamotoBlockHeader::empty();
let block = NakamotoBlock {
header,
txs: vec![],
diff --git a/stacks-signer/src/lib.rs b/stacks-signer/src/lib.rs
index 2a9e15675..0e8a6b10b 100644
--- a/stacks-signer/src/lib.rs
+++ b/stacks-signer/src/lib.rs
@@ -34,11 +34,10 @@ pub mod runloop;
pub mod v0;
/// The v1 implementation of the singer. This includes WSTS support
pub mod v1;
-
use std::fmt::{Debug, Display};
use std::sync::mpsc::Sender;
-use libsigner::SignerEvent;
+use libsigner::{SignerEvent, SignerEventTrait};
use wsts::state_machine::OperationResult;
use crate::client::StacksClient;
@@ -46,7 +45,7 @@ use crate::config::SignerConfig;
use crate::runloop::RunLoopCommand;
/// A trait which provides a common `Signer` interface for `v1` and `v2`
-pub trait Signer: Debug + Display {
+pub trait Signer: Debug + Display {
/// Create a new `Signer` instance
fn new(config: SignerConfig) -> Self;
/// Update the `Signer` instance's next reward cycle data with the latest `SignerConfig`
@@ -57,7 +56,7 @@ pub trait Signer: Debug + Display {
fn process_event(
&mut self,
stacks_client: &StacksClient,
- event: Option<&SignerEvent>,
+ event: Option<&SignerEvent>,
res: Sender>,
current_reward_cycle: u64,
);
diff --git a/stacks-signer/src/main.rs b/stacks-signer/src/main.rs
index f7b5a24e7..75514fd2e 100644
--- a/stacks-signer/src/main.rs
+++ b/stacks-signer/src/main.rs
@@ -27,34 +27,24 @@ extern crate serde_json;
extern crate toml;
use std::io::{self, Write};
-use std::path::PathBuf;
-use std::sync::mpsc::{channel, Receiver, Sender};
use blockstack_lib::util_lib::signed_structured_data::pox4::make_pox_4_signer_key_signature;
use clap::Parser;
use clarity::vm::types::QualifiedContractIdentifier;
-use libsigner::{RunningSigner, Signer, SignerEventReceiver, SignerSession, StackerDBSession};
+use libsigner::{SignerSession, StackerDBSession};
use libstackerdb::StackerDBChunkData;
-use slog::{slog_debug, slog_info};
+use slog::slog_debug;
+use stacks_common::debug;
use stacks_common::util::hash::to_hex;
use stacks_common::util::secp256k1::{MessageSignature, Secp256k1PublicKey};
-use stacks_common::{debug, info};
use stacks_signer::cli::{
Cli, Command, GenerateStackingSignatureArgs, GetChunkArgs, GetLatestChunkArgs, PutChunkArgs,
RunSignerArgs, StackerDBArgs,
};
use stacks_signer::config::GlobalConfig;
-use stacks_signer::runloop::{RunLoop, RunLoopCommand};
use stacks_signer::v1;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
-use wsts::state_machine::OperationResult;
-
-struct SpawnedSigner {
- running_signer: RunningSigner>,
- _cmd_send: Sender,
- _res_recv: Receiver>,
-}
/// Create a new stacker db session
fn stackerdb_session(host: &str, contract: QualifiedContractIdentifier) -> StackerDBSession {
@@ -78,33 +68,6 @@ fn write_chunk_to_stdout(chunk_opt: Option>) {
}
}
-// Spawn a running signer and return its handle, command sender, and result receiver
-fn spawn_running_signer(path: &PathBuf) -> SpawnedSigner {
- let config = GlobalConfig::try_from(path).unwrap();
- let endpoint = config.endpoint;
- info!("Starting signer with config: {}", config);
- let (_cmd_send, cmd_recv) = channel();
- let (res_send, _res_recv) = channel();
- let ev = SignerEventReceiver::new(config.network.is_mainnet());
- #[cfg(feature = "monitoring_prom")]
- {
- stacks_signer::monitoring::start_serving_monitoring_metrics(config.clone()).ok();
- }
- let runloop = RunLoop::new(config);
- let mut signer: Signer<
- RunLoopCommand,
- Vec,
- RunLoop,
- SignerEventReceiver,
- > = Signer::new(runloop, ev, cmd_recv, res_send);
- let running_signer = signer.spawn(endpoint).unwrap();
- SpawnedSigner {
- running_signer,
- _cmd_send,
- _res_recv,
- }
-}
-
fn handle_get_chunk(args: GetChunkArgs) {
debug!("Getting chunk...");
let mut session = stackerdb_session(&args.db_args.host, args.db_args.contract);
@@ -139,10 +102,11 @@ fn handle_put_chunk(args: PutChunkArgs) {
fn handle_run(args: RunSignerArgs) {
debug!("Running signer...");
- let spawned_signer = spawn_running_signer(&args.config);
+ let config = GlobalConfig::try_from(&args.config).unwrap();
+ let spawned_signer = v1::SpawnedSigner::from(config);
println!("Signer spawned successfully. Waiting for messages to process...");
// Wait for the spawned signer to stop (will only occur if an error occurs)
- let _ = spawned_signer.running_signer.join();
+ let _ = spawned_signer.join();
}
fn handle_generate_stacking_signature(
diff --git a/stacks-signer/src/runloop.rs b/stacks-signer/src/runloop.rs
index e38e846cd..ad25b4fc4 100644
--- a/stacks-signer/src/runloop.rs
+++ b/stacks-signer/src/runloop.rs
@@ -1,4 +1,5 @@
use std::collections::VecDeque;
+use std::fmt::Debug;
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
//
@@ -20,8 +21,9 @@ use std::time::Duration;
use blockstack_lib::burnchains::PoxConstants;
use blockstack_lib::chainstate::stacks::boot::SIGNERS_NAME;
use blockstack_lib::util_lib::boot::boot_code_id;
+use clarity::codec::StacksMessageCodec;
use hashbrown::HashMap;
-use libsigner::{BlockProposalSigners, SignerEntries, SignerEvent, SignerRunLoop};
+use libsigner::{BlockProposal, SignerEntries, SignerEvent, SignerRunLoop};
use slog::{slog_debug, slog_error, slog_info, slog_warn};
use stacks_common::types::chainstate::StacksAddress;
use stacks_common::{debug, error, info, warn};
@@ -40,7 +42,7 @@ pub enum SignerCommand {
/// Sign a message
Sign {
/// The block to sign over
- block_proposal: BlockProposalSigners,
+ block_proposal: BlockProposal,
/// Whether to make a taproot signature
is_taproot: bool,
/// Taproot merkle root
@@ -118,7 +120,11 @@ impl RewardCycleInfo {
}
/// The runloop for the stacks signer
-pub struct RunLoop {
+pub struct RunLoop
+where
+ Signer: SignerTrait,
+ T: StacksMessageCodec + Clone + Send + Debug,
+{
/// Configuration info
pub config: GlobalConfig,
/// The stacks node client
@@ -132,9 +138,11 @@ pub struct RunLoop {
pub commands: VecDeque,
/// The current reward cycle info. Only None if the runloop is uninitialized
pub current_reward_cycle_info: Option,
+ /// Phantom data for the message codec
+ _phantom_data: std::marker::PhantomData,
}
-impl RunLoop {
+impl, T: StacksMessageCodec + Clone + Send + Debug> RunLoop {
/// Create a new signer runloop from the provided configuration
pub fn new(config: GlobalConfig) -> Self {
let stacks_client = StacksClient::from(&config);
@@ -145,6 +153,7 @@ impl RunLoop {
state: State::Uninitialized,
commands: VecDeque::new(),
current_reward_cycle_info: None,
+ _phantom_data: std::marker::PhantomData,
}
}
/// Get the registered signers for a specific reward cycle
@@ -357,7 +366,9 @@ impl RunLoop {
}
}
-impl SignerRunLoop, RunLoopCommand> for RunLoop {
+impl, T: StacksMessageCodec + Clone + Send + Debug>
+ SignerRunLoop, RunLoopCommand, T> for RunLoop
+{
fn set_event_timeout(&mut self, timeout: Duration) {
self.config.event_timeout = timeout;
}
@@ -368,7 +379,7 @@ impl SignerRunLoop, RunLoopCommand> fo
fn run_one_pass(
&mut self,
- event: Option,
+ event: Option>,
cmd: Option,
res: Sender>,
) -> Option> {
diff --git a/stacks-signer/src/v1/mod.rs b/stacks-signer/src/v1/mod.rs
index 88d5afb73..7c2477cf2 100644
--- a/stacks-signer/src/v1/mod.rs
+++ b/stacks-signer/src/v1/mod.rs
@@ -20,3 +20,72 @@ pub mod coordinator;
pub mod signer;
/// The state module for the signer
pub mod signerdb;
+
+use std::sync::mpsc::{channel, Receiver, Sender};
+
+use libsigner::v1::messages::SignerMessage;
+use libsigner::SignerEventReceiver;
+use slog::slog_info;
+use stacks_common::info;
+use wsts::state_machine::OperationResult;
+
+use crate::config::GlobalConfig;
+use crate::runloop::{RunLoop, RunLoopCommand};
+use crate::v1::signer::Signer;
+
+/// The signer type for the v1 signer
+pub type RunningSigner = libsigner::RunningSigner<
+ SignerEventReceiver,
+ Vec,
+ SignerMessage,
+>;
+
+/// The spawned signer type for the v1 signer
+pub struct SpawnedSigner {
+ /// The underlying running signer thread handle
+ running_signer: RunningSigner,
+ /// The command sender for interacting with the running signer
+ pub cmd_send: Sender