diff --git a/Cargo.lock b/Cargo.lock index 7bd8313a40c..bc7d0c2858c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,7 +1615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3685,10 +3685,12 @@ dependencies = [ name = "gfss" version = "0.1.0" dependencies = [ + "digest", "omicron-workspace-hack", "proptest", "rand 0.8.5", "secrecy", + "serde", "subtle", "test-strategy", "thiserror 1.0.69", @@ -5318,7 +5320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -13003,6 +13005,36 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "trust-quorum" +version = "0.1.0" +dependencies = [ + "bcs", + "bootstore", + "camino", + "chacha20poly1305", + "derive_more", + "gfss", + "hex", + "hkdf", + "omicron-uuid-kinds", + "omicron-workspace-hack", + "proptest", + "rand 0.8.5", + "secrecy", + "serde", + "serde_with", + "sha3", + "slog", + "slog-error-chain", + "subtle", + "test-strategy", + "thiserror 1.0.69", + "tokio", + "uuid", + "zeroize", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -14130,7 +14162,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d23df67e233..2626a4d53c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -131,6 +131,7 @@ members = [ "sled-storage", "sp-sim", "test-utils", + "trust-quorum", "trust-quorum/gfss", "typed-rng", "update-common", @@ -279,6 +280,7 @@ default-members = [ "sled-hardware/types", "sled-storage", "sp-sim", + "trust-quorum", "trust-quorum/gfss", "test-utils", "typed-rng", @@ -401,6 +403,7 @@ dev-tools-common = { path = "dev-tools/common" } # Having the i-implement-... feature here makes diesel go away from the workspace-hack diesel = { version = "2.2.9", features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes", "postgres", "r2d2", "chrono", "serde_json", "network-address", "uuid"] } diesel-dtrace = "0.4.2" +digest = "0.10.7" dns-server = { path = "dns-server" } dns-server-api = { path = "dns-server-api" } dns-service-client = { path = "clients/dns-service-client" } @@ -435,6 +438,7 @@ gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway- gateway-test-utils = { path = "gateway-test-utils" } gateway-types = { path = "gateway-types" } gethostname = "0.5.0" +gfss = { path = "trust-quorum/gfss" } glob = "0.3.2" guppy = "0.17.17" headers = "0.4.0" @@ -679,7 +683,7 @@ static_assertions = "1.1.0" steno = "0.4.1" strum = { version = "0.26", features = [ "derive" ] } subprocess = "0.2.9" -subtle = "2.6" +subtle = "2.6.1" supports-color = "3.0.2" swrite = "0.1.0" sync-ptr = "0.1.1" diff --git a/trust-quorum/Cargo.toml b/trust-quorum/Cargo.toml new file mode 100644 index 00000000000..c6e4c24cf59 --- /dev/null +++ b/trust-quorum/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "trust-quorum" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +bcs.workspace = true +bootstore.workspace = true +camino.workspace = true +chacha20poly1305.workspace = true +derive_more.workspace = true +gfss.workspace = true +hex.workspace = true +hkdf.workspace = true +rand = { workspace = true, features = ["getrandom"] } +secrecy.workspace = true +serde.workspace = true +serde_with.workspace = true +sha3.workspace = true +slog.workspace = true +slog-error-chain.workspace = true +subtle.workspace = true +thiserror.workspace = true +tokio.workspace = true +uuid.workspace = true +omicron-uuid-kinds.workspace = true +zeroize.workspace = true +omicron-workspace-hack.workspace = true + +[dev-dependencies] +proptest.workspace = true +test-strategy.workspace = true diff --git a/trust-quorum/gfss/Cargo.toml b/trust-quorum/gfss/Cargo.toml index d99557a8387..06b4e806626 100644 --- a/trust-quorum/gfss/Cargo.toml +++ b/trust-quorum/gfss/Cargo.toml @@ -9,8 +9,10 @@ license = "MPL-2.0" workspace = true [dependencies] +digest.workspace = true rand = { workspace = true, features = ["getrandom"] } secrecy.workspace = true +serde.workspace = true subtle.workspace = true thiserror.workspace = true zeroize.workspace = true diff --git a/trust-quorum/gfss/src/gf256.rs b/trust-quorum/gfss/src/gf256.rs index 3e9ea3d9888..8eac4543ad8 100644 --- a/trust-quorum/gfss/src/gf256.rs +++ b/trust-quorum/gfss/src/gf256.rs @@ -26,6 +26,7 @@ use core::fmt::{self, Binary, Display, Formatter, LowerHex, UpperHex}; use core::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub}; use rand::Rng; use rand::distributions::{Distribution, Standard}; +use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; use zeroize::Zeroize; @@ -34,9 +35,15 @@ use zeroize::Zeroize; /// We explicitly don't enable the equality operators to prevent ourselves from /// accidentally using those instead of the constant time ones. #[repr(transparent)] -#[derive(Debug, Clone, Copy, Zeroize)] +#[derive(Debug, Clone, Copy, Zeroize, Serialize, Deserialize)] pub struct Gf256(u8); +impl AsRef for Gf256 { + fn as_ref(&self) -> &u8 { + &self.0 + } +} + impl Gf256 { pub fn new(n: u8) -> Gf256 { Gf256(n) diff --git a/trust-quorum/gfss/src/shamir.rs b/trust-quorum/gfss/src/shamir.rs index b603aac296c..a5f4e58f467 100644 --- a/trust-quorum/gfss/src/shamir.rs +++ b/trust-quorum/gfss/src/shamir.rs @@ -4,8 +4,10 @@ //! Shamir secret sharing over GF(2^8) +use digest::Digest; use rand::{Rng, rngs::OsRng}; use secrecy::Secret; +use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -101,12 +103,35 @@ impl<'a> ValidShares<'a> { } } -#[derive(Clone, Zeroize, ZeroizeOnDrop)] +#[derive(Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] pub struct Share { pub x_coordinate: Gf256, pub y_coordinates: Box<[Gf256]>, } +impl Share { + // Return a cryptographic hash of a Share using the parameterized + // algorithm. + pub fn digest(&self, output: &mut [u8]) { + let mut hasher = D::new(); + hasher.update([*self.x_coordinate.as_ref()]); + // Implementing AsRef<[u8]> for Box<[Gf256]> doesn't work due to + // coherence rules. To get around that we'd need a transparent newtype + // for the y_coordinates and some unsafe code, which we're loathe to do. + let mut ys: Vec = + self.y_coordinates.iter().map(|y| *y.as_ref()).collect(); + hasher.update(&ys); + output.copy_from_slice(&hasher.finalize()); + ys.zeroize(); + } +} + +impl std::fmt::Debug for Share { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KeyShareGf256").finish() + } +} + pub struct SecretShares { pub threshold: ValidThreshold, pub shares: Secret>, diff --git a/trust-quorum/src/configuration.rs b/trust-quorum/src/configuration.rs new file mode 100644 index 00000000000..172fe04e6cd --- /dev/null +++ b/trust-quorum/src/configuration.rs @@ -0,0 +1,119 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! A configuration of a trust quroum at a given epoch + +use crate::crypto::{EncryptedRackSecret, RackSecret, Salt, Sha3_256Digest}; +use crate::{Epoch, PlatformId, ReconfigureMsg, Threshold}; +use gfss::shamir::SplitError; +use omicron_uuid_kinds::RackUuid; +use secrecy::ExposeSecret; +use serde::{Deserialize, Serialize}; +use slog_error_chain::SlogInlineError; +use std::collections::BTreeMap; + +#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq, SlogInlineError)] +pub enum ConfigurationError { + #[error("rack secret split error")] + RackSecretSplit( + #[from] + #[source] + SplitError, + ), + #[error("too many members: must be fewer than 255")] + TooManyMembers, +} + +/// The configuration for a given epoch. +/// +/// Only valid for non-lrtq configurations +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Configuration { + /// Unique Id of the rack + pub rack_id: RackUuid, + + // Unique, monotonically increasing identifier for a configuration + pub epoch: Epoch, + + /// Who was the coordinator of this reconfiguration? + pub coordinator: PlatformId, + + // All members of the current configuration and the hash of their key shares + pub members: BTreeMap, + + /// The number of sleds required to reconstruct the rack secret + pub threshold: Threshold, + + // There is no previous configuration for the initial configuration + pub previous_configuration: Option, +} + +impl Configuration { + /// Create a new configuration for the trust quorum + /// + /// `previous_configuration` is never filled in upon construction. A + /// coordinator will fill this in as necessary after retrieving shares for + /// the last committed epoch. + pub fn new( + coordinator: PlatformId, + reconfigure_msg: &ReconfigureMsg, + ) -> Result { + let rack_secret = RackSecret::new(); + let shares = rack_secret.split( + reconfigure_msg.threshold, + reconfigure_msg + .members + .len() + .try_into() + .map_err(|_| ConfigurationError::TooManyMembers)?, + )?; + + let share_digests = shares.shares.expose_secret().iter().map(|s| { + let mut digest = Sha3_256Digest::default(); + s.digest::(&mut digest.0); + digest + }); + + let members = reconfigure_msg + .members + .iter() + .cloned() + .zip(share_digests) + .collect(); + + Ok(Configuration { + rack_id: reconfigure_msg.rack_id, + epoch: reconfigure_msg.epoch, + coordinator, + members, + threshold: reconfigure_msg.threshold, + previous_configuration: None, + }) + } +} + +/// Information for the last committed configuration that is necessary to track +/// in the next `Configuration`. +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct PreviousConfiguration { + /// The epoch of the last committed configuration + pub epoch: Epoch, + + /// Is the previous configuration LRTQ? + pub is_lrtq: bool, + + /// The encrypted rack secret for the last committed epoch + /// + /// This allows us to derive old encryption keys so they can be rotated + pub encrypted_last_committed_rack_secret: EncryptedRackSecret, + + /// A random value used to derive the key to encrypt the rack secret from + /// the last committed epoch. + /// + /// We only encrypt the rack secret once and so we use a nonce of all zeros. + /// This is why there is no corresponding `nonce` field. + pub encrypted_last_committed_rack_secret_salt: Salt, +} diff --git a/trust-quorum/src/crypto.rs b/trust-quorum/src/crypto.rs new file mode 100644 index 00000000000..e5bb53cec59 --- /dev/null +++ b/trust-quorum/src/crypto.rs @@ -0,0 +1,298 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Various cryptographic constructs used by trust quroum. + +use bootstore::trust_quorum::RackSecret as LrtqRackSecret; +use derive_more::From; +use gfss::shamir::{self, CombineError, SecretShares, Share, SplitError}; +use rand::RngCore; +use rand::rngs::OsRng; +use secrecy::{DebugSecret, ExposeSecret, Secret}; +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Sha3_256}; +use slog_error_chain::SlogInlineError; +use std::fmt::Debug; +use subtle::ConstantTimeEq; +use zeroize::{Zeroize, ZeroizeOnDrop}; + +use crate::Threshold; + +/// Each share contains a byte for the y-coordinate of 32 points on 32 different +/// polynomials over Ed25519. All points share an x-coordinate, which is the 0th +/// byte in a 33 byte Vec. +/// +/// This is enough information to share a secret 32 bytes long. +const LRTQ_SHARE_SIZE: usize = 33; + +/// We don't distinguish whether this is an Ed25519 Scalar or set of GF(256) +/// polynomials points with an x-coordinate of 0. Both can be treated as 32 byte +/// blobs when decrypted, as they are immediately fed into HKDF. +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct EncryptedRackSecret(pub Vec); + +// The key share format used for LRTQ +#[derive(Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop, From)] +pub struct LrtqShare(Vec); + +// We don't want to risk debug-logging the actual share contents, so implement +// `Debug` manually. +impl std::fmt::Debug for LrtqShare { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KeyShareEd25519").finish() + } +} + +impl LrtqShare { + pub fn new(share: Vec) -> Self { + assert_eq!(share.len(), LRTQ_SHARE_SIZE); + Self(share) + } + + pub fn digest(&self) -> ShareDigestLrtq { + ShareDigestLrtq(Sha3_256Digest( + Sha3_256::digest(&self.0).as_slice().try_into().unwrap(), + )) + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct ShareDigestLrtq(Sha3_256Digest); + +#[derive( + Default, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, +)] +pub struct Sha3_256Digest(pub [u8; 32]); + +/// A boxed array containing rack secret data +/// +/// This should never be used directly, and always wrapped in a `Secret` upon +/// construction. We sparate the two types, because a `Secret` must contain +/// `Zeroizable` data, and a `Box<[u8; 32]>` is not zeroizable on its own. +/// +/// We explicitly choose to box the data so that it is not littered around +/// memory via moves, and also so that it is not accidentally growable like +/// a `Vec`. +#[derive(Zeroize, ZeroizeOnDrop)] +struct RackSecretData(Box<[u8; 32]>); + +/// A rack secret reconstructed via share combination. +/// +/// This secret must be treated as a generic array of 32 bytes. We don't +/// differentiate between whether or not this secret was recreated via Ed25519 +/// +Ristretto key shares (LRTQ) or GF256 Key Shares (current protocol). +/// Therefore, this rack secret should never be split back into key shares. +pub struct ReconstructedRackSecret { + secret: Secret, +} + +impl DebugSecret for ReconstructedRackSecret {} + +impl Debug for ReconstructedRackSecret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Self::debug_secret(f) + } +} + +impl ExposeSecret<[u8; 32]> for ReconstructedRackSecret { + fn expose_secret(&self) -> &[u8; 32] { + &self.secret.expose_secret().0 + } +} + +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[error("invalid rack secret size")] +pub struct InvalidRackSecretSizeError; + +impl TryFrom>> for ReconstructedRackSecret { + type Error = InvalidRackSecretSizeError; + fn try_from(value: Secret>) -> Result { + // This is somewhat janky + // + // We explicitly clone the secret out, then put it in a boxed array. + // This should avoid an intermediate allocation that we'd have to zero. + let v: Vec = value.expose_secret().clone().into(); + let data: Box<[u8; 32]> = + v.try_into().map_err(|_| InvalidRackSecretSizeError)?; + Ok(ReconstructedRackSecret { + secret: Secret::new(RackSecretData(data)), + }) + } +} + +impl From for ReconstructedRackSecret { + fn from(value: LrtqRackSecret) -> Self { + let secret = value.expose_secret().as_bytes(); + ReconstructedRackSecret { + secret: Secret::new(RackSecretData(Box::new(*secret))), + } + } +} + +#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq, SlogInlineError)] +pub enum RackSecretReconstructError { + #[error("share combine error")] + Combine( + #[from] + #[source] + CombineError, + ), + #[error(transparent)] + Size(#[from] InvalidRackSecretSizeError), +} + +/// A shared secret based on GF256 +pub struct RackSecret { + secret: Secret, +} + +impl ExposeSecret<[u8; 32]> for RackSecret { + fn expose_secret(&self) -> &[u8; 32] { + &self.secret.expose_secret().0 + } +} + +impl Default for RackSecret { + fn default() -> Self { + Self::new() + } +} + +impl RackSecret { + /// Create a random 32 byte secret + pub fn new() -> RackSecret { + let mut rng = OsRng; + let mut data = Box::new([0u8; 32]); + while data.ct_eq(&[0u8; 32]).into() { + rng.fill_bytes(&mut *data); + } + RackSecret { secret: Secret::new(RackSecretData(data)) } + } + + /// Split a secert into `total_shares` number of shares, where combining + /// `threshold` of the shares can be used to recover the secret. + pub fn split( + &self, + threshold: Threshold, + total_shares: u8, + ) -> Result { + let shares = shamir::split_secret( + self.expose_secret(), + total_shares, + threshold.0, + )?; + Ok(shares) + } + + pub fn reconstruct( + shares: &[Share], + ) -> Result { + let secret = shamir::compute_secret(shares)?.try_into()?; + Ok(secret) + } +} + +impl DebugSecret for RackSecret {} + +impl Debug for RackSecret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Self::debug_secret(f) + } +} + +impl PartialEq for RackSecret { + fn eq(&self, other: &Self) -> bool { + self.expose_secret().ct_eq(other.expose_secret()).into() + } +} + +impl Eq for RackSecret {} + +/// Some public randomness for cryptographic operations +#[derive( + Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct Salt(pub [u8; 32]); + +impl Salt { + pub fn new() -> Salt { + let mut rng = OsRng; + let mut salt = [0u8; 32]; + rng.fill_bytes(&mut salt); + Salt(salt) + } +} + +impl Default for Salt { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::{ + prelude::TestCaseError, prop_assert, prop_assert_eq, prop_assert_ne, + }; + use test_strategy::{Arbitrary, proptest}; + + #[derive(Arbitrary, Debug)] + pub struct TestRackSecretInput { + #[strategy(2..255u8)] + threshold: u8, + #[strategy(2..255u8)] + total_shares: u8, + } + + fn check_rack_secret_invariants( + input: &TestRackSecretInput, + original: RackSecret, + res: Result, + ) -> Result<(), TestCaseError> { + if input.threshold > input.total_shares { + prop_assert!(res.is_err()); + return Ok(()); + } + + let shares = res.unwrap(); + + // Fewer than threshold shares generates a nonsense secret + let res = RackSecret::reconstruct( + &shares.shares.expose_secret()[0..(input.threshold - 2) as usize], + ); + if res.is_ok() { + let rs = res.unwrap(); + prop_assert_ne!(rs.expose_secret(), original.expose_secret()); + } + + // Can unlock with at least `threshold` shares + let rack_secret = RackSecret::reconstruct( + &shares.shares.expose_secret()[..input.threshold as usize], + ) + .unwrap(); + prop_assert_eq!(rack_secret.expose_secret(), original.expose_secret()); + + Ok(()) + } + + #[proptest] + fn rack_secret_test(input: TestRackSecretInput) { + let rs = RackSecret::new(); + let res = rs.split(Threshold(input.threshold), input.total_shares); + check_rack_secret_invariants(&input, rs, res)?; + } +} diff --git a/trust-quorum/src/lib.rs b/trust-quorum/src/lib.rs new file mode 100644 index 00000000000..cb2ade45e8e --- /dev/null +++ b/trust-quorum/src/lib.rs @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Implementation of the oxide rack trust quorum protocol +//! +//! This protocol is written as a +//! [no-IO](https://sans-io.readthedocs.io/how-to-sans-io.html) implementation. +//! All persistent state and all networking is managed outside of this +//! implementation. + +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +mod configuration; +pub(crate) mod crypto; +mod messages; +pub use configuration::Configuration; +pub use crypto::RackSecret; +pub use messages::*; + +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Display, +)] +pub struct Epoch(u64); + +/// The number of shares required to reconstruct the rack secret +/// +/// Typically referred to as `k` in the docs +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Display, +)] +pub struct Threshold(pub u8); + +/// A unique identifier for a given trust quorum member. +// +/// This data is derived from the subject common name in the platform identity +/// certificate that makes up part of the certificate chain used to establish +/// [sprockets](https://github.com/oxidecomputer/sprockets) connections. +/// +/// See RFDs 303 and 308 for more details. +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct PlatformId { + pub part_number: String, + pub serial_number: String, +} + +/// A container to make messages between trust quorum nodes routable +#[derive(Debug, Serialize, Deserialize)] +pub struct Envelope { + to: PlatformId, + from: PlatformId, + msg: PeerMsg, +} diff --git a/trust-quorum/src/messages.rs b/trust-quorum/src/messages.rs new file mode 100644 index 00000000000..1316fdcaf96 --- /dev/null +++ b/trust-quorum/src/messages.rs @@ -0,0 +1,55 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Messsages for the trust quorum protocol + +use crate::crypto::LrtqShare; +use crate::{Configuration, Epoch, PlatformId, Threshold}; +use gfss::shamir::Share; +use omicron_uuid_kinds::RackUuid; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeSet, time::Duration}; + +/// A request from nexus informing a node to start coordinating a +/// reconfiguration. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct ReconfigureMsg { + pub rack_id: RackUuid, + pub epoch: Epoch, + pub last_committed_epoch: Option, + pub members: BTreeSet, + pub threshold: Threshold, + + // The timeout before we send a follow up request to a peer + pub retry_timeout: Duration, +} + +/// Messages sent between trust quorum members over a sprockets channel +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PeerMsg { + Prepare(PrepareMsg), + PrepareAck(Epoch), + Commit(CommitMsg), + + GetShare(Epoch), + Share { epoch: Epoch, share: Share }, + + // LRTQ shares are always at epoch 0 + GetLrtqShare, + LrtqShare(LrtqShare), +} + +/// The start of a reconfiguration sent from a coordinator to a specific peer +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PrepareMsg { + pub config: Configuration, + pub share: Share, +} + +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, +)] +pub struct CommitMsg { + epoch: Epoch, +} diff --git a/uuid-kinds/src/lib.rs b/uuid-kinds/src/lib.rs index cd5821293a5..4ad81c728f6 100644 --- a/uuid-kinds/src/lib.rs +++ b/uuid-kinds/src/lib.rs @@ -66,6 +66,7 @@ impl_typed_uuid_kind! { OmicronZone => "service", PhysicalDisk => "physical_disk", Propolis => "propolis", + Rack => "rack", RackInit => "rack_init", RackReset => "rack_reset", ReconfiguratorSim => "reconfigurator_sim",