Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/remove v1 signer #5243

Merged
merged 9 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
473 changes: 14 additions & 459 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ members = [
# Dependencies we want to keep the same between workspace members
[workspace.dependencies]
ed25519-dalek = { version = "2.1.1", features = ["serde", "rand_core"] }
hashbrown = "0.14.3"
hashbrown = { version = "0.14.3", features = ["serde"] }
rand_core = "0.6"
rand = "0.8"
rand_chacha = "0.3.1"
tikv-jemallocator = "0.5.4"
wsts = { version = "9.0.0", default-features = false }
rusqlite = { version = "0.31.0", features = ["blob", "serde_json", "i128_blob", "bundled", "trace"] }

# Use a bit more than default optimization for
Expand Down
1 change: 0 additions & 1 deletion libsigner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ stacks-common = { path = "../stacks-common" }
stackslib = { path = "../stackslib"}
thiserror = "1.0"
tiny_http = "0.12"
wsts = { workspace = true }

[dev-dependencies]
mutants = "0.0.3"
Expand Down
8 changes: 1 addition & 7 deletions libsigner/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use std::time::SystemTime;
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
use blockstack_lib::chainstate::stacks::boot::{MINERS_NAME, SIGNERS_NAME};
use blockstack_lib::chainstate::stacks::events::StackerDBChunksEvent;
use blockstack_lib::chainstate::stacks::{StacksTransaction, ThresholdSignature};
use blockstack_lib::chainstate::stacks::StacksTransaction;
use blockstack_lib::net::api::postblock_proposal::{
BlockValidateReject, BlockValidateResponse, ValidateRejectCode,
};
Expand All @@ -48,12 +48,6 @@ use stacks_common::util::HexError;
use tiny_http::{
Method as HttpMethod, Request as HttpRequest, Response as HttpResponse, Server as HttpServer,
};
use wsts::common::Signature;
use wsts::net::{
DkgBegin, DkgEnd, DkgEndBegin, DkgPrivateBegin, DkgPrivateShares, DkgPublicShares, DkgStatus,
Message, NonceRequest, NonceResponse, Packet, SignatureShareRequest, SignatureShareResponse,
};
use wsts::state_machine::signer;

use crate::http::{decode_http_body, decode_http_request};
use crate::EventError;
Expand Down
2 changes: 0 additions & 2 deletions libsigner/src/libsigner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ mod session;
mod signer_set;
/// v0 signer related code
pub mod v0;
/// v1 signer related code
pub mod v1;

use std::cmp::Eq;
use std::fmt::Debug;
Expand Down
43 changes: 9 additions & 34 deletions libsigner/src/runloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,15 @@ 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, T: SignerEventTrait> {
pub trait SignerRunLoop<R: 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
fn get_event_timeout(&self) -> Duration;
/// Run one pass of the event loop, given new Signer events discovered since the last pass.
/// Returns Some(R) if this is the final pass -- the runloop evaluated to R
/// Returns None to keep running.
fn run_one_pass(
&mut self,
event: Option<SignerEvent<T>>,
cmd: Option<CMD>,
res: &Sender<R>,
) -> Option<R>;
fn run_one_pass(&mut self, event: Option<SignerEvent<T>>, res: &Sender<R>) -> Option<R>;

/// This is the main loop body for the signer. It continuously receives events from
/// `event_recv`, polling for up to `self.get_event_timeout()` units of time. Once it has
Expand All @@ -66,7 +61,6 @@ pub trait SignerRunLoop<R: Send, CMD: Send, T: SignerEventTrait> {
fn main_loop<EVST: EventStopSignaler>(
&mut self,
event_recv: Receiver<SignerEvent<T>>,
command_recv: Receiver<CMD>,
result_send: Sender<R>,
mut event_stop_signaler: EVST,
) -> Option<R> {
Expand All @@ -81,11 +75,7 @@ pub trait SignerRunLoop<R: Send, CMD: Send, T: SignerEventTrait> {
return None;
}
};
// Do not block for commands
let next_command_opt = command_recv.try_recv().ok();
if let Some(final_state) =
self.run_one_pass(next_event_opt, next_command_opt, &result_send)
{
if let Some(final_state) = self.run_one_pass(next_event_opt, &result_send) {
info!("Runloop exit; signaling event-receiver to stop");
event_stop_signaler.send();
return Some(final_state);
Expand All @@ -95,13 +85,11 @@ pub trait SignerRunLoop<R: Send, CMD: Send, T: SignerEventTrait> {
}

/// The top-level signer implementation
pub struct Signer<CMD, R, SL, EV, T> {
pub struct Signer<R, SL, EV, T> {
/// the runloop itself
signer_loop: Option<SL>,
/// the event receiver to use
event_receiver: Option<EV>,
/// the command receiver to use
command_receiver: Option<Receiver<CMD>>,
/// the result sender to use
result_sender: Option<Sender<R>>,
/// phantom data for the codec
Expand Down Expand Up @@ -193,31 +181,24 @@ pub fn set_runloop_signal_handler<ST: EventStopSignaler + Send + 'static>(mut st
}).expect("FATAL: failed to set signal handler");
}

impl<CMD, R, SL, EV, T> Signer<CMD, R, SL, EV, T> {
impl<R, SL, EV, T> Signer<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, T> {
pub fn new(runloop: SL, event_receiver: EV, result_sender: Sender<R>) -> Signer<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,
}
}
}

impl<
CMD: Send + 'static,
R: Send + 'static,
T: SignerEventTrait + 'static,
SL: SignerRunLoop<R, CMD, T> + Send + 'static,
SL: SignerRunLoop<R, T> + Send + 'static,
EV: EventReceiver<T> + Send + 'static,
> Signer<CMD, R, SL, EV, T>
> Signer<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
Expand All @@ -234,10 +215,6 @@ impl<
.event_receiver
.take()
.ok_or(EventError::AlreadyRunning)?;
let command_receiver = self
.command_receiver
.take()
.ok_or(EventError::AlreadyRunning)?;
let result_sender = self
.result_sender
.take()
Expand Down Expand Up @@ -266,9 +243,7 @@ impl<
let runloop_thread = thread::Builder::new()
.name(format!("signer_runloop:{bind_port}"))
.stack_size(THREAD_STACK_SIZE)
.spawn(move || {
signer_loop.main_loop(event_recv, command_receiver, result_sender, stop_signaler)
})
.spawn(move || signer_loop.main_loop(event_recv, result_sender, stop_signaler))
.map_err(|e| {
error!("SignerRunLoop failed to start: {:?}", &e);
ret_stop_signaler.send();
Expand Down
132 changes: 42 additions & 90 deletions libsigner/src/signer_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,125 +13,77 @@
// 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::{BTreeMap, HashMap};

use blockstack_lib::chainstate::stacks::boot::NakamotoSignerEntry;
use hashbrown::{HashMap, HashSet};
use stacks_common::types::chainstate::{StacksAddress, StacksPublicKey};
use wsts::curve::ecdsa;
use wsts::curve::point::{Compressed, Point};
use wsts::state_machine::PublicKeys;

/// A reward set parsed into the structures required by WSTS party members and coordinators.
/// A reward set parsed into relevant structures
#[derive(Debug, Clone)]
pub struct SignerEntries {
/// The signer addresses mapped to signer id
pub signer_ids: HashMap<StacksAddress, u32>,
/// The signer ids mapped to public key and key ids mapped to public keys
pub public_keys: PublicKeys,
/// The signer ids mapped to key ids
pub signer_key_ids: HashMap<u32, Vec<u32>>,
/// The signer ids mapped to wsts public keys
pub signer_public_keys: HashMap<u32, Point>,
/// The signer ids mapped to a hash set of key ids
/// The wsts coordinator uses a hash set for each signer since it needs to do lots of lookups
pub coordinator_key_ids: HashMap<u32, HashSet<u32>>,
/// The signer addresses mapped to signer ID
pub signer_addr_to_id: HashMap<StacksAddress, u32>,
/// The signer IDs mapped to addresses. Uses a BTreeMap to ensure *reward cycle order*
pub signer_id_to_addr: BTreeMap<u32, StacksAddress>,
/// signer ID mapped to public key
pub signer_id_to_pk: HashMap<u32, StacksPublicKey>,
/// public_key mapped to signer ID
pub signer_pk_to_id: HashMap<StacksPublicKey, u32>,
/// The signer public keys
pub signer_pks: Vec<StacksPublicKey>,
/// The signer addresses
pub signer_addresses: Vec<StacksAddress>,
/// The signer address mapped to signing weight
pub signer_addr_to_weight: HashMap<StacksAddress, u32>,
}

/// Parsing errors for `SignerEntries`
#[derive(Debug)]
pub enum Error {
/// A member of the signing set has a signing key buffer
/// which does not represent a ecdsa public key.
/// which does not represent a valid Stacks public key
BadSignerPublicKey(String),
/// The number of signers was greater than u32::MAX
SignerCountOverflow,
}

impl SignerEntries {
/// Try to parse the reward set defined by `NakamotoSignEntry` into the structures required
/// by WSTS party members and coordinators.
/// Try to parse the reward set defined by `NakamotoSignEntry` into the SignerEntries struct
pub fn parse(is_mainnet: bool, reward_set: &[NakamotoSignerEntry]) -> Result<Self, Error> {
let mut weight_end = 1;
let mut signer_key_ids = HashMap::with_capacity(reward_set.len());
let mut signer_public_keys = HashMap::with_capacity(reward_set.len());
let mut coordinator_key_ids = HashMap::with_capacity(4000);
let mut signer_ids = HashMap::with_capacity(reward_set.len());
let mut wsts_signers = HashMap::new();
let mut wsts_key_ids = HashMap::new();
let mut signer_pk_to_id = HashMap::with_capacity(reward_set.len());
let mut signer_id_to_pk = HashMap::with_capacity(reward_set.len());
let mut signer_addr_to_id = HashMap::with_capacity(reward_set.len());
let mut signer_pks = Vec::with_capacity(reward_set.len());
let mut signer_id_to_addr = BTreeMap::new();
let mut signer_addr_to_weight = HashMap::new();
let mut signer_addresses = Vec::with_capacity(reward_set.len());
for (i, entry) in reward_set.iter().enumerate() {
let signer_id = u32::try_from(i).map_err(|_| Error::SignerCountOverflow)?;
let ecdsa_pk =
ecdsa::PublicKey::try_from(entry.signing_key.as_slice()).map_err(|e| {
Error::BadSignerPublicKey(format!(
"Failed to convert signing key to ecdsa::PublicKey: {e}"
))
})?;
let signer_public_key = Point::try_from(&Compressed::from(ecdsa_pk.to_bytes()))
.map_err(|e| {
Error::BadSignerPublicKey(format!(
"Failed to convert signing key to wsts::Point: {e}"
))
})?;
let stacks_public_key = StacksPublicKey::from_slice(entry.signing_key.as_slice())
let signer_public_key = StacksPublicKey::from_slice(entry.signing_key.as_slice())
.map_err(|e| {
Error::BadSignerPublicKey(format!(
"Failed to convert signing key to StacksPublicKey: {e}"
))
})?;

let stacks_address = StacksAddress::p2pkh(is_mainnet, &stacks_public_key);
signer_ids.insert(stacks_address, signer_id);

signer_public_keys.insert(signer_id, signer_public_key);
let weight_start = weight_end;
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);
}
signer_key_ids.insert(signer_id, (weight_start..weight_end).collect());
coordinator_key_ids.insert(signer_id, key_ids);
wsts_signers.insert(signer_id, ecdsa_pk);
let stacks_address = StacksAddress::p2pkh(is_mainnet, &signer_public_key);
signer_addr_to_id.insert(stacks_address, signer_id);
signer_id_to_pk.insert(signer_id, signer_public_key);
signer_pk_to_id.insert(signer_public_key, signer_id);
signer_pks.push(signer_public_key);
signer_id_to_addr.insert(signer_id, stacks_address);
signer_addr_to_weight.insert(stacks_address, entry.weight);
signer_addresses.push(stacks_address);
}

Ok(Self {
signer_ids,
public_keys: PublicKeys {
signers: wsts_signers,
key_ids: wsts_key_ids,
},
signer_key_ids,
signer_public_keys,
coordinator_key_ids,
signer_addr_to_id,
signer_id_to_pk,
signer_pk_to_id,
signer_pks,
signer_id_to_addr,
signer_addr_to_weight,
signer_addresses,
})
}

/// Return the number of Key IDs in the WSTS group signature
pub fn count_keys(&self) -> Result<u32, Error> {
self.public_keys
.key_ids
.len()
.try_into()
.map_err(|_| Error::SignerCountOverflow)
}

/// Return the number of Key IDs in the WSTS group signature
pub fn count_signers(&self) -> Result<u32, Error> {
self.public_keys
.signers
.len()
.try_into()
.map_err(|_| Error::SignerCountOverflow)
}

/// Return the number of Key IDs required to sign a message with the WSTS group signature
pub fn get_signing_threshold(&self) -> Result<u32, Error> {
let num_keys = self.count_keys()?;
Ok((num_keys as f64 * 7_f64 / 10_f64).ceil() as u32)
}

/// Return the number of Key IDs required to sign a message with the WSTS group signature
pub fn get_dkg_threshold(&self) -> Result<u32, Error> {
let num_keys = self.count_keys()?;
Ok((num_keys as f64 * 9_f64 / 10_f64).ceil() as u32)
}
}
12 changes: 6 additions & 6 deletions libsigner/src/tests/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::http::{decode_http_body, decode_http_request, decode_http_response, r

#[test]
fn test_decode_http_request_ok() {
let tests = vec![
let tests = [
("GET /foo HTTP/1.1\r\nHost: localhost:6270\r\n\r\n",
("GET", "/foo", vec![("host", "localhost:6270")])),
("POST asdf HTTP/1.1\r\nHost: core.blockstack.org\r\nFoo: Bar\r\n\r\n",
Expand Down Expand Up @@ -61,7 +61,7 @@ fn test_decode_http_request_ok() {

#[test]
fn test_decode_http_request_err() {
let tests = vec![
let tests = [
(
"GET /foo HTTP/1.1\r\n",
EventError::Deserialize("".to_string()),
Expand Down Expand Up @@ -99,7 +99,7 @@ fn test_decode_http_request_err() {

#[test]
fn test_decode_http_response_ok() {
let tests = vec![
let tests = [
("HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: 123\r\nX-Request-ID: 0\r\n\r\n",
vec![("content-type", "application/octet-stream"), ("content-length", "123"), ("x-request-id", "0")]),
("HTTP/1.1 200 Ok\r\nContent-Type: application/octet-stream\r\nTransfer-encoding: chunked\r\nX-Request-ID: 0\r\n\r\n",
Expand All @@ -123,7 +123,7 @@ fn test_decode_http_response_ok() {

#[test]
fn test_decode_http_response_err() {
let tests = vec![
let tests = [
("HTTP/1.1 400 Bad Request\r\nContent-Type: application/json\r\nContent-Length: 456\r\nFoo: Bar\r\nX-Request-ID: 0\r\n\r\n",
RPCError::HttpError(400)),
("HTTP/1.1 200",
Expand Down Expand Up @@ -223,7 +223,7 @@ impl Write for MockHTTPSocket {

#[test]
fn test_run_http_request_with_body() {
let tests = vec![
let tests = [
("GET", "/test-no-content-type-and-no-body", None, vec![]),
(
"GET",
Expand Down Expand Up @@ -288,7 +288,7 @@ fn test_run_http_request_with_body() {

#[test]
fn test_run_http_request_no_body() {
let tests = vec![
let tests = [
("GET", "/test-no-content-type-and-no-body", None, vec![]),
(
"GET",
Expand Down
Loading
Loading