diff --git a/Cargo.lock b/Cargo.lock index 38db70adba..add680a994 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1153,6 +1153,20 @@ dependencies = [ [[package]] name = "phaselock-types" version = "0.0.6-dev" +dependencies = [ + "async-std", + "async-tungstenite", + "bincode", + "blake3", + "futures", + "hex_fmt", + "rand 0.7.3", + "rand_chacha 0.2.2", + "serde", + "serde_bytes", + "snafu", + "threshold_crypto", +] [[package]] name = "pin-project" diff --git a/Cargo.toml b/Cargo.toml index 97d20aaab4..aedd66d6c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,18 +31,18 @@ dashmap = "5.1.0" flume = "0.10.10" futures = "0.3.21" hex_fmt = "0.3.0" +phaselock-types = { path = "./phaselock-types", version = "0.0.6-dev" } # jf-primitives = { features=["std"], git = "ssh://git@gitlab.com/translucence/crypto/jellyfish.git", rev = "3faf4400a7ae67de18400051f57afe8e4fb6d4de"} rand = "0.7.3" rand_chacha = "0.2.2" -serde_bytes = "0.11" serde = { version = "1.0.136", features = ["derive", "rc"] } +serde_bytes = "0.11" snafu = { version = "0.7.0", features = ["backtraces"] } threshold_crypto = "0.4.0" tracing = "0.1.30" tracing-error = "0.2.0" tracing-futures = "0.2.5" tracing-unwrap = "0.9.2" -phaselock-types = { path = "./phaselock-types", version = "0.0.6-dev" } [dev-dependencies] async-std = { version = "1.10.0", features = ["attributes"] } @@ -79,4 +79,4 @@ lto = "thin" members = [ "phaselock-types" -] \ No newline at end of file +] diff --git a/phaselock-types/Cargo.toml b/phaselock-types/Cargo.toml index 3a755e573d..b15333036f 100644 --- a/phaselock-types/Cargo.toml +++ b/phaselock-types/Cargo.toml @@ -9,3 +9,15 @@ version = "0.0.6-dev" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +async-std = "1.10.0" +async-tungstenite = "0.16.1" +bincode = "1.3.3" +blake3 = "1.3.1" +futures = "0.3.21" +hex_fmt = "0.3.0" +rand = "0.7.3" +rand_chacha = "0.2.2" +serde = { version = "1.0.136", features = ["derive"] } +serde_bytes = "0.11.5" +snafu = "0.7.0" +threshold_crypto = "0.4.0" diff --git a/phaselock-types/src/data.rs b/phaselock-types/src/data.rs new file mode 100644 index 0000000000..3d6e583c92 --- /dev/null +++ b/phaselock-types/src/data.rs @@ -0,0 +1,358 @@ +//! Provides types useful for representing `PhaseLock`'s data structures +//! +//! This module provides types for representing consensus internal state, such as the [`Leaf`], +//! `PhaseLock`'s version of a block, and the [`QuorumCertificate`], representing the threshold +//! signatures fundamental to consensus. +use blake3::Hasher; +use hex_fmt::HexFmt; +use serde::{Deserialize, Serialize}; +use threshold_crypto as tc; + +use std::fmt::Debug; + +use crate::traits::BlockContents; + +/// generates boilerplate code for any wrapper types +/// around `InternalHash` +macro_rules! gen_hash_wrapper_type { + ($t:ident) => { + #[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)] + /// External wrapper type + pub struct $t { + inner: InternalHash, + } + + impl $t { + /// Converts an array of the correct size directly into an `Self` + pub fn from_array(input: [u8; N]) -> Self { + $t { + inner: InternalHash::from_array(input), + } + } + /// Clones the contents of this Hash into a `Vec` + pub fn to_vec(self) -> Vec { + self.inner.to_vec() + } + /// Testing only random generation + pub fn random() -> Self { + $t { + inner: InternalHash::random(), + } + } + } + impl AsRef<[u8]> for $t { + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } + } + + impl From<[u8; N]> for $t { + fn from(input: [u8; N]) -> Self { + Self::from_array(input) + } + } + + impl Default for $t { + fn default() -> Self { + $t { + inner: InternalHash::default(), + } + } + } + impl Debug for $t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("{}", std::any::type_name::<$t>())) + .field("inner", &format!("{}", HexFmt(&self.inner))) + .finish() + } + } + }; +} + +gen_hash_wrapper_type!(BlockHash); +gen_hash_wrapper_type!(LeafHash); +gen_hash_wrapper_type!(TransactionHash); +gen_hash_wrapper_type!(VerifyHash); +gen_hash_wrapper_type!(StateHash); + +/// Internal type used for representing hashes +/// +/// This is a thin wrapper around a `[u8; N]` used to work around various issues with libraries that +/// have not updated to be const-generic aware. In particular, this provides a `serde` [`Serialize`] +/// and [`Deserialize`] implementation over the const-generic array, which `serde` normally does not +/// have for the general case. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)] +pub struct InternalHash { + /// The underlying array + /// No support for const generics + #[serde(with = "serde_bytes_array")] + inner: [u8; N], +} + +impl Debug for InternalHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InternalHash") + .field("inner", &format!("{}", HexFmt(&self.inner))) + .finish() + } +} + +impl InternalHash { + /// Converts an array of the correct size directly into an `InternalHash` + pub const fn from_array(input: [u8; N]) -> Self { + Self { inner: input } + } + + /// Clones the contents of this `InternalHash` into a `Vec` + pub fn to_vec(self) -> Vec { + self.inner.to_vec() + } + + /// Testing only random generation of a `InternalHash` + pub fn random() -> Self { + use rand::Rng; + let mut array = [0_u8; N]; + let mut rng = rand::thread_rng(); + rng.fill(&mut array[..]); + Self { inner: array } + } +} + +impl AsRef<[u8]> for InternalHash { + fn as_ref(&self) -> &[u8] { + &self.inner + } +} + +impl From<[u8; N]> for InternalHash { + fn from(input: [u8; N]) -> Self { + Self::from_array(input) + } +} + +impl Default for InternalHash { + fn default() -> Self { + InternalHash { + inner: [0_u8; { N }], + } + } +} + +/// [Needed](https://github.com/serde-rs/bytes/issues/26#issuecomment-902550669) to (de)serialize const generic arrays +mod serde_bytes_array { + use core::convert::TryInto; + + use serde::de::Error; + use serde::{Deserializer, Serializer}; + + /// This just specializes [`serde_bytes::serialize`] to ``. + pub fn serialize(bytes: &[u8], serializer: S) -> Result + where + S: Serializer, + { + serde_bytes::serialize(bytes, serializer) + } + + /// This takes the result of [`serde_bytes::deserialize`] from `[u8]` to `[u8; N]`. + pub fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error> + where + D: Deserializer<'de>, + { + let slice: &[u8] = serde_bytes::deserialize(deserializer)?; + let array: [u8; N] = slice.try_into().map_err(|_| { + let expected = format!("[u8; {}]", N); + D::Error::invalid_length(slice.len(), &expected.as_str()) + })?; + Ok(array) + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] +/// Represents the stages of consensus +pub enum Stage { + /// Between rounds + None, + /// Prepare Phase + Prepare, + /// PreCommit Phase + PreCommit, + /// Commit Phase + Commit, + /// Decide Phase + Decide, +} + +/// The type used for Quorum Certificates +/// +/// A Quorum Certificate is a threshold signature of the [`Leaf`] being proposed, as well as some +/// metadata, such as the [`Stage`] of consensus the quorum certificate was generated during. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct QuorumCertificate { + /// Hash of the block refereed to by this Quorum Certificate. + /// + /// This is included for convenience, and is not fundamental to consensus or covered by the + /// signature. This _must_ be identical to the [`BlockContents`] provided hash of the `item` in + /// the referenced leaf. + pub block_hash: BlockHash, + /// Hash of the [`Leaf`] referred to by this Quorum Certificate + /// + /// This value is covered by the threshold signature. + pub leaf_hash: LeafHash, + /// The view number this quorum certificate was generated during + /// + /// This value is covered by the threshold signature. + pub view_number: u64, + /// The [`Stage`] of consensus that this Quorum Certificate was generated during + /// + /// This value is covered by the threshold signature. + pub stage: Stage, + /// The threshold signature associated with this Quorum Certificate. + /// + /// This is nullable as part of a temporary mechanism to support bootstrapping from a genesis + /// block, as the genesis block can not be produced through the normal means. + pub signature: Option, + /// Temporary bypass for boostrapping + /// + /// This value indicates that this is a dummy certificate for the genesis block, and thus does + /// not have a signature. This value is not covered by the signature, and it is invalid for this + /// to be set outside of bootstrap + pub genesis: bool, +} + +impl QuorumCertificate { + /// Verifies a quorum certificate + /// + /// This concatenates the encoding of the [`Leaf`] hash, the `view_number`, and the `stage`, in + /// that order, and makes sure that the associated signature validates against the resulting + /// byte string. + /// + /// If the `genesis` value is set, this disables the normal checking, and instead performs + /// bootstrap checking. + #[must_use] + pub fn verify(&self, key: &tc::PublicKeySet, view: u64, stage: Stage) -> bool { + if let Some(signature) = &self.signature { + let concatenated_hash = create_verify_hash(&self.leaf_hash, view, stage); + key.public_key().verify(signature, &concatenated_hash) + } else { + self.genesis + } + } + + /// Converts this Quorum Certificate to a version using a `Vec` rather than a const-generic + /// array. + /// + /// This is useful for erasing the const-generic length for error types and logging, but is not + /// directly consensus relevant. + pub fn to_vec_cert(&self) -> VecQuorumCertificate { + VecQuorumCertificate { + hash: self.block_hash.as_ref().to_vec(), + view_number: self.view_number, + stage: self.stage, + signature: self.signature.clone(), + genesis: self.genesis, + } + } +} + +impl Debug for QuorumCertificate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuorumCertificate") + .field("hash", &format!("{:12}", HexFmt(&self.leaf_hash))) + .field("view_number", &self.view_number) + .field("stage", &self.stage) + .field("signature", &self.signature) + .field("genesis", &self.genesis) + .finish() + } +} + +/// [`QuorumCertificate`] variant using a `Vec` rather than a const-generic array +/// +/// This type mainly exists to work around an issue with +/// [`snafu`](https://github.com/shepmaster/snafu) when used with const-generics, by erasing the +/// const-generic length. +/// +/// This type is not used directly by consensus. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct VecQuorumCertificate { + /// Block this QC refers to + pub hash: Vec, + /// The view we were on when we made this certificate + pub view_number: u64, + /// The stage of consensus we were on when we made this certificate + pub stage: Stage, + /// The signature portion of this QC + pub signature: Option, + /// Temporary bypass for boostrapping + pub genesis: bool, +} + +impl Debug for VecQuorumCertificate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuorumCertificate") + .field("hash", &format!("{:12}", HexFmt(&self.hash))) + .field("view_number", &self.view_number) + .field("stage", &self.stage) + .field("signature", &self.signature) + .field("genesis", &self.genesis) + .finish() + } +} + +/// This concatenates the encoding of `leaf_hash`, `view`, and `stage`, in +/// that order, and hashes the result. +pub fn create_verify_hash( + leaf_hash: &LeafHash, + view: u64, + stage: Stage, +) -> VerifyHash<32> { + let mut hasher = Hasher::new(); + hasher.update(leaf_hash.as_ref()); + hasher.update(&view.to_be_bytes()); + hasher.update(&(stage as u64).to_be_bytes()); + let hash = hasher.finalize(); + VerifyHash::from_array(*hash.as_bytes()) +} + +#[derive(Serialize, Deserialize, Clone)] +/// A node in `PhaseLock`'s consensus-internal merkle tree. +/// +/// This is the consensus-internal analogous concept to a block, and it contains the block proper, +/// as well as the hash of its parent `Leaf`. +pub struct Leaf { + /// The hash of the parent `Leaf` + pub parent: LeafHash, + /// The block contained in this `Leaf` + pub item: T, +} + +impl, const N: usize> Leaf { + /// Creates a new leaf with the specified block and parent + /// + /// # Arguments + /// * `item` - The block to include + /// * `parent` - The hash of the `Leaf` that is to be the parent of this `Leaf` + pub fn new(item: T, parent: LeafHash) -> Self { + Leaf { parent, item } + } + + /// Hashes the leaf with the hashing algorithm provided by the [`BlockContents`] implementation + /// + /// This will concatenate the `parent` hash with the [`BlockContents`] provided hash of the + /// contained block, and then return the hash of the resulting concatenated byte string. + pub fn hash(&self) -> LeafHash { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(self.parent.as_ref()); + bytes.extend_from_slice(BlockContents::hash(&self.item).as_ref()); + T::hash_leaf(&bytes) + } +} + +impl Debug for Leaf { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("Leaf<{}>", std::any::type_name::())) + .field("item", &self.item) + .field("parent", &format!("{:12}", HexFmt(&self.parent))) + .finish() + } +} diff --git a/src/types/error.rs b/phaselock-types/src/error.rs similarity index 92% rename from src/types/error.rs rename to phaselock-types/src/error.rs index 20bdc76926..a035f9342f 100644 --- a/src/types/error.rs +++ b/phaselock-types/src/error.rs @@ -1,12 +1,12 @@ -//! Error type for [`PhaseLock`](crate::PhaseLock) +//! Error type for `PhaseLock` //! //! This module provides [`PhaseLockError`], which is an enum representing possible faults that can //! occur while interacting with this crate. use snafu::Snafu; -/// Error type for [`PhaseLock`](crate::PhaseLock) +/// Error type for `PhaseLock` #[derive(Debug, Snafu)] -#[snafu(visibility(pub(crate)))] +#[snafu(visibility(pub))] #[non_exhaustive] pub enum PhaseLockError { /// Failed to Message the leader in the given stage @@ -15,7 +15,7 @@ pub enum PhaseLockError { /// The stage the failure occurred in stage: crate::data::Stage, /// The underlying network fault - source: crate::traits::NetworkError, + source: crate::traits::network::NetworkError, }, /// Failed to broadcast a message on the network #[snafu(display("Failed to broadcast a message in stage {stage:?}: {source}"))] @@ -23,7 +23,7 @@ pub enum PhaseLockError { /// The stage the failure occurred in stage: crate::data::Stage, /// The underlying network fault - source: crate::traits::NetworkError, + source: crate::traits::network::NetworkError, }, /// Bad or forged quorum certificate #[snafu(display("Bad or forged QC in stage {:?}", stage))] @@ -62,7 +62,7 @@ pub enum PhaseLockError { #[snafu(display("Failure in networking layer: {source}"))] NetworkFault { /// Underlying network fault - source: crate::traits::NetworkError, + source: crate::traits::network::NetworkError, }, /// Item was not present in storage ItemNotFound { diff --git a/phaselock-types/src/event.rs b/phaselock-types/src/event.rs new file mode 100644 index 0000000000..eface47bee --- /dev/null +++ b/phaselock-types/src/event.rs @@ -0,0 +1,75 @@ +//! Events that a `PhaseLock` instance can emit + +use std::sync::Arc; + +use crate::{data::Stage, error::PhaseLockError}; + +/// A status event emitted by a `PhaseLock` instance +/// +/// This includes some metadata, such as the stage and view number that the event was generated in, +/// as well as an inner [`EventType`] describing the event proper. +#[derive(Clone, Debug)] +pub struct Event { + /// The view number that this event originates from + pub view_number: u64, + /// The stage that this event originates from + pub stage: Stage, + /// The underlying event + pub event: EventType, +} + +/// The type and contents of a status event emitted by a `PhaseLock` instance +/// +/// This enum does not include metadata shared among all variants, such as the stage and view +/// number, and is thus always returned wrapped in an [`Event`]. +#[non_exhaustive] +#[derive(Clone, Debug)] +pub enum EventType { + /// A view encountered an error and was interrupted + Error { + /// The underlying error + error: Arc, + }, + /// A new block was proposed + Propose { + /// The block that was proposed + block: Arc, + }, + /// A new decision event was issued + Decide { + /// The list of blocks that were committed by this decision + /// + /// This list is sorted in reverse view number order, with the newest (highest view number) + /// block first in the list. + /// + /// This list may be incomplete if the node is currently performing catchup. + block: Arc>, + /// The list of states that were committed by this decision + /// + /// This list is sorted in reverse view number order, with the newest (highest view number) + /// state first in the list. + /// + /// This list may be incomplete if the node is currently performing catchup. + state: Arc>, + }, + /// A new view was started by this node + NewView { + /// The view being started + view_number: u64, + }, + /// A view was canceled by a timeout interrupt + ViewTimeout { + /// The view that timed out + view_number: u64, + }, + /// This node is the leader for this view + Leader { + /// The current view number + view_number: u64, + }, + /// This node is a follower for this view + Follower { + /// The current view number + view_number: u64, + }, +} diff --git a/phaselock-types/src/lib.rs b/phaselock-types/src/lib.rs index 656311d76a..cd2619b9b6 100644 --- a/phaselock-types/src/lib.rs +++ b/phaselock-types/src/lib.rs @@ -7,3 +7,99 @@ clippy::missing_docs_in_private_items, clippy::panic )] +#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)] +pub use threshold_crypto as tc; + +pub mod data; +pub mod error; +pub mod event; +pub mod message; +pub mod traits; + +use serde::{Deserialize, Serialize}; + +use std::fmt::Debug; + +use data::{create_verify_hash, LeafHash, Stage}; + +/// Public key type +/// +/// Opaque wrapper around `threshold_crypto` key +/// +/// TODO: These items don't really need to be pub, but are due to the pain in the ass factor of the +/// migration to a split crate +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +pub struct PubKey { + /// Overall public key set for the network + pub set: tc::PublicKeySet, + /// The public key share that this node holds + pub node: tc::PublicKeyShare, + /// The portion of the KeyShare this node holds + pub nonce: u64, +} + +impl PubKey { + /// Testing only random key generation + #[allow(dead_code)] + pub fn random(nonce: u64) -> PubKey { + let sks = tc::SecretKeySet::random(1, &mut rand::thread_rng()); + let set = sks.public_keys(); + let node = set.public_key_share(nonce); + PubKey { set, node, nonce } + } + /// Temporary escape hatch to generate a `PubKey` from a `SecretKeySet` and a node id + /// + /// This _will_ be removed when shared secret generation is implemented. For now, it exists to + /// solve the resulting chicken and egg problem. + #[allow(clippy::similar_names)] + pub fn from_secret_key_set_escape_hatch(sks: &tc::SecretKeySet, node_id: u64) -> Self { + let pks = sks.public_keys(); + let tc_pub_key = pks.public_key_share(node_id); + PubKey { + set: pks, + node: tc_pub_key, + nonce: node_id, + } + } +} + +impl PartialOrd for PubKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.nonce.partial_cmp(&other.nonce) + } +} + +impl Ord for PubKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.nonce.cmp(&other.nonce) + } +} + +impl Debug for PubKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PubKey").field("id", &self.nonce).finish() + } +} + +/// Private key stub type +/// +/// Opaque wrapper around `threshold_crypto` key +#[derive(Clone, Debug)] +pub struct PrivKey { + /// This node's share of the overall secret key + pub node: tc::SecretKeyShare, +} + +impl PrivKey { + /// Uses this private key to produce a partial signature for the given block hash + #[must_use] + pub fn partial_sign( + &self, + hash: &LeafHash, + stage: Stage, + view: u64, + ) -> tc::SignatureShare { + let blockhash = create_verify_hash(hash, view, stage); + self.node.sign(blockhash) + } +} diff --git a/src/types/message.rs b/phaselock-types/src/message.rs similarity index 97% rename from src/types/message.rs rename to phaselock-types/src/message.rs index 3c11f8bc66..8384307f7a 100644 --- a/src/types/message.rs +++ b/phaselock-types/src/message.rs @@ -1,7 +1,7 @@ //! Network message types //! //! This module contains types used to represent the various types of messages that -//! [`PhaseLock`](crate::PhaseLock) nodes can send among themselves. +//! `PhaseLock` nodes can send among themselves. use crate::data::Stage; use hex_fmt::HexFmt; @@ -10,10 +10,7 @@ use threshold_crypto::SignatureShare; use std::fmt::Debug; -use crate::{ - data::{Leaf, LeafHash}, - QuorumCertificate, -}; +use crate::data::{Leaf, LeafHash, QuorumCertificate}; #[derive(Serialize, Deserialize, Clone, Debug)] /// Enum representation of any message type diff --git a/phaselock-types/src/traits.rs b/phaselock-types/src/traits.rs new file mode 100644 index 0000000000..2c36a2b572 --- /dev/null +++ b/phaselock-types/src/traits.rs @@ -0,0 +1,10 @@ +//! Common traits for the `PhaseLock` protocol +pub mod block_contents; +pub mod network; +pub mod node_implementation; +pub mod state; +pub mod stateful_handler; +pub mod storage; + +pub use block_contents::BlockContents; +pub use state::State; diff --git a/src/traits/block_contents.rs b/phaselock-types/src/traits/block_contents.rs similarity index 97% rename from src/traits/block_contents.rs rename to phaselock-types/src/traits/block_contents.rs index 206cd729b4..de1a96b99f 100644 --- a/src/traits/block_contents.rs +++ b/phaselock-types/src/traits/block_contents.rs @@ -53,12 +53,11 @@ pub trait BlockContents: fn hash_transaction(tx: &Self::Transaction) -> TransactionHash; /// Produces a hash for an arbitrary sequence of bytes /// - /// Used to produce hashes for internal [`PhaseLock`](crate::PhaseLock) control structures + /// Used to produce hashes for internal `PhaseLock` control structures fn hash_leaf(bytes: &[u8]) -> LeafHash; } /// Dummy implementation of `BlockContents` for unit tests -#[cfg(test)] pub mod dummy { #[allow(clippy::wildcard_imports)] use super::*; diff --git a/phaselock-types/src/traits/network.rs b/phaselock-types/src/traits/network.rs new file mode 100644 index 0000000000..44af9caf22 --- /dev/null +++ b/phaselock-types/src/traits/network.rs @@ -0,0 +1,110 @@ +//! Network access abstraction +//! +//! Contains types and traits used by `PhaseLock` to abstract over network access + +use async_tungstenite::tungstenite::error as werror; +use futures::future::BoxFuture; +use serde::{de::DeserializeOwned, Serialize}; +use snafu::Snafu; + +use crate::PubKey; + +/// A boxed future trait object with a static lifetime +pub type BoxedFuture = BoxFuture<'static, T>; + +/// Error type for networking +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum NetworkError { + /// A Listener failed to send a message + ListenerSend, + /// Could not deliver a message to a specified recipient + CouldNotDeliver, + /// Attempted to deliver a message to an unknown node + NoSuchNode, + /// Failed to serialize a message + FailedToSerialize { + /// Originating bincode error + source: bincode::Error, + }, + /// Failed to deserealize a message + FailedToDeserialize { + /// originating bincode error + source: bincode::Error, + }, + /// WebSockets specific error + WebSocket { + /// Originating websockets error + source: werror::Error, + }, + /// Error orginiating from within the executor + ExecutorError { + /// Originating async_std error + source: async_std::io::Error, + }, + /// Failed to decode a socket specification + SocketDecodeError { + /// Input that was given + input: String, + /// Originating io error + source: std::io::Error, + }, + /// Failed to bind a listener socket + FailedToBindListener { + /// originating io error + source: std::io::Error, + }, + /// No sockets were open + NoSocketsError { + /// Input that was given + input: String, + }, + /// Generic error type for compatibility if needed + Other { + /// Originating error + inner: Box, + }, + /// Channel error + ChannelSend, + /// Could not complete handshake + IdentityHandshake, + /// The underlying connection has been shut down + ShutDown, +} + +/// Describes, generically, the behaviors a networking implementation must have +pub trait NetworkingImplementation: Send + Sync +where + M: Serialize + DeserializeOwned + Send + Clone + 'static, +{ + /// Broadcasts a message to the network + /// + /// Should provide that the message eventually reach all non-faulty nodes + fn broadcast_message(&self, message: M) -> BoxFuture<'_, Result<(), NetworkError>>; + /// Sends a direct message to a specific node + fn message_node( + &self, + message: M, + recipient: PubKey, + ) -> BoxFuture<'_, Result<(), NetworkError>>; + /// Moves out the entire queue of received broadcast messages, should there be any + /// + /// Provided as a future to allow the backend to do async locking + fn broadcast_queue(&self) -> BoxFuture<'_, Result, NetworkError>>; + /// Provides a future for the next received broadcast + /// + /// Will unwrap the underlying `NetworkMessage` + fn next_broadcast(&self) -> BoxFuture<'_, Result>; + /// Moves out the entire queue of received direct messages to this node + fn direct_queue(&self) -> BoxFuture<'_, Result, NetworkError>>; + /// Provides a future for the next received direct message to this node + /// + /// Will unwrap the underlying `NetworkMessage` + fn next_direct(&self) -> BoxFuture<'_, Result>; + /// Node's currently known to the networking implementation + /// + /// Kludge function to work around leader election + fn known_nodes(&self) -> BoxFuture<'_, Vec>; + /// Object safe clone + fn obj_clone(&self) -> Box + 'static>; +} diff --git a/phaselock-types/src/traits/node_implementation.rs b/phaselock-types/src/traits/node_implementation.rs new file mode 100644 index 0000000000..37876f32dc --- /dev/null +++ b/phaselock-types/src/traits/node_implementation.rs @@ -0,0 +1,41 @@ +//! Composite trait for node behavior +//! +//! This module defines the [`NodeImplementation`] trait, which is a composite trait used for +//! describing the overall behavior of a node, as a composition of implementations of the node trait. + +use std::fmt::Debug; + +use crate::{ + message::Message, + traits::{ + network::NetworkingImplementation, stateful_handler::StatefulHandler, storage::Storage, + BlockContents, + }, +}; + +/// Node implementation aggregate trait +/// +/// This trait exists to collect multiple behavior implementations into one type, to allow +/// `PhaseLock` to avoid annoying numbers of type arguments and type patching. +/// +/// It is recommended you implement this trait on a zero sized type, as `PhaseLock`does not actually +/// store or keep a reference to any value implementing this trait. +pub trait NodeImplementation: Send + Sync + Debug + Clone + 'static { + /// Block type for this consensus implementation + type Block: BlockContents + 'static; + /// State type for this consensus implementation + type State: crate::traits::State; + /// Storage type for this consensus implementation + type Storage: Storage + Clone; + /// Networking type for this consensus implementation + type Networking: NetworkingImplementation< + Message< + Self::Block, + <>::Block as BlockContents>::Transaction, + Self::State, + N, + >, + > + Clone; + /// Stateful call back handler for this consensus implementation + type StatefulHandler: StatefulHandler; +} diff --git a/src/traits/state.rs b/phaselock-types/src/traits/state.rs similarity index 97% rename from src/traits/state.rs rename to phaselock-types/src/traits/state.rs index 8264c94e17..d0849829e3 100644 --- a/src/traits/state.rs +++ b/phaselock-types/src/traits/state.rs @@ -7,7 +7,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::{error::Error, fmt::Debug, hash::Hash}; -use crate::traits::block_contents::BlockContents; +use crate::traits::BlockContents; /// Abstraction over the state that blocks modify /// @@ -43,7 +43,6 @@ pub trait State: } /// Dummy implementation of `State` for unit tests -#[cfg(test)] pub mod dummy { #[allow(clippy::wildcard_imports)] use super::*; diff --git a/src/traits/stateful_handler.rs b/phaselock-types/src/traits/stateful_handler.rs similarity index 81% rename from src/traits/stateful_handler.rs rename to phaselock-types/src/traits/stateful_handler.rs index babf877aeb..65b6cb5f8c 100644 --- a/src/traits/stateful_handler.rs +++ b/phaselock-types/src/traits/stateful_handler.rs @@ -2,12 +2,12 @@ use std::{fmt::Debug, marker::PhantomData}; -use super::{BlockContents, State}; +use crate::traits::{BlockContents, State}; /// Trait for a stateful event handler /// -/// The [`PhaseLock`](crate::PhaseLock) instance will keep around the provided value of this type, -/// and call the `notify` method every time a series of blocks are committed. +/// The `PhaseLock` instance will keep around the provided value of this type, and call the `notify` +/// method every time a series of blocks are committed. /// /// A do-nothing implementation ([`Stateless`]) is provided, as a convince for implementations that /// do not need this functionality. @@ -17,8 +17,8 @@ pub trait StatefulHandler: Send + Sync + Debug + 'static { /// State type for this consensus implementation type State: State; - /// The [`PhaseLock`](crate::PhaseLock) implementation will call this method, with the series of - /// blocks and states that are being committed, whenever a commit action takes place. + /// The `PhaseLock` implementation will call this method, with the series of blocks and states + /// that are being committed, whenever a commit action takes place. /// /// The provided states and blocks are guaranteed to be in ascending order of age (newest to /// oldest). diff --git a/phaselock-types/src/traits/storage.rs b/phaselock-types/src/traits/storage.rs new file mode 100644 index 0000000000..c8001ab461 --- /dev/null +++ b/phaselock-types/src/traits/storage.rs @@ -0,0 +1,93 @@ +//! Abstraction over on-disk storage of node state + +use futures::future::BoxFuture; + +use crate::{ + data::{BlockHash, Leaf, LeafHash, QuorumCertificate}, + traits::{BlockContents, State}, +}; + +/// Result for a storage type +#[derive(Debug)] +pub enum StorageResult { + /// The item was located in storage + Some(T), + /// The item was not found + None, + /// An error occurred + Err(Box), +} + +impl StorageResult { + /// Returns true if the result is a `Some` + pub fn is_some(&self) -> bool { + matches!(self, StorageResult::Some(_)) + } + /// Returns true if the result is a `None` + pub fn is_none(&self) -> bool { + matches!(self, StorageResult::None) + } + /// Returns true if the result is a `Err` + pub fn is_err(&self) -> bool { + matches!(self, StorageResult::Err(_)) + } + /// Converts to an option + pub fn ok(self) -> Option { + match self { + StorageResult::Some(x) => Some(x), + StorageResult::None | StorageResult::Err(_) => None, + } + } + /// Unwraps a `Some` value, panicking otherwise, this is a testing only function + /// + /// TODO: This is testing only, find a better way to mark as such + #[allow(clippy::panic, clippy::missing_panics_doc)] + pub fn unwrap(self) -> T { + if let StorageResult::Some(x) = self { + x + } else { + panic!("Unwrapped an empty/error value!"); + } + } +} + +/// Abstraction over on disk persistence of node state +/// +/// This should be a cloneable handle to an underlying storage, with each clone pointing to the same +/// underlying storage. +/// +/// This trait has been constructed for object saftey over convenience. +pub trait Storage + 'static, S: State + 'static, const N: usize>: + Clone + Send + Sync +{ + /// Retrieves a block from storage, returning `None` if it could not be found in local storage + fn get_block<'b, 'a: 'b>(&'a self, hash: &'b BlockHash) -> BoxFuture<'b, StorageResult>; + /// Inserts a block into storage + fn insert_block(&self, hash: BlockHash, block: B) -> BoxFuture<'_, StorageResult<()>>; + /// Retrieves a Quorum Certificate from storage, by the hash of the block it refers to + fn get_qc<'b, 'a: 'b>( + &'a self, + hash: &'b BlockHash, + ) -> BoxFuture<'b, StorageResult>>; + /// Retrieves the Quorum Certificate associated with a particular view number + fn get_qc_for_view(&self, view: u64) -> BoxFuture<'_, StorageResult>>; + /// Inserts a Quorum Certificate into the storage. Should reject the QC if it is malformed or + /// not from a decide stage + fn insert_qc(&self, qc: QuorumCertificate) -> BoxFuture<'_, StorageResult<()>>; + /// Retrieves a leaf by its hash + fn get_leaf<'b, 'a: 'b>( + &'a self, + hash: &'b LeafHash, + ) -> BoxFuture<'b, StorageResult>>; + /// Retrieves a leaf by the hash of its block + fn get_leaf_by_block<'b, 'a: 'b>( + &'a self, + hash: &'b BlockHash, + ) -> BoxFuture<'b, StorageResult>>; + /// Inserts a leaf + fn insert_leaf(&self, leaf: Leaf) -> BoxFuture<'_, StorageResult<()>>; + /// Inserts a `State`, indexed by the hash of the `Leaf` that created it + fn insert_state(&self, state: S, hash: LeafHash) -> BoxFuture<'_, StorageResult<()>>; + /// Retrieves a `State`, indexed by the hash of the `Leaf` that created it + fn get_state<'b, 'a: 'b>(&'a self, hash: &'b LeafHash) -> BoxFuture<'_, StorageResult>; +} diff --git a/src/data.rs b/src/data.rs index 12b9b631f6..ef20294b1e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,357 +4,7 @@ //! [`PhaseLock`](crate::PhaseLock)'s version of a block, and the [`QuorumCertificate`], //! representing the threshold signatures fundamental to consensus. -use blake3::Hasher; -use hex_fmt::HexFmt; -use serde::{Deserialize, Serialize}; - -use std::fmt::Debug; -use threshold_crypto as tc; - -use crate::traits::BlockContents; - -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash)] -/// A node in [`PhaseLock`](crate::PhaseLock)'s consensus-internal merkle tree. -/// -/// This is the consensus-internal analogous concept to a block, and it contains the block proper, -/// as well as the hash of its parent `Leaf`. -pub struct Leaf { - /// The hash of the parent `Leaf` - pub parent: LeafHash, - /// The block contained in this `Leaf` - pub item: T, -} - -impl, const N: usize> Leaf { - /// Creates a new leaf with the specified block and parent - /// - /// # Arguments - /// * `item` - The block to include - /// * `parent` - The hash of the `Leaf` that is to be the parent of this `Leaf` - pub fn new(item: T, parent: LeafHash) -> Self { - Leaf { parent, item } - } - - /// Hashes the leaf with the hashing algorithm provided by the [`BlockContents`] implementation - /// - /// This will concatenate the `parent` hash with the [`BlockContents`] provided hash of the - /// contained block, and then return the hash of the resulting concatenated byte string. - pub fn hash(&self) -> LeafHash { - let mut bytes = Vec::::new(); - bytes.extend_from_slice(self.parent.as_ref()); - bytes.extend_from_slice(BlockContents::hash(&self.item).as_ref()); - T::hash_leaf(&bytes) - } -} - -impl Debug for Leaf { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("Leaf<{}>", std::any::type_name::())) - .field("item", &self.item) - .field("parent", &format!("{:12}", HexFmt(&self.parent))) - .finish() - } -} - -/// The type used for Quorum Certificates -/// -/// A Quorum Certificate is a threshold signature of the [`Leaf`] being proposed, as well as some -/// metadata, such as the [`Stage`] of consensus the quorum certificate was generated during. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct QuorumCertificate { - /// Hash of the block refereed to by this Quorum Certificate. - /// - /// This is included for convenience, and is not fundamental to consensus or covered by the - /// signature. This _must_ be identical to the [`BlockContents`] provided hash of the `item` in - /// the referenced leaf. - pub(crate) block_hash: BlockHash, - /// Hash of the [`Leaf`] referred to by this Quorum Certificate - /// - /// This value is covered by the threshold signature. - pub(crate) leaf_hash: LeafHash, - /// The view number this quorum certificate was generated during - /// - /// This value is covered by the threshold signature. - pub(crate) view_number: u64, - /// The [`Stage`] of consensus that this Quorum Certificate was generated during - /// - /// This value is covered by the threshold signature. - pub(crate) stage: Stage, - /// The threshold signature associated with this Quorum Certificate. - /// - /// This is nullable as part of a temporary mechanism to support bootstrapping from a genesis block, as - /// the genesis block can not be produced through the normal means. - pub(crate) signature: Option, - /// Temporary bypass for boostrapping - /// - /// This value indicates that this is a dummy certificate for the genesis block, and thus does not have - /// a signature. This value is not covered by the signature, and it is invalid for this to be set - /// outside of bootstrap - pub(crate) genesis: bool, -} - -impl QuorumCertificate { - /// Verifies a quorum certificate - /// - /// This concatenates the encoding of the [`Leaf`] hash, the `view_number`, and the `stage`, in - /// that order, and makes sure that the associated signature validates against the resulting - /// byte string. - /// - /// If the `genesis` value is set, this disables the normal checking, and instead performs - /// bootstrap checking. - #[must_use] - pub fn verify(&self, key: &tc::PublicKeySet, view: u64, stage: Stage) -> bool { - if let Some(signature) = &self.signature { - let concatenated_hash = create_verify_hash(&self.leaf_hash, view, stage); - key.public_key().verify(signature, &concatenated_hash) - } else { - self.genesis - } - } - - /// Converts this Quorum Certificate to a version using a `Vec` rather than a const-generic - /// array. - /// - /// This is useful for erasing the const-generic length for error types and logging, but is not - /// directly consensus relevant. - pub fn to_vec_cert(&self) -> VecQuorumCertificate { - VecQuorumCertificate { - hash: self.block_hash.as_ref().to_vec(), - view_number: self.view_number, - stage: self.stage, - signature: self.signature.clone(), - genesis: self.genesis, - } - } -} - -impl Debug for QuorumCertificate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("QuorumCertificate") - .field("hash", &format!("{:12}", HexFmt(&self.leaf_hash))) - .field("view_number", &self.view_number) - .field("stage", &self.stage) - .field("signature", &self.signature) - .field("genesis", &self.genesis) - .finish() - } -} - -/// [`QuorumCertificate`] variant using a `Vec` rather than a const-generic array -/// -/// This type mainly exists to work around an issue with -/// [`snafu`](https://github.com/shepmaster/snafu) when used with const-generics, by erasing the -/// const-generic length. -/// -/// This type is not used directly by consensus. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] -pub struct VecQuorumCertificate { - /// Block this QC refers to - pub(crate) hash: Vec, - /// The view we were on when we made this certificate - pub(crate) view_number: u64, - /// The stage of consensus we were on when we made this certificate - pub(crate) stage: Stage, - /// The signature portion of this QC - pub(crate) signature: Option, - /// Temporary bypass for boostrapping - pub(crate) genesis: bool, -} - -impl Debug for VecQuorumCertificate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("QuorumCertificate") - .field("hash", &format!("{:12}", HexFmt(&self.hash))) - .field("view_number", &self.view_number) - .field("stage", &self.stage) - .field("signature", &self.signature) - .field("genesis", &self.genesis) - .finish() - } -} - -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)] -/// Represents the stages of consensus -pub enum Stage { - /// Between rounds - None, - /// Prepare Phase - Prepare, - /// PreCommit Phase - PreCommit, - /// Commit Phase - Commit, - /// Decide Phase - Decide, -} - -/// This concatenates the encoding of `leaf_hash`, `view`, and `stage`, in -/// that order, and hashes the result. -pub fn create_verify_hash( - leaf_hash: &LeafHash, - view: u64, - stage: Stage, -) -> VerifyHash<32> { - let mut hasher = Hasher::new(); - hasher.update(leaf_hash.as_ref()); - hasher.update(&view.to_be_bytes()); - hasher.update(&(stage as u64).to_be_bytes()); - let hash = hasher.finalize(); - VerifyHash::from_array(*hash.as_bytes()) -} - -/// generates boilerplate code for any wrapper types -/// around `InternalHash` -macro_rules! gen_hash_wrapper_type { - ($t:ident) => { - #[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)] - /// External wrapper type - pub struct $t { - inner: InternalHash, - } - - impl $t { - /// Converts an array of the correct size directly into an `Self` - pub fn from_array(input: [u8; N]) -> Self { - $t { - inner: InternalHash::from_array(input), - } - } - /// Clones the contents of this Hash into a `Vec` - pub fn to_vec(self) -> Vec { - self.inner.to_vec() - } - #[cfg(test)] - pub fn random() -> Self { - $t { - inner: InternalHash::random(), - } - } - } - impl AsRef<[u8]> for $t { - fn as_ref(&self) -> &[u8] { - self.inner.as_ref() - } - } - - impl From<[u8; N]> for $t { - fn from(input: [u8; N]) -> Self { - Self::from_array(input) - } - } - - impl Default for $t { - fn default() -> Self { - $t { - inner: InternalHash::default(), - } - } - } - impl Debug for $t { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("{}", std::any::type_name::<$t>())) - .field("inner", &format!("{}", HexFmt(&self.inner))) - .finish() - } - } - }; -} - -gen_hash_wrapper_type!(BlockHash); -gen_hash_wrapper_type!(LeafHash); -gen_hash_wrapper_type!(TransactionHash); -gen_hash_wrapper_type!(VerifyHash); -gen_hash_wrapper_type!(StateHash); - -/// Internal type used for representing hashes -/// -/// This is a thin wrapper around a `[u8; N]` used to work around various issues with libraries that -/// have not updated to be const-generic aware. In particular, this provides a `serde` [`Serialize`] -/// and [`Deserialize`] implementation over the const-generic array, which `serde` normally does not -/// have for the general case. -#[derive(PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)] -struct InternalHash { - /// The underlying array - /// No support for const generics - #[serde(with = "serde_bytes_array")] - inner: [u8; N], -} - -impl Debug for InternalHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("InternalHash") - .field("inner", &format!("{}", HexFmt(&self.inner))) - .finish() - } -} - -impl InternalHash { - /// Converts an array of the correct size directly into an `InternalHash` - pub const fn from_array(input: [u8; N]) -> Self { - Self { inner: input } - } - - /// Clones the contents of this `InternalHash` into a `Vec` - pub fn to_vec(self) -> Vec { - self.inner.to_vec() - } - - /// Testing only random generation of a `InternalHash` - #[cfg(test)] - pub fn random() -> Self { - use rand::Rng; - let mut array = [0_u8; N]; - let mut rng = rand::thread_rng(); - rng.fill(&mut array[..]); - Self { inner: array } - } -} - -impl AsRef<[u8]> for InternalHash { - fn as_ref(&self) -> &[u8] { - &self.inner - } -} - -impl From<[u8; N]> for InternalHash { - fn from(input: [u8; N]) -> Self { - Self::from_array(input) - } -} - -impl Default for InternalHash { - fn default() -> Self { - InternalHash { - inner: [0_u8; { N }], - } - } -} - -/// [Needed](https://github.com/serde-rs/bytes/issues/26#issuecomment-902550669) to (de)serialize const generic arrays -mod serde_bytes_array { - use core::convert::TryInto; - - use serde::de::Error; - use serde::{Deserializer, Serializer}; - - /// This just specializes [`serde_bytes::serialize`] to ``. - pub(crate) fn serialize(bytes: &[u8], serializer: S) -> Result - where - S: Serializer, - { - serde_bytes::serialize(bytes, serializer) - } - - /// This takes the result of [`serde_bytes::deserialize`] from `[u8]` to `[u8; N]`. - pub(crate) fn deserialize<'de, D, const N: usize>(deserializer: D) -> Result<[u8; N], D::Error> - where - D: Deserializer<'de>, - { - let slice: &[u8] = serde_bytes::deserialize(deserializer)?; - let array: [u8; N] = slice.try_into().map_err(|_| { - let expected = format!("[u8; {}]", N); - D::Error::invalid_length(slice.len(), &expected.as_str()) - })?; - Ok(array) - } -} +pub use phaselock_types::data::{ + create_verify_hash, BlockHash, Leaf, LeafHash, QuorumCertificate, Stage, StateHash, + TransactionHash, VecQuorumCertificate, VerifyHash, +}; diff --git a/src/lib.rs b/src/lib.rs index f3faef72dd..ee8d39c8cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,23 +39,22 @@ pub mod types; pub mod utility; use std::fmt::Debug; -use std::hash::Hash; use std::sync::Arc; use std::time::{Duration, Instant}; use async_std::sync::{Mutex, RwLock}; use async_std::task::{spawn, yield_now, JoinHandle}; -use data::create_verify_hash; -use serde::{Deserialize, Serialize}; use snafu::ResultExt; use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument}; +use phaselock_types::error::NetworkFaultSnafu; + use crate::{ data::{Leaf, LeafHash, QuorumCertificate, Stage}, traits::{BlockContents, NetworkingImplementation, NodeImplementation, Storage, StorageResult}, types::{ - error::NetworkFaultSnafu, Commit, Decide, Event, EventType, Message, NewView, - PhaseLockHandle, PreCommit, Prepare, Vote, + Commit, Decide, Event, EventType, Message, NewView, PhaseLockHandle, PreCommit, Prepare, + Vote, }, utility::{ broadcast::BroadcastSender, @@ -63,12 +62,17 @@ use crate::{ }, }; -pub use crate::types::error::PhaseLockError; - +// -- Rexports +// External /// Reexport rand crate pub use rand; /// Reexport threshold crypto crate pub use threshold_crypto as tc; +// Internal +/// Reexport error type +pub use phaselock_types::error::PhaseLockError; +/// Reexport key types +pub use phaselock_types::{PrivKey, PubKey}; /// Length, in bytes, of a 512 bit hash pub const H_512: usize = 64; @@ -78,84 +82,6 @@ pub const H_256: usize = 32; /// Convenience type alias type Result = std::result::Result; -/// Public key type -/// -/// Opaque wrapper around `threshold_crypto` key -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] -pub struct PubKey { - /// Overall public key set for the network - set: tc::PublicKeySet, - /// The public key share that this node holds - node: tc::PublicKeyShare, - /// The portion of the KeyShare this node holds - pub nonce: u64, -} - -impl PubKey { - /// Testing only random key generation - #[allow(dead_code)] - pub(crate) fn random(nonce: u64) -> PubKey { - let sks = tc::SecretKeySet::random(1, &mut rand::thread_rng()); - let set = sks.public_keys(); - let node = set.public_key_share(nonce); - PubKey { set, node, nonce } - } - /// Temporary escape hatch to generate a `PubKey` from a `SecretKeySet` and a node id - /// - /// This _will_ be removed when shared secret generation is implemented. For now, it exists to - /// solve the resulting chicken and egg problem. - pub fn from_secret_key_set_escape_hatch(sks: &tc::SecretKeySet, node_id: u64) -> Self { - let pks = sks.public_keys(); - let tc_pub_key = pks.public_key_share(node_id); - PubKey { - set: pks, - node: tc_pub_key, - nonce: node_id, - } - } -} - -impl PartialOrd for PubKey { - fn partial_cmp(&self, other: &Self) -> Option { - self.nonce.partial_cmp(&other.nonce) - } -} - -impl Ord for PubKey { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.nonce.cmp(&other.nonce) - } -} - -impl Debug for PubKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PubKey").field("id", &self.nonce).finish() - } -} - -/// Private key stub type -/// -/// Opaque wrapper around `threshold_crypto` key -#[derive(Clone, Debug)] -pub struct PrivKey { - /// This node's share of the overall secret key - pub(crate) node: tc::SecretKeyShare, -} - -impl PrivKey { - /// Uses this private key to produce a partial signature for the given block hash - #[must_use] - pub fn partial_sign( - &self, - hash: &LeafHash, - stage: Stage, - view: u64, - ) -> tc::SignatureShare { - let blockhash = create_verify_hash(hash, view, stage); - self.node.sign(blockhash) - } -} - /// Holds configuration for a `PhaseLock` #[derive(Debug, Clone)] pub struct PhaseLockConfig { diff --git a/src/state_machine.rs b/src/state_machine.rs index 09705080bd..ebe1daaa65 100644 --- a/src/state_machine.rs +++ b/src/state_machine.rs @@ -19,16 +19,17 @@ use std::{ use futures::{future::BoxFuture, Future, FutureExt}; use tracing::{info, instrument}; +use phaselock_types::error::{FailedToBroadcastSnafu, FailedToMessageLeaderSnafu, PhaseLockError}; + use super::{ debug, error, generate_qc, trace, warn, yield_now, Arc, Commit, Debug, Decide, Event, - EventType, Leaf, LeafHash, Message, PhaseLock, PhaseLockError, PreCommit, Prepare, PubKey, - QuorumCertificate, Result, ResultExt, Stage, Vote, + EventType, Leaf, LeafHash, Message, PhaseLock, PreCommit, Prepare, PubKey, QuorumCertificate, + Result, ResultExt, Stage, Vote, }; use crate::{ traits::{ BlockContents, NetworkingImplementation, State, StatefulHandler, Storage, StorageResult, }, - types::error::{FailedToBroadcastSnafu, FailedToMessageLeaderSnafu}, utility::broadcast::BroadcastSender, NodeImplementation, }; diff --git a/src/traits.rs b/src/traits.rs index a26e58482c..3b2ab34271 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,30 +1,27 @@ -mod block_contents; /// Sortition trait pub mod election; mod networking; mod node_implementation; -mod state; -mod stateful_handler; mod storage; -pub use block_contents::BlockContents; pub use election::Election; pub use networking::{BoxedFuture, NetworkError, NetworkReliability, NetworkingImplementation}; pub use node_implementation::NodeImplementation; -pub use state::State; -pub use stateful_handler::StatefulHandler; +pub use phaselock_types::traits::stateful_handler::StatefulHandler; +pub use phaselock_types::traits::BlockContents; +pub use phaselock_types::traits::State; pub use storage::{Storage, StorageResult}; /// Module for publicly usable implementations of the traits pub mod implementations { pub use super::networking::memory_network::{DummyReliability, MasterMap, MemoryNetwork}; pub use super::networking::w_network::WNetwork; - pub use super::stateful_handler::Stateless; pub use super::storage::memory_storage::MemoryStorage; + pub use phaselock_types::traits::stateful_handler::Stateless; } /// Dummy testing implementations #[cfg(test)] pub mod dummy { - pub use super::state::dummy::DummyState; + pub use phaselock_types::traits::state::dummy::DummyState; } diff --git a/src/traits/networking.rs b/src/traits/networking.rs index b12a81eb04..4bf4c1c86a 100644 --- a/src/traits/networking.rs +++ b/src/traits/networking.rs @@ -19,116 +19,9 @@ use std::time::Duration; pub mod memory_network; pub mod w_network; -/// A boxed future trait object with a static lifetime -pub type BoxedFuture = BoxFuture<'static, T>; - -/// Error type for networking -#[derive(Debug, Snafu)] -#[snafu(visibility(pub(crate)))] -pub enum NetworkError { - /// A Listener failed to send a message - ListenerSend, - /// Could not deliver a message to a specified recipient - CouldNotDeliver, - /// Attempted to deliver a message to an unknown node - NoSuchNode, - /// Failed to serialize a message - FailedToSerialize { - /// Originating bincode error - source: bincode::Error, - }, - /// Failed to deserealize a message - FailedToDeserialize { - /// originating bincode error - source: bincode::Error, - }, - /// WebSockets specific error - WebSocket { - /// Originating websockets error - source: werror::Error, - }, - /// Error orginiating from within the executor - ExecutorError { - /// Originating async_std error - source: async_std::io::Error, - }, - /// Failed to decode a socket specification - SocketDecodeError { - /// Input that was given - input: String, - /// Originating io error - source: std::io::Error, - }, - /// Failed to bind a listener socket - FailedToBindListener { - /// originating io error - source: std::io::Error, - }, - /// No sockets were open - NoSocketsError { - /// Input that was given - input: String, - }, - /// Generic error type for compatibility if needed - Other { - /// Originating error - inner: Box, - }, - /// Channel error - ChannelSend, - /// Could not complete handshake - IdentityHandshake, - /// The underlying connection has been shut down - ShutDown, -} - -/// Describes, generically, the behaviors a networking implementation must have -pub trait NetworkingImplementation: Send + Sync -where - M: Serialize + DeserializeOwned + Send + Clone + 'static, -{ - /// Broadcasts a message to the network - /// - /// Should provide that the message eventually reach all non-faulty nodes - fn broadcast_message(&self, message: M) -> BoxFuture<'_, Result<(), NetworkError>>; - /// Sends a direct message to a specific node - fn message_node( - &self, - message: M, - recipient: PubKey, - ) -> BoxFuture<'_, Result<(), NetworkError>>; - /// Moves out the entire queue of received broadcast messages, should there be any - /// - /// Provided as a future to allow the backend to do async locking - fn broadcast_queue(&self) -> BoxFuture<'_, Result, NetworkError>>; - /// Provides a future for the next received broadcast - /// - /// Will unwrap the underlying `NetworkMessage` - fn next_broadcast(&self) -> BoxFuture<'_, Result>; - /// Moves out the entire queue of received direct messages to this node - fn direct_queue(&self) -> BoxFuture<'_, Result, NetworkError>>; - /// Provides a future for the next received direct message to this node - /// - /// Will unwrap the underlying `NetworkMessage` - fn next_direct(&self) -> BoxFuture<'_, Result>; - /// Node's currently known to the networking implementation - /// - /// Kludge function to work around leader election - fn known_nodes(&self) -> BoxFuture<'_, Vec>; - /// Object safe clone - fn obj_clone(&self) -> Box + 'static>; -} - -/// interface describing how reliable the network is -pub trait NetworkReliability: std::fmt::Debug + Sync + std::marker::Send + Copy { - /// Sample from bernoulli distribution to decide whether - /// or not to keep a packet - /// # Panics - /// - /// Panics if `self.keep_numerator > self.keep_denominator` - /// - fn sample_keep(&self) -> bool; - /// sample from uniform distribution to decide whether - /// or not to keep a packet - fn sample_delay(&self) -> Duration; -} +pub use phaselock_types::traits::network::{ + BoxedFuture, ChannelSendSnafu, CouldNotDeliverSnafu, ExecutorSnafu, FailedToBindListenerSnafu, + FailedToDeserializeSnafu, FailedToSerializeSnafu, IdentityHandshakeSnafu, ListenerSendSnafu, + NetworkError, NetworkingImplementation, NoSocketsSnafu, NoSuchNodeSnafu, OtherSnafu, + ShutDownSnafu, SocketDecodeSnafu, WebSocketSnafu, +}; diff --git a/src/traits/node_implementation.rs b/src/traits/node_implementation.rs index 8cafa8df18..8fb141f3f8 100644 --- a/src/traits/node_implementation.rs +++ b/src/traits/node_implementation.rs @@ -3,39 +3,4 @@ //! This module defines the [`NodeImplementation`] trait, which is a composite trait used for //! describing the overall behavior of a node, as a composition of implementations of the node trait. -use std::fmt::Debug; - -use crate::{ - traits::{state, BlockContents, NetworkingImplementation, Storage}, - types::Message, -}; - -use super::StatefulHandler; - -/// Node implementation aggregate trait -/// -/// This trait exists to collect multiple behavior implementations into one type, to allow -/// [`PhaseLock`](crate::PhaseLock) to avoid annoying numbers of type arguments and type patching. -/// -/// It is recommended you implement this trait on a zero sized type, as -/// [`PhaseLock`](crate::PhaseLock) does not actually store or keep a reference to any value -/// implementing this trait. -pub trait NodeImplementation: Send + Sync + Debug + Clone + 'static { - /// Block type for this consensus implementation - type Block: BlockContents + 'static; - /// State type for this consensus implementation - type State: state::State; - /// Storage type for this consensus implementation - type Storage: Storage + Clone; - /// Networking type for this consensus implementation - type Networking: NetworkingImplementation< - Message< - Self::Block, - <>::Block as BlockContents>::Transaction, - Self::State, - N, - >, - > + Clone; - /// Stateful call back handler for this consensus implementation - type StatefulHandler: StatefulHandler; -} +pub use phaselock_types::traits::node_implementation::NodeImplementation; diff --git a/src/traits/storage.rs b/src/traits/storage.rs index 0fb0757d88..8b373f5028 100644 --- a/src/traits/storage.rs +++ b/src/traits/storage.rs @@ -1,97 +1,4 @@ //! Abstraction over on-disk storage of node state - -use futures::future::BoxFuture; - -use crate::{ - data::{BlockHash, Leaf, LeafHash}, - traits::BlockContents, - QuorumCertificate, -}; - -use super::state::State; - pub mod memory_storage; -/// Result for a storage type -#[derive(Debug)] -pub enum StorageResult { - /// The item was located in storage - Some(T), - /// The item was not found - None, - /// An error occurred - Err(Box), -} - -impl StorageResult { - /// Returns true if the result is a `Some` - pub fn is_some(&self) -> bool { - matches!(self, StorageResult::Some(_)) - } - /// Returns true if the result is a `None` - pub fn is_none(&self) -> bool { - matches!(self, StorageResult::None) - } - /// Returns true if the result is a `Err` - pub fn is_err(&self) -> bool { - matches!(self, StorageResult::Err(_)) - } - /// Converts to an option - pub fn ok(self) -> Option { - match self { - StorageResult::Some(x) => Some(x), - StorageResult::None | StorageResult::Err(_) => None, - } - } - /// Unwraps a `Some` value, panicking otherwise, this is a testing only function - #[cfg(test)] - #[allow(clippy::panic, clippy::missing_panics_doc)] - pub fn unwrap(self) -> T { - if let StorageResult::Some(x) = self { - x - } else { - panic!("Unwrapped an empty/error value!"); - } - } -} - -/// Abstraction over on disk persistence of node state; -/// -/// This should be a cloneable handle to an underlying storage, with each clone pointing to the same -/// underlying storage. -/// -/// This trait has been constructed for object saftey over convenience. -pub trait Storage + 'static, S: State + 'static, const N: usize>: - Clone + Send + Sync -{ - /// Retrieves a block from storage, returning `None` if it could not be found in local storage - fn get_block<'b, 'a: 'b>(&'a self, hash: &'b BlockHash) -> BoxFuture<'b, StorageResult>; - /// Inserts a block into storage - fn insert_block(&self, hash: BlockHash, block: B) -> BoxFuture<'_, StorageResult<()>>; - /// Retrieves a Quorum Certificate from storage, by the hash of the block it refers to - fn get_qc<'b, 'a: 'b>( - &'a self, - hash: &'b BlockHash, - ) -> BoxFuture<'b, StorageResult>>; - /// Retrieves the Quorum Certificate associated with a particular view number - fn get_qc_for_view(&self, view: u64) -> BoxFuture<'_, StorageResult>>; - /// Inserts a Quorum Certificate into the storage. Should reject the QC if it is malformed or - /// not from a decide stage - fn insert_qc(&self, qc: QuorumCertificate) -> BoxFuture<'_, StorageResult<()>>; - /// Retrieves a leaf by its hash - fn get_leaf<'b, 'a: 'b>( - &'a self, - hash: &'b LeafHash, - ) -> BoxFuture<'b, StorageResult>>; - /// Retrieves a leaf by the hash of its block - fn get_leaf_by_block<'b, 'a: 'b>( - &'a self, - hash: &'b BlockHash, - ) -> BoxFuture<'b, StorageResult>>; - /// Inserts a leaf - fn insert_leaf(&self, leaf: Leaf) -> BoxFuture<'_, StorageResult<()>>; - /// Inserts a `State`, indexed by the hash of the `Leaf` that created it - fn insert_state(&self, state: S, hash: LeafHash) -> BoxFuture<'_, StorageResult<()>>; - /// Retrieves a `State`, indexed by the hash of the `Leaf` that created it - fn get_state<'b, 'a: 'b>(&'a self, hash: &'b LeafHash) -> BoxFuture<'_, StorageResult>; -} +pub use phaselock_types::traits::storage::{Storage, StorageResult}; diff --git a/src/traits/storage/memory_storage.rs b/src/traits/storage/memory_storage.rs index b721dbd0dc..45e34f8d95 100644 --- a/src/traits/storage/memory_storage.rs +++ b/src/traits/storage/memory_storage.rs @@ -12,9 +12,8 @@ use std::sync::Arc; use crate::{ data::{BlockHash, Leaf, LeafHash}, traits::{ - block_contents::BlockContents, - state::State, storage::{Storage, StorageResult}, + BlockContents, State, }, QuorumCertificate, }; @@ -245,9 +244,9 @@ impl + 'static, S: State + 'static, const N: u mod test { use super::*; use crate::data::Stage; - #[allow(clippy::wildcard_imports)] - use crate::traits::block_contents::dummy::*; use crate::utility::test_util::setup_logging; + #[allow(clippy::wildcard_imports)] + use phaselock_types::traits::block_contents::dummy::*; use tracing::instrument; fn dummy_qc( diff --git a/src/types.rs b/src/types.rs index e99abd90d9..ac4a7e2f50 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,13 +1,10 @@ -pub(crate) mod error; mod event; mod handle; -mod message; - -pub use error::PhaseLockError; pub use event::Event; pub use event::EventType; pub use handle::PhaseLockHandle; -pub use message::{Commit, Decide, Message, NewView, PreCommit, Prepare, Vote}; +pub(crate) use phaselock_types::error::PhaseLockError; +pub use phaselock_types::message::{Commit, Decide, Message, NewView, PreCommit, Prepare, Vote}; diff --git a/src/types/event.rs b/src/types/event.rs index f408c63db9..473ce092b8 100644 --- a/src/types/event.rs +++ b/src/types/event.rs @@ -1,76 +1,3 @@ //! Events that a [`PhaseLock`](crate::PhaseLock) instance can emit -use crate::{data::Stage, PhaseLockError}; - -use std::sync::Arc; - -/// A status event emitted by a [`PhaseLock`](crate::PhaseLock) instance -/// -/// This includes some metadata, such as the stage and view number that the event was generated in, -/// as well as an inner [`EventType`] describing the event proper. -#[non_exhaustive] -#[derive(Clone, Debug)] -pub struct Event { - /// The view number that this event originates from - pub view_number: u64, - /// The stage that this event originates from - pub stage: Stage, - /// The underlying event - pub event: EventType, -} - -/// The type and contents of a status event emitted by a [`PhaseLock`](crate::PhaseLock) instance -/// -/// This enum does not include metadata shared among all variants, such as the stage and view -/// number, and is thus always returned wrapped in an [`Event`]. -#[non_exhaustive] -#[derive(Clone, Debug)] -pub enum EventType { - /// A view encountered an error and was interrupted - Error { - /// The underlying error - error: Arc, - }, - /// A new block was proposed - Propose { - /// The block that was proposed - block: Arc, - }, - /// A new decision event was issued - Decide { - /// The list of blocks that were committed by this decision - /// - /// This list is sorted in reverse view number order, with the newest (highest view number) - /// block first in the list. - /// - /// This list may be incomplete if the node is currently performing catchup. - block: Arc>, - /// The list of states that were committed by this decision - /// - /// This list is sorted in reverse view number order, with the newest (highest view number) - /// state first in the list. - /// - /// This list may be incomplete if the node is currently performing catchup. - state: Arc>, - }, - /// A new view was started by this node - NewView { - /// The view being started - view_number: u64, - }, - /// A view was canceled by a timeout interrupt - ViewTimeout { - /// The view that timed out - view_number: u64, - }, - /// This node is the leader for this view - Leader { - /// The current view number - view_number: u64, - }, - /// This node is a follower for this view - Follower { - /// The current view number - view_number: u64, - }, -} +pub use phaselock_types::event::{Event, EventType};