mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-01-12 22:43:42 +08:00
Merge pull request #4787 from stacks-network/chore/block-signature-message-type
Chore/block signature message type
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<T: StacksMessageCodec + Clone + Debug + Send = Self>:
|
||||
StacksMessageCodec + Clone + Debug + Send
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: StacksMessageCodec + Clone + Debug + Send> 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<SignerMessage>` will contain any signer WSTS messages made by the miner while acting as a coordinator.
|
||||
/// The `Option<StacksPublicKey>` will contain the message sender's public key if the vec is non-empty.
|
||||
MinerMessages(Vec<SignerMessage>, Option<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<SignerMessage>),
|
||||
/// 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<W: Write>(&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<T: SignerEventTrait> {
|
||||
/// A miner sent a message over .miners
|
||||
/// The `Vec<T>` will contain any signer messages made by the miner.
|
||||
/// The `StacksPublicKey` is the message sender's public key.
|
||||
MinerMessages(Vec<T>, 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<T>),
|
||||
/// 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<T: SignerEventTrait> {
|
||||
/// 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<SocketAddr, EventError>;
|
||||
/// Return the next event
|
||||
fn next_event(&mut self) -> Result<SignerEvent, EventError>;
|
||||
fn next_event(&mut self) -> Result<SignerEvent<T>, EventError>;
|
||||
/// Add a downstream event consumer
|
||||
fn add_consumer(&mut self, event_out: Sender<SignerEvent>);
|
||||
fn add_consumer(&mut self, event_out: Sender<SignerEvent<T>>);
|
||||
/// Forward the event to downstream consumers
|
||||
fn forward_event(&mut self, ev: SignerEvent) -> bool;
|
||||
fn forward_event(&mut self, ev: SignerEvent<T>) -> 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<T: SignerEventTrait> {
|
||||
/// Address we bind to
|
||||
local_addr: Option<SocketAddr>,
|
||||
/// server socket that listens for HTTP POSTs from the node
|
||||
http_server: Option<HttpServer>,
|
||||
/// channel into which to write newly-discovered data
|
||||
out_channels: Vec<Sender<SignerEvent>>,
|
||||
out_channels: Vec<Sender<SignerEvent<T>>>,
|
||||
/// inter-thread stop variable -- if set to true, then the `main_loop` will exit
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
/// Whether the receiver is running on mainnet
|
||||
is_mainnet: bool,
|
||||
}
|
||||
|
||||
impl SignerEventReceiver {
|
||||
impl<T: SignerEventTrait> SignerEventReceiver<T> {
|
||||
/// 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<T> {
|
||||
SignerEventReceiver {
|
||||
http_server: None,
|
||||
local_addr: None,
|
||||
@@ -193,7 +202,7 @@ impl SignerEventReceiver {
|
||||
/// Do something with the socket
|
||||
pub fn with_server<F, R>(&mut self, todo: F) -> Result<R, EventError>
|
||||
where
|
||||
F: FnOnce(&SignerEventReceiver, &mut HttpServer, bool) -> R,
|
||||
F: FnOnce(&SignerEventReceiver<T>, &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<T: SignerEventTrait> EventReceiver<T> for SignerEventReceiver<T> {
|
||||
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<SignerEvent, EventError> {
|
||||
fn next_event(&mut self) -> Result<SignerEvent<T>, 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<T>) -> 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<SignerEvent>) {
|
||||
fn add_consumer(&mut self, out_channel: Sender<SignerEvent<T>>) {
|
||||
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<T: SignerEventTrait>(
|
||||
local_addr: Option<SocketAddr>,
|
||||
mut request: HttpRequest,
|
||||
) -> Result<SignerEvent, EventError> {
|
||||
) -> Result<SignerEvent<T>, 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<StackerDBChunksEvent> for SignerEvent {
|
||||
impl<T: SignerEventTrait> TryFrom<StackerDBChunksEvent> for SignerEvent<T> {
|
||||
type Error = EventError;
|
||||
|
||||
fn try_from(event: StackerDBChunksEvent) -> Result<Self, Self::Error> {
|
||||
@@ -415,18 +424,18 @@ impl TryFrom<StackerDBChunksEvent> 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<StackerDBChunksEvent> for SignerEvent {
|
||||
return Err(EventError::UnrecognizedStackerDBContract(event.contract_id));
|
||||
};
|
||||
// signer-XXX-YYY boot contract
|
||||
let signer_messages: Vec<SignerMessage> = event
|
||||
let signer_messages: Vec<T> = event
|
||||
.modified_slots
|
||||
.iter()
|
||||
.filter_map(|chunk| read_next::<SignerMessage, _>(&mut &chunk.data[..]).ok())
|
||||
.filter_map(|chunk| read_next::<T, _>(&mut &chunk.data[..]).ok())
|
||||
.collect();
|
||||
SignerEvent::SignerMessages(signer_set, signer_messages)
|
||||
} else {
|
||||
@@ -448,7 +457,9 @@ impl TryFrom<StackerDBChunksEvent> for SignerEvent {
|
||||
}
|
||||
|
||||
/// Process a proposal response from the node
|
||||
fn process_proposal_response(mut request: HttpRequest) -> Result<SignerEvent, EventError> {
|
||||
fn process_proposal_response<T: SignerEventTrait>(
|
||||
mut request: HttpRequest,
|
||||
) -> Result<SignerEvent<T>, 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<SignerEvent, Ev
|
||||
}
|
||||
|
||||
/// Process a new burn block event from the node
|
||||
fn process_new_burn_block_event(mut request: HttpRequest) -> Result<SignerEvent, EventError> {
|
||||
fn process_new_burn_block_event<T: SignerEventTrait>(
|
||||
mut request: HttpRequest,
|
||||
) -> Result<SignerEvent<T>, EventError> {
|
||||
debug!("Got burn_block event");
|
||||
let mut body = String::new();
|
||||
if let Err(e) = request.as_reader().read_to_string(&mut body) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<R: Send, CMD: Send> {
|
||||
pub trait SignerRunLoop<R: Send, CMD: Send, T: SignerEventTrait> {
|
||||
/// 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<R: Send, CMD: Send> {
|
||||
/// Returns None to keep running.
|
||||
fn run_one_pass(
|
||||
&mut self,
|
||||
event: Option<SignerEvent>,
|
||||
event: Option<SignerEvent<T>>,
|
||||
cmd: Option<CMD>,
|
||||
res: Sender<R>,
|
||||
) -> Option<R>;
|
||||
@@ -64,7 +65,7 @@ pub trait SignerRunLoop<R: Send, CMD: Send> {
|
||||
/// This would run in a separate thread from the event receiver.
|
||||
fn main_loop<EVST: EventStopSignaler>(
|
||||
&mut self,
|
||||
event_recv: Receiver<SignerEvent>,
|
||||
event_recv: Receiver<SignerEvent<T>>,
|
||||
command_recv: Receiver<CMD>,
|
||||
result_send: Sender<R>,
|
||||
mut event_stop_signaler: EVST,
|
||||
@@ -93,7 +94,7 @@ pub trait SignerRunLoop<R: Send, CMD: Send> {
|
||||
}
|
||||
|
||||
/// The top-level signer implementation
|
||||
pub struct Signer<CMD, R, SL, EV> {
|
||||
pub struct Signer<CMD, R, SL, EV, T> {
|
||||
/// the runloop itself
|
||||
signer_loop: Option<SL>,
|
||||
/// the event receiver to use
|
||||
@@ -102,10 +103,12 @@ pub struct Signer<CMD, R, SL, EV> {
|
||||
command_receiver: Option<Receiver<CMD>>,
|
||||
/// the result sender to use
|
||||
result_sender: Option<Sender<R>>,
|
||||
/// phantom data for the codec
|
||||
phantom_data: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// The running signer implementation
|
||||
pub struct RunningSigner<EV: EventReceiver, R> {
|
||||
pub struct RunningSigner<EV: EventReceiver<T>, R, T: SignerEventTrait> {
|
||||
/// join handle for signer runloop
|
||||
signer_join: JoinHandle<Option<R>>,
|
||||
/// join handle for event receiver
|
||||
@@ -114,7 +117,7 @@ pub struct RunningSigner<EV: EventReceiver, R> {
|
||||
stop_signal: EV::ST,
|
||||
}
|
||||
|
||||
impl<EV: EventReceiver, R> RunningSigner<EV, R> {
|
||||
impl<EV: EventReceiver<T>, R, T: SignerEventTrait> RunningSigner<EV, R, T> {
|
||||
/// Stop the signer, and get the final state
|
||||
pub fn stop(mut self) -> Option<R> {
|
||||
// kill event receiver
|
||||
@@ -189,19 +192,20 @@ pub fn set_runloop_signal_handler<ST: EventStopSignaler + Send + 'static>(mut st
|
||||
}).expect("FATAL: failed to set signal handler");
|
||||
}
|
||||
|
||||
impl<CMD, R, SL, EV> Signer<CMD, R, SL, EV> {
|
||||
impl<CMD, R, SL, EV, T> Signer<CMD, R, SL, EV, T> {
|
||||
/// Create a new signer with the given runloop and event receiver.
|
||||
pub fn new(
|
||||
runloop: SL,
|
||||
event_receiver: EV,
|
||||
command_receiver: Receiver<CMD>,
|
||||
result_sender: Sender<R>,
|
||||
) -> Signer<CMD, R, SL, EV> {
|
||||
) -> Signer<CMD, R, SL, EV, T> {
|
||||
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<CMD, R, SL, EV> Signer<CMD, R, SL, EV> {
|
||||
impl<
|
||||
CMD: Send + 'static,
|
||||
R: Send + 'static,
|
||||
SL: SignerRunLoop<R, CMD> + Send + 'static,
|
||||
EV: EventReceiver + Send + 'static,
|
||||
> Signer<CMD, R, SL, EV>
|
||||
T: SignerEventTrait + 'static,
|
||||
SL: SignerRunLoop<R, CMD, T> + Send + 'static,
|
||||
EV: EventReceiver<T> + Send + 'static,
|
||||
> Signer<CMD, R, SL, EV, T>
|
||||
{
|
||||
/// 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<RunningSigner<EV, R>, EventError> {
|
||||
pub fn spawn(&mut self, bind_addr: SocketAddr) -> Result<RunningSigner<EV, R, T>, EventError> {
|
||||
let mut event_receiver = self
|
||||
.event_receiver
|
||||
.take()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,7 +86,7 @@ impl SignerEntries {
|
||||
weight_end = weight_start + entry.weight;
|
||||
let key_ids: HashSet<u32> = (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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T: SignerEventTrait> {
|
||||
poll_timeout: Duration,
|
||||
events: Vec<SignerEvent>,
|
||||
events: Vec<SignerEvent<T>>,
|
||||
max_events: usize,
|
||||
}
|
||||
|
||||
impl SimpleRunLoop {
|
||||
pub fn new(max_events: usize) -> SimpleRunLoop {
|
||||
impl<T: SignerEventTrait> SimpleRunLoop<T> {
|
||||
pub fn new(max_events: usize) -> SimpleRunLoop<T> {
|
||||
SimpleRunLoop {
|
||||
poll_timeout: Duration::from_millis(100),
|
||||
events: vec![],
|
||||
@@ -62,7 +63,7 @@ enum Command {
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl SignerRunLoop<Vec<SignerEvent>, Command> for SimpleRunLoop {
|
||||
impl<T: SignerEventTrait> SignerRunLoop<Vec<SignerEvent<T>>, Command, T> for SimpleRunLoop<T> {
|
||||
fn set_event_timeout(&mut self, timeout: Duration) {
|
||||
self.poll_timeout = timeout;
|
||||
}
|
||||
@@ -73,10 +74,10 @@ impl SignerRunLoop<Vec<SignerEvent>, Command> for SimpleRunLoop {
|
||||
|
||||
fn run_one_pass(
|
||||
&mut self,
|
||||
event: Option<SignerEvent>,
|
||||
event: Option<SignerEvent<T>>,
|
||||
_cmd: Option<Command>,
|
||||
_res: Sender<Vec<SignerEvent>>,
|
||||
) -> Option<Vec<SignerEvent>> {
|
||||
_res: Sender<Vec<SignerEvent<T>>>,
|
||||
) -> Option<Vec<SignerEvent<T>>> {
|
||||
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<SignerEvent> = chunks
|
||||
let sent_events: Vec<SignerEvent<SignerMessage>> = 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<SignerEvent> = vec![SignerEvent::StatusCheck];
|
||||
let sent_events: Vec<SignerEvent<SignerMessage>> = vec![SignerEvent::StatusCheck];
|
||||
|
||||
assert_eq!(sent_events, accepted_events);
|
||||
mock_stacks_node.join().unwrap();
|
||||
|
||||
572
libsigner/src/v0/messages.rs
Normal file
572
libsigner/src/v0/messages.rs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<u8> for SignerMessageTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let type_prefix_byte = read_next::<u8, _>(fd)?;
|
||||
let type_prefix = SignerMessageTypePrefix::try_from(type_prefix_byte)?;
|
||||
let message = match type_prefix {
|
||||
SignerMessageTypePrefix::BlockProposal => {
|
||||
let block_proposal = read_next::<BlockProposal, _>(fd)?;
|
||||
SignerMessage::BlockProposal(block_proposal)
|
||||
}
|
||||
SignerMessageTypePrefix::BlockResponse => {
|
||||
let block_response = read_next::<BlockResponse, _>(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<W: Write>(&self, fd: &mut W) -> Result<(), CodecError>;
|
||||
/// Deserialize the struct from the provided reader
|
||||
fn inner_consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError>;
|
||||
}
|
||||
|
||||
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<u8> for RejectCodeTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<u8> for BlockResponseTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let type_prefix_byte = read_next::<u8, _>(fd)?;
|
||||
let type_prefix = BlockResponseTypePrefix::try_from(type_prefix_byte)?;
|
||||
let response = match type_prefix {
|
||||
BlockResponseTypePrefix::Accepted => {
|
||||
let hash = read_next::<Sha512Trunc256Sum, _>(fd)?;
|
||||
let sig = read_next::<MessageSignature, _>(fd)?;
|
||||
BlockResponse::Accepted((hash, sig))
|
||||
}
|
||||
BlockResponseTypePrefix::Rejected => {
|
||||
let rejection = read_next::<BlockRejection, _>(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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let reason_bytes = read_next::<Vec<u8>, _>(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::<RejectCode, _>(fd)?;
|
||||
let signer_signature_hash = read_next::<Sha512Trunc256Sum, _>(fd)?;
|
||||
Ok(Self {
|
||||
reason,
|
||||
reason_code,
|
||||
signer_signature_hash,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockValidateReject> 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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let type_prefix_byte = read_next::<u8, _>(fd)?;
|
||||
let type_prefix = RejectCodeTypePrefix::try_from(type_prefix_byte)?;
|
||||
let code = match type_prefix {
|
||||
RejectCodeTypePrefix::ValidationFailed => RejectCode::ValidationFailed(
|
||||
ValidateRejectCode::try_from(read_next::<u8, _>(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<BlockResponse> for SignerMessage {
|
||||
fn from(block_response: BlockResponse) -> Self {
|
||||
Self::BlockResponse(block_response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockRejection> for SignerMessage {
|
||||
fn from(block_rejection: BlockRejection) -> Self {
|
||||
Self::BlockResponse(BlockResponse::Rejected(block_rejection))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockValidateReject> 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::<RejectCode, _>(&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::<RejectCode, _>(&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::<BlockRejection, _>(&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::<BlockRejection, _>(&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::<BlockResponse, _>(&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::<BlockResponse, _>(&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::<SignerMessage, _>(&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::<Sha512Trunc256Sum>::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::<SignerMessage, _>(&mut &serialized_signer_message[..])
|
||||
.expect("Failed to deserialize SignerMessage");
|
||||
assert_eq!(signer_message, deserialized_signer_message);
|
||||
}
|
||||
}
|
||||
17
libsigner/src/v0/mod.rs
Normal file
17
libsigner/src/v0/mod.rs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// Messages for the v0 signer
|
||||
pub mod messages;
|
||||
@@ -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<u8> 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<W: Write>(&self, fd: &mut W) -> Result<(), CodecError>;
|
||||
/// Deserialize the struct from the provided reader
|
||||
fn inner_consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError>;
|
||||
}
|
||||
|
||||
@@ -537,50 +568,87 @@ impl StacksMessageCodecExtensions for HashSet<u32> {
|
||||
}
|
||||
}
|
||||
|
||||
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<u8> for DkgFailureTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let failure_type_prefix = read_next::<u8, _>(fd)?;
|
||||
let failure_type_prefix_byte = read_next::<u8, _>(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::<u32>::inner_consensus_deserialize(fd)?;
|
||||
DkgFailure::MissingPublicShares(set)
|
||||
}
|
||||
2 => {
|
||||
DkgFailureTypePrefix::BadPublicShares => {
|
||||
let set = HashSet::<u32>::inner_consensus_deserialize(fd)?;
|
||||
DkgFailure::BadPublicShares(set)
|
||||
}
|
||||
3 => {
|
||||
DkgFailureTypePrefix::MissingPrivateShares => {
|
||||
let set = HashSet::<u32>::inner_consensus_deserialize(fd)?;
|
||||
DkgFailure::MissingPrivateShares(set)
|
||||
}
|
||||
4 => {
|
||||
DkgFailureTypePrefix::BadPrivateShares => {
|
||||
let mut map = HashMap::new();
|
||||
let len = read_next::<u32, _>(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<u8> for DkgStatusTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let dkg_id = read_next::<u64, _>(fd)?;
|
||||
let signer_id = read_next::<u32, _>(fd)?;
|
||||
let status_type_prefix = read_next::<u8, _>(fd)?;
|
||||
let status_type_prefix_byte = read_next::<u8, _>(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<u8> for BlockResponseTypePrefix {
|
||||
type Error = CodecError;
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
|
||||
let type_prefix = read_next::<u8, _>(fd)?;
|
||||
let type_prefix_byte = read_next::<u8, _>(fd)?;
|
||||
let type_prefix = BlockResponseTypePrefix::try_from(type_prefix_byte)?;
|
||||
let response = match type_prefix {
|
||||
0 => {
|
||||
BlockResponseTypePrefix::Accepted => {
|
||||
let hash = read_next::<Sha512Trunc256Sum, _>(fd)?;
|
||||
let sig = read_next::<ThresholdSignature, _>(fd)?;
|
||||
BlockResponse::Accepted((hash, sig))
|
||||
}
|
||||
1 => {
|
||||
BlockResponseTypePrefix::Rejected => {
|
||||
let rejection = read_next::<BlockRejection, _>(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 {
|
||||
17
libsigner/src/v1/mod.rs
Normal file
17
libsigner/src/v1/mod.rs
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// Messages for the v1 signer
|
||||
pub mod messages;
|
||||
@@ -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};
|
||||
|
||||
@@ -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![],
|
||||
|
||||
@@ -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<T: SignerEventTrait>: 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<T>>,
|
||||
res: Sender<Vec<OperationResult>>,
|
||||
current_reward_cycle: u64,
|
||||
);
|
||||
|
||||
@@ -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<SignerEventReceiver, Vec<OperationResult>>,
|
||||
_cmd_send: Sender<RunLoopCommand>,
|
||||
_res_recv: Receiver<Vec<OperationResult>>,
|
||||
}
|
||||
|
||||
/// 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<Vec<u8>>) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<OperationResult>,
|
||||
RunLoop<v1::signer::Signer>,
|
||||
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(
|
||||
|
||||
@@ -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<Signer: SignerTrait> {
|
||||
pub struct RunLoop<Signer, T>
|
||||
where
|
||||
Signer: SignerTrait<T>,
|
||||
T: StacksMessageCodec + Clone + Send + Debug,
|
||||
{
|
||||
/// Configuration info
|
||||
pub config: GlobalConfig,
|
||||
/// The stacks node client
|
||||
@@ -132,9 +138,11 @@ pub struct RunLoop<Signer: SignerTrait> {
|
||||
pub commands: VecDeque<RunLoopCommand>,
|
||||
/// The current reward cycle info. Only None if the runloop is uninitialized
|
||||
pub current_reward_cycle_info: Option<RewardCycleInfo>,
|
||||
/// Phantom data for the message codec
|
||||
_phantom_data: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<Signer: SignerTrait> RunLoop<Signer> {
|
||||
impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug> RunLoop<Signer, T> {
|
||||
/// 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<Signer: SignerTrait> RunLoop<Signer> {
|
||||
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<Signer: SignerTrait> RunLoop<Signer> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Signer: SignerTrait> SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop<Signer> {
|
||||
impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug>
|
||||
SignerRunLoop<Vec<OperationResult>, RunLoopCommand, T> for RunLoop<Signer, T>
|
||||
{
|
||||
fn set_event_timeout(&mut self, timeout: Duration) {
|
||||
self.config.event_timeout = timeout;
|
||||
}
|
||||
@@ -368,7 +379,7 @@ impl<Signer: SignerTrait> SignerRunLoop<Vec<OperationResult>, RunLoopCommand> fo
|
||||
|
||||
fn run_one_pass(
|
||||
&mut self,
|
||||
event: Option<SignerEvent>,
|
||||
event: Option<SignerEvent<T>>,
|
||||
cmd: Option<RunLoopCommand>,
|
||||
res: Sender<Vec<OperationResult>>,
|
||||
) -> Option<Vec<OperationResult>> {
|
||||
|
||||
@@ -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<SignerMessage>,
|
||||
Vec<OperationResult>,
|
||||
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<RunLoopCommand>,
|
||||
/// The result receiver for interacting with the running signer
|
||||
pub res_recv: Receiver<Vec<OperationResult>>,
|
||||
}
|
||||
|
||||
impl From<GlobalConfig> for SpawnedSigner {
|
||||
fn from(config: GlobalConfig) -> Self {
|
||||
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")]
|
||||
{
|
||||
crate::monitoring::start_serving_monitoring_metrics(config.clone()).ok();
|
||||
}
|
||||
let runloop = RunLoop::new(config);
|
||||
let mut signer: libsigner::Signer<
|
||||
RunLoopCommand,
|
||||
Vec<OperationResult>,
|
||||
RunLoop<Signer, SignerMessage>,
|
||||
SignerEventReceiver<SignerMessage>,
|
||||
SignerMessage,
|
||||
> = libsigner::Signer::new(runloop, ev, cmd_recv, res_send);
|
||||
let running_signer = signer.spawn(endpoint).unwrap();
|
||||
SpawnedSigner {
|
||||
running_signer,
|
||||
cmd_send,
|
||||
res_recv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpawnedSigner {
|
||||
/// Stop the signer thread and return the final state
|
||||
pub fn stop(self) -> Option<Vec<OperationResult>> {
|
||||
self.running_signer.stop()
|
||||
}
|
||||
|
||||
/// Wait for the signer to terminate, and get the final state. WARNING: This will hang forever if the event receiver stop signal was never sent/no error occurred.
|
||||
pub fn join(self) -> Option<Vec<OperationResult>> {
|
||||
self.running_signer.join()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::time::Instant;
|
||||
@@ -26,10 +27,10 @@ use blockstack_lib::chainstate::stacks::StacksTransaction;
|
||||
use blockstack_lib::net::api::postblock_proposal::BlockValidateResponse;
|
||||
use blockstack_lib::util_lib::db::Error as DBError;
|
||||
use hashbrown::HashSet;
|
||||
use libsigner::{
|
||||
BlockProposalSigners, BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerEvent,
|
||||
SignerMessage,
|
||||
use libsigner::v1::messages::{
|
||||
BlockRejection, BlockResponse, MessageSlotID, RejectCode, SignerMessage,
|
||||
};
|
||||
use libsigner::{BlockProposal, SignerEvent};
|
||||
use rand_core::OsRng;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use slog::{slog_debug, slog_error, slog_info, slog_warn};
|
||||
@@ -78,8 +79,8 @@ pub struct BlockInfo {
|
||||
pub signed_over: bool,
|
||||
}
|
||||
|
||||
impl From<BlockProposalSigners> for BlockInfo {
|
||||
fn from(value: BlockProposalSigners) -> Self {
|
||||
impl From<BlockProposal> for BlockInfo {
|
||||
fn from(value: BlockProposal) -> Self {
|
||||
Self {
|
||||
block: value.block,
|
||||
burn_block_height: value.burn_height,
|
||||
@@ -93,10 +94,7 @@ impl From<BlockProposalSigners> for BlockInfo {
|
||||
}
|
||||
impl BlockInfo {
|
||||
/// Create a new BlockInfo with an associated nonce request packet
|
||||
pub fn new_with_request(
|
||||
block_proposal: BlockProposalSigners,
|
||||
nonce_request: NonceRequest,
|
||||
) -> Self {
|
||||
pub fn new_with_request(block_proposal: BlockProposal, nonce_request: NonceRequest) -> Self {
|
||||
let mut block_info = BlockInfo::from(block_proposal);
|
||||
block_info.nonce_request = Some(nonce_request);
|
||||
block_info.signed_over = true;
|
||||
@@ -185,7 +183,7 @@ impl std::fmt::Display for Signer {
|
||||
}
|
||||
}
|
||||
|
||||
impl SignerTrait for Signer {
|
||||
impl SignerTrait<SignerMessage> for Signer {
|
||||
/// Create a new signer from the given configuration
|
||||
fn new(config: SignerConfig) -> Self {
|
||||
Self::from(config)
|
||||
@@ -209,7 +207,7 @@ impl SignerTrait for Signer {
|
||||
fn process_event(
|
||||
&mut self,
|
||||
stacks_client: &StacksClient,
|
||||
event: Option<&SignerEvent>,
|
||||
event: Option<&SignerEvent<SignerMessage>>,
|
||||
res: Sender<Vec<OperationResult>>,
|
||||
current_reward_cycle: u64,
|
||||
) {
|
||||
@@ -262,11 +260,9 @@ impl SignerTrait for Signer {
|
||||
self.handle_signer_messages(stacks_client, res, messages, current_reward_cycle);
|
||||
}
|
||||
Some(SignerEvent::MinerMessages(messages, miner_key)) => {
|
||||
if let Some(miner_key) = miner_key {
|
||||
let miner_key = PublicKey::try_from(miner_key.to_bytes_compressed().as_slice())
|
||||
.expect("FATAL: could not convert from StacksPublicKey to PublicKey");
|
||||
self.miner_key = Some(miner_key);
|
||||
};
|
||||
let miner_key = PublicKey::try_from(miner_key.to_bytes_compressed().as_slice())
|
||||
.expect("FATAL: could not convert from StacksPublicKey to PublicKey");
|
||||
self.miner_key = Some(miner_key);
|
||||
if current_reward_cycle != self.reward_cycle {
|
||||
// There is not point in processing blocks if we are not the current reward cycle (we can never actually contribute to signing these blocks)
|
||||
debug!("{self}: Received a proposed block, but this signer's reward cycle is not the current one ({current_reward_cycle}). Ignoring...");
|
||||
@@ -913,7 +909,7 @@ impl Signer {
|
||||
nonce_request: &mut NonceRequest,
|
||||
) -> Option<BlockInfo> {
|
||||
let Some(block_proposal) =
|
||||
BlockProposalSigners::consensus_deserialize(&mut nonce_request.message.as_slice()).ok()
|
||||
BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice()).ok()
|
||||
else {
|
||||
// We currently reject anything that is not a valid block proposal
|
||||
warn!("{self}: Received a nonce request for an unknown message stream. Reject it.",);
|
||||
|
||||
@@ -183,11 +183,7 @@ mod tests {
|
||||
use blockstack_lib::chainstate::nakamoto::{
|
||||
NakamotoBlock, NakamotoBlockHeader, NakamotoBlockVote,
|
||||
};
|
||||
use blockstack_lib::chainstate::stacks::ThresholdSignature;
|
||||
use libsigner::BlockProposalSigners;
|
||||
use stacks_common::bitvec::BitVec;
|
||||
use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId, TrieHash};
|
||||
use stacks_common::util::secp256k1::MessageSignature;
|
||||
use libsigner::BlockProposal;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -198,25 +194,14 @@ mod tests {
|
||||
}
|
||||
|
||||
fn create_block_override(
|
||||
overrides: impl FnOnce(&mut BlockProposalSigners),
|
||||
) -> (BlockInfo, BlockProposalSigners) {
|
||||
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(),
|
||||
};
|
||||
overrides: impl FnOnce(&mut BlockProposal),
|
||||
) -> (BlockInfo, BlockProposal) {
|
||||
let header = NakamotoBlockHeader::empty();
|
||||
let block = NakamotoBlock {
|
||||
header,
|
||||
txs: vec![],
|
||||
};
|
||||
let mut block_proposal = BlockProposalSigners {
|
||||
let mut block_proposal = BlockProposal {
|
||||
block,
|
||||
burn_height: 7,
|
||||
reward_cycle: 42,
|
||||
@@ -225,7 +210,7 @@ mod tests {
|
||||
(BlockInfo::from(block_proposal.clone()), block_proposal)
|
||||
}
|
||||
|
||||
fn create_block() -> (BlockInfo, BlockProposalSigners) {
|
||||
fn create_block() -> (BlockInfo, BlockProposal) {
|
||||
create_block_override(|_| {})
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ impl HttpResponse for RPCGetStxTransferCostRequestHandler {
|
||||
|
||||
impl StacksHttpRequest {
|
||||
pub fn new_get_stx_transfer_cost(host: PeerHost) -> StacksHttpRequest {
|
||||
let mut contents = HttpRequestContents::new();
|
||||
let contents = HttpRequestContents::new();
|
||||
StacksHttpRequest::new_for_peer(host, "GET".into(), "/v2/fees/transfer".into(), contents)
|
||||
.expect("FATAL: failed to construct request from infallible data")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::time::{Duration, Instant};
|
||||
use clarity::vm::clarity::ClarityConnection;
|
||||
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
|
||||
use hashbrown::HashSet;
|
||||
use libsigner::{MessageSlotID, SignerMessage};
|
||||
use libsigner::v1::messages::{MessageSlotID, SignerMessage};
|
||||
use stacks::burnchains::Burnchain;
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash};
|
||||
|
||||
@@ -17,10 +17,8 @@ use std::sync::mpsc::Receiver;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use libsigner::{
|
||||
BlockProposalSigners, MessageSlotID, SignerEntries, SignerEvent, SignerMessage, SignerSession,
|
||||
StackerDBSession,
|
||||
};
|
||||
use libsigner::v1::messages::{MessageSlotID, SignerMessage};
|
||||
use libsigner::{BlockProposal, SignerEntries, SignerEvent, SignerSession, StackerDBSession};
|
||||
use stacks::burnchains::Burnchain;
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
use stacks::chainstate::burn::BlockSnapshot;
|
||||
@@ -384,7 +382,7 @@ impl SignCoordinator {
|
||||
self.coordinator.current_sign_id = sign_id;
|
||||
self.coordinator.current_sign_iter_id = sign_iter_id;
|
||||
|
||||
let proposal_msg = BlockProposalSigners {
|
||||
let proposal_msg = BlockProposal {
|
||||
block: block.clone(),
|
||||
burn_height: burn_block_height,
|
||||
reward_cycle: reward_cycle_id,
|
||||
|
||||
@@ -26,7 +26,8 @@ use clarity::vm::costs::ExecutionCost;
|
||||
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
|
||||
use http_types::headers::AUTHORIZATION;
|
||||
use lazy_static::lazy_static;
|
||||
use libsigner::{BlockProposalSigners, SignerMessage, SignerSession, StackerDBSession};
|
||||
use libsigner::v1::messages::SignerMessage;
|
||||
use libsigner::{BlockProposal, SignerSession, StackerDBSession};
|
||||
use rand::RngCore;
|
||||
use stacks::burnchains::{MagicBytes, Txid};
|
||||
use stacks::chainstate::burn::db::sortdb::SortitionDB;
|
||||
@@ -319,7 +320,7 @@ pub fn get_latest_block_proposal(
|
||||
panic!("Expected a nonce request. Got {:?}", packet.msg);
|
||||
};
|
||||
let block_proposal =
|
||||
BlockProposalSigners::consensus_deserialize(&mut nonce_request.message.as_slice())
|
||||
BlockProposal::consensus_deserialize(&mut nonce_request.message.as_slice())
|
||||
.expect("Failed to deserialize block proposal");
|
||||
block_proposal.block
|
||||
};
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use std::collections::HashSet;
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, thread};
|
||||
|
||||
use clarity::boot_util::boot_code_id;
|
||||
use clarity::vm::Value;
|
||||
use libsigner::{
|
||||
BlockProposalSigners, BlockResponse, MessageSlotID, RejectCode, RunningSigner, Signer,
|
||||
SignerEntries, SignerEventReceiver, SignerMessage,
|
||||
};
|
||||
use libsigner::v1::messages::{BlockResponse, MessageSlotID, RejectCode, SignerMessage};
|
||||
use libsigner::{BlockProposal, SignerEntries};
|
||||
use rand::thread_rng;
|
||||
use rand_core::RngCore;
|
||||
use stacks::burnchains::Txid;
|
||||
@@ -43,8 +40,8 @@ use stacks_common::util::secp256k1::MessageSignature;
|
||||
use stacks_signer::client::{SignerSlotID, StackerDB, StacksClient};
|
||||
use stacks_signer::config::{build_signer_config_tomls, GlobalConfig as SignerConfig, Network};
|
||||
use stacks_signer::runloop::{RunLoopCommand, SignerCommand};
|
||||
use stacks_signer::v1;
|
||||
use stacks_signer::v1::coordinator::CoordinatorSelector;
|
||||
use stacks_signer::v1::SpawnedSigner;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
use wsts::curve::point::Point;
|
||||
@@ -85,12 +82,8 @@ struct RunningNodes {
|
||||
struct SignerTest {
|
||||
// The stx and bitcoin nodes and their run loops
|
||||
pub running_nodes: RunningNodes,
|
||||
// The channels for sending commands to the signers
|
||||
pub signer_cmd_senders: Vec<Sender<RunLoopCommand>>,
|
||||
// The channels for receiving results from the signers
|
||||
pub result_receivers: Vec<Receiver<Vec<OperationResult>>>,
|
||||
// The running signer and its threads
|
||||
pub running_signers: Vec<RunningSigner<SignerEventReceiver, Vec<OperationResult>>>,
|
||||
// The spawned signers and their threads
|
||||
pub spawned_signers: Vec<SpawnedSigner>,
|
||||
// the private keys of the signers
|
||||
pub signer_stacks_private_keys: Vec<StacksPrivateKey>,
|
||||
// link to the stacks node
|
||||
@@ -128,21 +121,15 @@ impl SignerTest {
|
||||
Some(9000),
|
||||
);
|
||||
|
||||
let mut running_signers = Vec::new();
|
||||
let mut signer_cmd_senders = Vec::new();
|
||||
let mut result_receivers = Vec::new();
|
||||
for i in 0..num_signers {
|
||||
let (cmd_send, cmd_recv) = channel();
|
||||
let (res_send, res_recv) = channel();
|
||||
info!("spawn signer");
|
||||
running_signers.push(spawn_signer(
|
||||
&signer_configs[i as usize],
|
||||
cmd_recv,
|
||||
res_send,
|
||||
));
|
||||
signer_cmd_senders.push(cmd_send);
|
||||
result_receivers.push(res_recv);
|
||||
}
|
||||
let spawned_signers: Vec<_> = (0..num_signers)
|
||||
.into_iter()
|
||||
.map(|i| {
|
||||
info!("spawning signer");
|
||||
let signer_config =
|
||||
SignerConfig::load_from_str(&signer_configs[i as usize]).unwrap();
|
||||
SpawnedSigner::from(signer_config)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Setup the nodes and deploy the contract to it
|
||||
let node = setup_stx_btc_node(naka_conf, &signer_stacks_private_keys, &signer_configs);
|
||||
@@ -151,9 +138,7 @@ impl SignerTest {
|
||||
|
||||
Self {
|
||||
running_nodes: node,
|
||||
result_receivers,
|
||||
signer_cmd_senders,
|
||||
running_signers,
|
||||
spawned_signers,
|
||||
signer_stacks_private_keys,
|
||||
stacks_client,
|
||||
run_stamp,
|
||||
@@ -431,10 +416,11 @@ impl SignerTest {
|
||||
debug!("Waiting for DKG...");
|
||||
let mut key = Point::default();
|
||||
let dkg_now = Instant::now();
|
||||
for recv in self.result_receivers.iter() {
|
||||
for signer in self.spawned_signers.iter() {
|
||||
let mut aggregate_public_key = None;
|
||||
loop {
|
||||
let results = recv
|
||||
let results = signer
|
||||
.res_recv
|
||||
.recv_timeout(timeout)
|
||||
.expect("failed to recv dkg results");
|
||||
for result in results {
|
||||
@@ -739,12 +725,10 @@ impl SignerTest {
|
||||
/// # Panics
|
||||
/// Panics if `signer_idx` is out of bounds
|
||||
fn stop_signer(&mut self, signer_idx: usize) -> StacksPrivateKey {
|
||||
let running_signer = self.running_signers.remove(signer_idx);
|
||||
self.signer_cmd_senders.remove(signer_idx);
|
||||
self.result_receivers.remove(signer_idx);
|
||||
let spawned_signer = self.spawned_signers.remove(signer_idx);
|
||||
let signer_key = self.signer_stacks_private_keys.remove(signer_idx);
|
||||
|
||||
running_signer.stop();
|
||||
spawned_signer.stop();
|
||||
signer_key
|
||||
}
|
||||
|
||||
@@ -765,15 +749,10 @@ impl SignerTest {
|
||||
.pop()
|
||||
.unwrap();
|
||||
|
||||
let (cmd_send, cmd_recv) = channel();
|
||||
let (res_send, res_recv) = channel();
|
||||
|
||||
info!("Restarting signer");
|
||||
let signer = spawn_signer(&signer_config, cmd_recv, res_send);
|
||||
|
||||
self.result_receivers.insert(signer_idx, res_recv);
|
||||
self.signer_cmd_senders.insert(signer_idx, cmd_send);
|
||||
self.running_signers.insert(signer_idx, signer);
|
||||
let config = SignerConfig::load_from_str(&signer_config).unwrap();
|
||||
let signer = SpawnedSigner::from(config);
|
||||
self.spawned_signers.insert(signer_idx, signer);
|
||||
}
|
||||
|
||||
fn shutdown(self) {
|
||||
@@ -787,37 +766,13 @@ impl SignerTest {
|
||||
.run_loop_stopper
|
||||
.store(false, Ordering::SeqCst);
|
||||
// Stop the signers before the node to prevent hanging
|
||||
for signer in self.running_signers {
|
||||
for signer in self.spawned_signers {
|
||||
assert!(signer.stop().is_none());
|
||||
}
|
||||
self.running_nodes.run_loop_thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_signer(
|
||||
data: &str,
|
||||
receiver: Receiver<RunLoopCommand>,
|
||||
sender: Sender<Vec<OperationResult>>,
|
||||
) -> RunningSigner<SignerEventReceiver, Vec<OperationResult>> {
|
||||
let config = SignerConfig::load_from_str(data).unwrap();
|
||||
let ev = SignerEventReceiver::new(config.network.is_mainnet());
|
||||
let endpoint = config.endpoint;
|
||||
#[cfg(feature = "monitoring_prom")]
|
||||
{
|
||||
stacks_signer::monitoring::start_serving_monitoring_metrics(config.clone()).ok();
|
||||
}
|
||||
let runloop: stacks_signer::runloop::RunLoop<v1::signer::Signer> =
|
||||
stacks_signer::runloop::RunLoop::new(config);
|
||||
let mut signer: Signer<
|
||||
RunLoopCommand,
|
||||
Vec<OperationResult>,
|
||||
stacks_signer::runloop::RunLoop<v1::signer::Signer>,
|
||||
SignerEventReceiver,
|
||||
> = Signer::new(runloop, ev, receiver, sender);
|
||||
info!("Spawning signer on endpoint {}", endpoint);
|
||||
signer.spawn(endpoint).unwrap()
|
||||
}
|
||||
|
||||
fn setup_stx_btc_node(
|
||||
mut naka_conf: NeonConfig,
|
||||
signer_stacks_private_keys: &[StacksPrivateKey],
|
||||
@@ -984,8 +939,9 @@ fn stackerdb_dkg() {
|
||||
// Determine the coordinator of the current node height
|
||||
info!("signer_runloop: spawn send commands to do dkg");
|
||||
let dkg_now = Instant::now();
|
||||
for sender in signer_test.signer_cmd_senders.iter() {
|
||||
sender
|
||||
for signer in signer_test.spawned_signers.iter() {
|
||||
signer
|
||||
.cmd_send
|
||||
.send(RunLoopCommand {
|
||||
reward_cycle,
|
||||
command: SignerCommand::Dkg,
|
||||
@@ -1075,12 +1031,12 @@ fn stackerdb_sign_request_rejected() {
|
||||
|
||||
info!("------------------------- Test Sign -------------------------");
|
||||
let reward_cycle = signer_test.get_current_reward_cycle();
|
||||
let block_proposal_1 = BlockProposalSigners {
|
||||
let block_proposal_1 = BlockProposal {
|
||||
block: block1.clone(),
|
||||
burn_height: 0,
|
||||
reward_cycle,
|
||||
};
|
||||
let block_proposal_2 = BlockProposalSigners {
|
||||
let block_proposal_2 = BlockProposal {
|
||||
block: block2.clone(),
|
||||
burn_height: 0,
|
||||
reward_cycle,
|
||||
@@ -1104,11 +1060,13 @@ fn stackerdb_sign_request_rejected() {
|
||||
merkle_root: None,
|
||||
},
|
||||
};
|
||||
for sender in signer_test.signer_cmd_senders.iter() {
|
||||
sender
|
||||
for signer in signer_test.spawned_signers.iter() {
|
||||
signer
|
||||
.cmd_send
|
||||
.send(sign_command.clone())
|
||||
.expect("failed to send sign command");
|
||||
sender
|
||||
signer
|
||||
.cmd_send
|
||||
.send(sign_taproot_command.clone())
|
||||
.expect("failed to send sign taproot command");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user