From 0a611cb1557a892ee83c3031e76f4c22bbb74fc1 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Fri, 3 Jan 2025 06:57:28 -0500 Subject: [PATCH] Further flesh out tributary scanning Renames `label` to `round` since `Label` was renamed to `SigningProtocolRound`. Adds some more context-less validation to transactions which used to be done within the custom decode function which was simplified via the usage of borsh. Documents in processor-messages where the Coordinator sends each of its messages. --- coordinator/Cargo.toml | 2 +- coordinator/src/tributary/db.rs | 119 +++++++--- coordinator/src/tributary/scan.rs | 217 +++++++++++++++++- coordinator/src/tributary/transaction.rs | 46 +++- processor/messages/src/lib.rs | 28 ++- .../validator-sets/primitives/src/lib.rs | 4 +- 6 files changed, 352 insertions(+), 64 deletions(-) diff --git a/coordinator/Cargo.toml b/coordinator/Cargo.toml index 8dcd4cd5..2af0f822 100644 --- a/coordinator/Cargo.toml +++ b/coordinator/Cargo.toml @@ -39,7 +39,7 @@ serai-db = { path = "../common/db" } serai-env = { path = "../common/env" } serai-task = { path = "../common/task", version = "0.1" } -processor-messages = { package = "serai-processor-messages", path = "../processor/messages" } +messages = { package = "serai-processor-messages", path = "../processor/messages" } message-queue = { package = "serai-message-queue", path = "../message-queue" } tributary = { package = "tributary-chain", path = "./tributary" } diff --git a/coordinator/src/tributary/db.rs b/coordinator/src/tributary/db.rs index 008cd5c8..31ae2e1e 100644 --- a/coordinator/src/tributary/db.rs +++ b/coordinator/src/tributary/db.rs @@ -5,7 +5,7 @@ use borsh::{BorshSerialize, BorshDeserialize}; use serai_client::{primitives::SeraiAddress, validator_sets::primitives::ValidatorSet}; -use processor_messages::sign::VariantSignId; +use messages::sign::{VariantSignId, SignId}; use serai_db::*; @@ -13,20 +13,20 @@ use crate::tributary::transaction::SigningProtocolRound; /// A topic within the database which the group participates in #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)] -pub enum Topic { +pub(crate) enum Topic { /// Vote to remove a participant RemoveParticipant { participant: SeraiAddress }, // DkgParticipation isn't represented here as participations are immediately sent to the // processor, not accumulated within this databse /// Participation in the signing protocol to confirm the DKG results on Substrate - DkgConfirmation { attempt: u32, label: SigningProtocolRound }, + DkgConfirmation { attempt: u32, round: SigningProtocolRound }, /// The local view of the SlashReport, to be aggregated into the final SlashReport SlashReport, /// Participation in a signing protocol - Sign { id: VariantSignId, attempt: u32, label: SigningProtocolRound }, + Sign { id: VariantSignId, attempt: u32, round: SigningProtocolRound }, } enum Participating { @@ -40,13 +40,13 @@ impl Topic { #[allow(clippy::match_same_arms)] match self { Topic::RemoveParticipant { .. } => None, - Topic::DkgConfirmation { attempt, label: _ } => Some(Topic::DkgConfirmation { + Topic::DkgConfirmation { attempt, round: _ } => Some(Topic::DkgConfirmation { attempt: attempt + 1, - label: SigningProtocolRound::Preprocess, + round: SigningProtocolRound::Preprocess, }), Topic::SlashReport { .. } => None, - Topic::Sign { id, attempt, label: _ } => { - Some(Topic::Sign { id, attempt: attempt + 1, label: SigningProtocolRound::Preprocess }) + Topic::Sign { id, attempt, round: _ } => { + Some(Topic::Sign { id, attempt: attempt + 1, round: SigningProtocolRound::Preprocess }) } } } @@ -56,27 +56,40 @@ impl Topic { #[allow(clippy::match_same_arms)] match self { Topic::RemoveParticipant { .. } => None, - Topic::DkgConfirmation { attempt, label } => match label { + Topic::DkgConfirmation { attempt, round } => match round { SigningProtocolRound::Preprocess => { let attempt = attempt + 1; Some(( attempt, - Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Preprocess }, + Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Preprocess }, )) } SigningProtocolRound::Share => None, }, Topic::SlashReport { .. } => None, - Topic::Sign { id, attempt, label } => match label { + Topic::Sign { id, attempt, round } => match round { SigningProtocolRound::Preprocess => { let attempt = attempt + 1; - Some((attempt, Topic::Sign { id, attempt, label: SigningProtocolRound::Preprocess })) + Some((attempt, Topic::Sign { id, attempt, round: SigningProtocolRound::Preprocess })) } SigningProtocolRound::Share => None, }, } } + // The SignId for this topic + // + // Returns None if Topic isn't Topic::Sign + pub(crate) fn sign_id(self, set: ValidatorSet) -> Option { + #[allow(clippy::match_same_arms)] + match self { + Topic::RemoveParticipant { .. } => None, + Topic::DkgConfirmation { .. } => None, + Topic::SlashReport { .. } => None, + Topic::Sign { id, attempt, round: _ } => Some(SignId { session: set.session, id, attempt }), + } + } + /// The topic which precedes this topic as a prerequisite /// /// The preceding topic must define this topic as succeeding @@ -84,17 +97,17 @@ impl Topic { #[allow(clippy::match_same_arms)] match self { Topic::RemoveParticipant { .. } => None, - Topic::DkgConfirmation { attempt, label } => match label { + Topic::DkgConfirmation { attempt, round } => match round { SigningProtocolRound::Preprocess => None, SigningProtocolRound::Share => { - Some(Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Preprocess }) + Some(Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Preprocess }) } }, Topic::SlashReport { .. } => None, - Topic::Sign { id, attempt, label } => match label { + Topic::Sign { id, attempt, round } => match round { SigningProtocolRound::Preprocess => None, SigningProtocolRound::Share => { - Some(Topic::Sign { id, attempt, label: SigningProtocolRound::Preprocess }) + Some(Topic::Sign { id, attempt, round: SigningProtocolRound::Preprocess }) } }, } @@ -107,16 +120,16 @@ impl Topic { #[allow(clippy::match_same_arms)] match self { Topic::RemoveParticipant { .. } => None, - Topic::DkgConfirmation { attempt, label } => match label { + Topic::DkgConfirmation { attempt, round } => match round { SigningProtocolRound::Preprocess => { - Some(Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Share }) + Some(Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Share }) } SigningProtocolRound::Share => None, }, Topic::SlashReport { .. } => None, - Topic::Sign { id, attempt, label } => match label { + Topic::Sign { id, attempt, round } => match round { SigningProtocolRound::Preprocess => { - Some(Topic::Sign { id, attempt, label: SigningProtocolRound::Share }) + Some(Topic::Sign { id, attempt, round: SigningProtocolRound::Share }) } SigningProtocolRound::Share => None, }, @@ -155,7 +168,7 @@ impl Topic { } /// The resulting data set from an accumulation -pub enum DataSet { +pub(crate) enum DataSet { /// Accumulating this did not produce a data set to act on /// (non-existent, not ready, prior handled, not participating, etc.) None, @@ -187,15 +200,21 @@ create_db!( } ); -pub struct TributaryDb; +db_channel!( + CoordinatorTributary { + ProcessorMessages: (set: ValidatorSet) -> messages::CoordinatorMessage, + } +); + +pub(crate) struct TributaryDb; impl TributaryDb { - pub fn last_handled_tributary_block( + pub(crate) fn last_handled_tributary_block( getter: &impl Get, set: ValidatorSet, ) -> Option<(u64, [u8; 32])> { LastHandledTributaryBlock::get(getter, set) } - pub fn set_last_handled_tributary_block( + pub(crate) fn set_last_handled_tributary_block( txn: &mut impl DbTxn, set: ValidatorSet, block_number: u64, @@ -204,18 +223,35 @@ impl TributaryDb { LastHandledTributaryBlock::set(txn, set, &(block_number, block_hash)); } - pub fn recognize_topic(txn: &mut impl DbTxn, set: ValidatorSet, topic: Topic) { + pub(crate) fn latest_substrate_block_to_cosign( + getter: &impl Get, + set: ValidatorSet, + ) -> Option<[u8; 32]> { + LatestSubstrateBlockToCosign::get(getter, set) + } + pub(crate) fn set_latest_substrate_block_to_cosign( + txn: &mut impl DbTxn, + set: ValidatorSet, + substrate_block_hash: [u8; 32], + ) { + LatestSubstrateBlockToCosign::set(txn, set, &substrate_block_hash); + } + + pub(crate) fn recognize_topic(txn: &mut impl DbTxn, set: ValidatorSet, topic: Topic) { AccumulatedWeight::set(txn, set, topic, &0); } - pub fn start_of_block(txn: &mut impl DbTxn, set: ValidatorSet, block_number: u64) { + pub(crate) fn start_of_block(txn: &mut impl DbTxn, set: ValidatorSet, block_number: u64) { for topic in Reattempt::take(txn, set, block_number).unwrap_or(vec![]) { // TODO: Slash all people who preprocessed but didn't share Self::recognize_topic(txn, set, topic); + if let Some(id) = topic.sign_id(set) { + Self::send_message(txn, set, messages::sign::CoordinatorMessage::Reattempt { id }); + } } } - pub fn fatal_slash( + pub(crate) fn fatal_slash( txn: &mut impl DbTxn, set: ValidatorSet, validator: SeraiAddress, @@ -225,12 +261,16 @@ impl TributaryDb { SlashPoints::set(txn, set, validator, &u64::MAX); } - pub fn is_fatally_slashed(getter: &impl Get, set: ValidatorSet, validator: SeraiAddress) -> bool { + pub(crate) fn is_fatally_slashed( + getter: &impl Get, + set: ValidatorSet, + validator: SeraiAddress, + ) -> bool { SlashPoints::get(getter, set, validator).unwrap_or(0) == u64::MAX } #[allow(clippy::too_many_arguments)] - pub fn accumulate( + pub(crate) fn accumulate( txn: &mut impl DbTxn, set: ValidatorSet, validators: &[SeraiAddress], @@ -286,7 +326,8 @@ impl TributaryDb { // Check if we now cross the weight threshold if accumulated_weight >= topic.required_participation(total_weight) { // Queue this for re-attempt after enough time passes - if let Some((attempt, reattempt_topic)) = topic.reattempt_topic() { + let reattempt_topic = topic.reattempt_topic(); + if let Some((attempt, reattempt_topic)) = reattempt_topic { // 5 minutes #[cfg(not(feature = "longer-reattempts"))] const BASE_REATTEMPT_DELAY: u32 = @@ -316,15 +357,11 @@ impl TributaryDb { let mut data_set = HashMap::with_capacity(validators.len()); for validator in validators { if let Some(data) = Accumulated::::get(txn, set, topic, *validator) { - // Clean this data up if there's not a succeeding topic - // If there is, we wait as the succeeding topic checks our participation in this topic - if succeeding_topic.is_none() { + // Clean this data up if there's not a re-attempt topic + // If there is a re-attempt topic, we clean it up upon re-attempt + if reattempt_topic.is_none() { Accumulated::::del(txn, set, topic, *validator); } - // If this *was* the succeeding topic, clean up the preceding topic's data - if let Some(preceding_topic) = preceding_topic { - Accumulated::::del(txn, set, preceding_topic, *validator); - } data_set.insert(*validator, data); } } @@ -343,4 +380,12 @@ impl TributaryDb { DataSet::None } } + + pub(crate) fn send_message( + txn: &mut impl DbTxn, + set: ValidatorSet, + message: impl Into, + ) { + ProcessorMessages::send(txn, set, &message.into()); + } } diff --git a/coordinator/src/tributary/scan.rs b/coordinator/src/tributary/scan.rs index 47e1103d..1a53bdda 100644 --- a/coordinator/src/tributary/scan.rs +++ b/coordinator/src/tributary/scan.rs @@ -3,10 +3,13 @@ use std::collections::HashMap; use ciphersuite::group::GroupEncoding; -use serai_client::{primitives::SeraiAddress, validator_sets::primitives::ValidatorSet}; +use serai_client::{ + primitives::SeraiAddress, + validator_sets::primitives::{ValidatorSet, Slash}, +}; use tributary::{ - Signed as TributarySigned, TransactionError, TransactionKind, TransactionTrait, + Signed as TributarySigned, TransactionKind, TransactionTrait, Transaction as TributaryTransaction, Block, TributaryReader, tendermint::{ tx::{TendermintTx, Evidence, decode_signed_message}, @@ -17,9 +20,11 @@ use tributary::{ use serai_db::*; use serai_task::ContinuallyRan; +use messages::sign::VariantSignId; + use crate::tributary::{ db::*, - transaction::{Signed, Transaction}, + transaction::{SigningProtocolRound, Signed, Transaction}, }; struct ScanBlock<'a, D: DbTxn, TD: Db> { @@ -43,9 +48,21 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> { } match tx { + // Accumulate this vote and fatally slash the participant if past the threshold Transaction::RemoveParticipant { participant, signed } => { - // Accumulate this vote and fatally slash the participant if past the threshold let signer = signer(signed); + + // Check the participant voted to be removed actually exists + if !self.validators.iter().any(|validator| *validator == participant) { + TributaryDb::fatal_slash( + self.txn, + self.set, + signer, + "voted to remove non-existent participant", + ); + return; + } + match TributaryDb::accumulate( self.txn, self.set, @@ -59,14 +76,22 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> { ) { DataSet::None => {} DataSet::Participating(_) => { - TributaryDb::fatal_slash(self.txn, self.set, participant, "voted to remove") + TributaryDb::fatal_slash(self.txn, self.set, participant, "voted to remove"); } - } + }; } + // Send the participation to the processor Transaction::DkgParticipation { participation, signed } => { - // Send the participation to the processor - todo!("TODO") + TributaryDb::send_message( + self.txn, + self.set, + messages::key_gen::CoordinatorMessage::Participation { + session: self.set.session, + participant: todo!("TODO"), + participation, + }, + ); } Transaction::DkgConfirmationPreprocess { attempt, preprocess, signed } => { // Accumulate the preprocesses into our own FROST attempt manager @@ -79,11 +104,42 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> { Transaction::Cosign { substrate_block_hash } => { // Update the latest intended-to-be-cosigned Substrate block - todo!("TODO") + TributaryDb::set_latest_substrate_block_to_cosign(self.txn, self.set, substrate_block_hash); + + // TODO: If we aren't currently cosigning a block, start cosigning this one } Transaction::Cosigned { substrate_block_hash } => { // Start cosigning the latest intended-to-be-cosigned block - todo!("TODO") + let Some(latest_substrate_block_to_cosign) = + TributaryDb::latest_substrate_block_to_cosign(self.txn, self.set) + else { + return; + }; + // If this is the block we just cosigned, return + if latest_substrate_block_to_cosign == substrate_block_hash { + return; + } + let substrate_block_number = todo!("TODO"); + // Whitelist the topic + TributaryDb::recognize_topic( + self.txn, + self.set, + Topic::Sign { + id: VariantSignId::Cosign(substrate_block_number), + attempt: 0, + round: SigningProtocolRound::Preprocess, + }, + ); + // Send the message for the processor to start signing + TributaryDb::send_message( + self.txn, + self.set, + messages::coordinator::CoordinatorMessage::CosignSubstrateBlock { + session: self.set.session, + block_number: substrate_block_number, + block: substrate_block_hash, + }, + ); } Transaction::SubstrateBlock { hash } => { // Whitelist all of the IDs this Substrate block causes to be signed @@ -95,11 +151,148 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> { } Transaction::SlashReport { slash_points, signed } => { + let signer = signer(signed); + + if slash_points.len() != self.validators.len() { + TributaryDb::fatal_slash( + self.txn, + self.set, + signer, + "slash report was for a distinct amount of signers", + ); + return; + } + // Accumulate, and if past the threshold, calculate *the* slash report and start signing it - todo!("TODO") + match TributaryDb::accumulate( + self.txn, + self.set, + self.validators, + self.total_weight, + block_number, + Topic::SlashReport, + signer, + self.validator_weights[&signer], + &slash_points, + ) { + DataSet::None => {} + DataSet::Participating(data_set) => { + // Find the median reported slashes for this validator + // TODO: This lets 34% perform a fatal slash. Should that be allowed? + let mut median_slash_report = Vec::with_capacity(self.validators.len()); + for i in 0 .. self.validators.len() { + let mut this_validator = + data_set.values().map(|report| report[i]).collect::>(); + this_validator.sort_unstable(); + // Choose the median, where if there are two median values, the lower one is chosen + let median_index = if (this_validator.len() % 2) == 1 { + this_validator.len() / 2 + } else { + (this_validator.len() / 2) - 1 + }; + median_slash_report.push(this_validator[median_index]); + } + + // We only publish slashes for the `f` worst performers to: + // 1) Effect amnesty if there were network disruptions which affected everyone + // 2) Ensure the signing threshold doesn't have a disincentive to do their job + + // Find the worst performer within the signing threshold's slash points + let f = (self.validators.len() - 1) / 3; + let worst_validator_in_supermajority_slash_points = { + let mut sorted_slash_points = median_slash_report.clone(); + sorted_slash_points.sort_unstable(); + // This won't be a valid index if `f == 0`, which means we don't have any validators + // to slash + let index_of_first_validator_to_slash = self.validators.len() - f; + let index_of_worst_validator_in_supermajority = index_of_first_validator_to_slash - 1; + sorted_slash_points[index_of_worst_validator_in_supermajority] + }; + + // Perform the amortization + for slash_points in &mut median_slash_report { + *slash_points = + slash_points.saturating_sub(worst_validator_in_supermajority_slash_points) + } + let amortized_slash_report = median_slash_report; + + // Create the resulting slash report + let mut slash_report = vec![]; + for (validator, points) in self.validators.iter().copied().zip(amortized_slash_report) { + if points != 0 { + slash_report.push(Slash { key: validator.into(), points }); + } + } + assert!(slash_report.len() <= f); + + // Recognize the topic for signing the slash report + TributaryDb::recognize_topic( + self.txn, + self.set, + Topic::Sign { + id: VariantSignId::SlashReport, + attempt: 0, + round: SigningProtocolRound::Preprocess, + }, + ); + // Send the message for the processor to start signing + TributaryDb::send_message( + self.txn, + self.set, + messages::coordinator::CoordinatorMessage::SignSlashReport { + session: self.set.session, + report: slash_report, + }, + ); + } + }; } - Transaction::Sign { id, attempt, label, data, signed } => todo!("TODO"), + Transaction::Sign { id, attempt, round, data, signed } => { + let topic = Topic::Sign { id, attempt, round }; + let signer = signer(signed); + + if u64::try_from(data.len()).unwrap() != self.validator_weights[&signer] { + TributaryDb::fatal_slash( + self.txn, + self.set, + signer, + "signer signed with a distinct amount of key shares than they had key shares", + ); + return; + } + + match TributaryDb::accumulate( + self.txn, + self.set, + self.validators, + self.total_weight, + block_number, + topic, + signer, + self.validator_weights[&signer], + &data, + ) { + DataSet::None => {} + DataSet::Participating(data_set) => { + let id = topic.sign_id(self.set).expect("Topic::Sign didn't have SignId"); + let flatten_data_set = |data_set| todo!("TODO"); + let data_set = flatten_data_set(data_set); + TributaryDb::send_message( + self.txn, + self.set, + match round { + SigningProtocolRound::Preprocess => { + messages::sign::CoordinatorMessage::Preprocesses { id, preprocesses: data_set } + } + SigningProtocolRound::Share => { + messages::sign::CoordinatorMessage::Shares { id, shares: data_set } + } + }, + ) + } + }; + } } } diff --git a/coordinator/src/tributary/transaction.rs b/coordinator/src/tributary/transaction.rs index c5d00e30..65391296 100644 --- a/coordinator/src/tributary/transaction.rs +++ b/coordinator/src/tributary/transaction.rs @@ -14,9 +14,9 @@ use schnorr::SchnorrSignature; use scale::Encode; use borsh::{BorshSerialize, BorshDeserialize}; -use serai_client::primitives::SeraiAddress; +use serai_client::{primitives::SeraiAddress, validator_sets::primitives::MAX_KEY_SHARES_PER_SET}; -use processor_messages::sign::VariantSignId; +use messages::sign::VariantSignId; use tributary::{ ReadWrite, @@ -25,7 +25,7 @@ use tributary::{ }, }; -/// The label for data from a signing protocol. +/// The round this data is for, within a signing protocol. #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)] pub enum SigningProtocolRound { /// A preprocess. @@ -182,8 +182,8 @@ pub enum Transaction { id: VariantSignId, /// The attempt number of this signing protocol attempt: u32, - /// The label for this data within the signing protocol - label: SigningProtocolRound, + /// The round this data is for, within the signing protocol + round: SigningProtocolRound, /// The data itself /// /// There will be `n` blobs of data where `n` is the amount of key shares the validator sending @@ -234,8 +234,8 @@ impl TransactionTrait for Transaction { Transaction::SubstrateBlock { .. } => TransactionKind::Provided("SubstrateBlock"), Transaction::Batch { .. } => TransactionKind::Provided("Batch"), - Transaction::Sign { id, attempt, label, signed, .. } => { - TransactionKind::Signed((b"Sign", id, attempt).encode(), signed.nonce(label.nonce())) + Transaction::Sign { id, attempt, round, signed, .. } => { + TransactionKind::Signed((b"Sign", id, attempt).encode(), signed.nonce(round.nonce())) } Transaction::SlashReport { signed, .. } => { @@ -253,9 +253,37 @@ impl TransactionTrait for Transaction { Blake2b::::digest(&tx).into() } - // We don't have any verification logic embedded into the transaction. We just slash anyone who - // publishes an invalid transaction. + // This is a stateless verification which we use to enforce some size limits. fn verify(&self) -> Result<(), TransactionError> { + #[allow(clippy::match_same_arms)] + match self { + // Fixed-length TX + Transaction::RemoveParticipant { .. } => {} + + // TODO: MAX_DKG_PARTICIPATION_LEN + Transaction::DkgParticipation { .. } => {} + // These are fixed-length TXs + Transaction::DkgConfirmationPreprocess { .. } | Transaction::DkgConfirmationShare { .. } => {} + + // Provided TXs + Transaction::Cosign { .. } | + Transaction::Cosigned { .. } | + Transaction::SubstrateBlock { .. } | + Transaction::Batch { .. } => {} + + Transaction::Sign { data, .. } => { + if data.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() { + Err(TransactionError::InvalidContent)? + } + // TODO: MAX_SIGN_LEN + } + + Transaction::SlashReport { slash_points, .. } => { + if slash_points.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() { + Err(TransactionError::InvalidContent)? + } + } + }; Ok(()) } } diff --git a/processor/messages/src/lib.rs b/processor/messages/src/lib.rs index 7c964ebc..ee6ed8ac 100644 --- a/processor/messages/src/lib.rs +++ b/processor/messages/src/lib.rs @@ -22,9 +22,13 @@ pub mod key_gen { #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum CoordinatorMessage { - // Instructs the Processor to begin the key generation process. + /// Instructs the Processor to begin the key generation process. + /// + /// This is sent by the Coordinator when it creates the Tributary (TODO). GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec)> }, - // Received participations for the specified key generation protocol. + /// Received participations for the specified key generation protocol. + /// + /// This is sent by the Coordinator's Tributary scanner. Participation { session: Session, participant: Participant, participation: Vec }, } @@ -113,11 +117,17 @@ pub mod sign { #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub enum CoordinatorMessage { - // Received preprocesses for the specified signing protocol. + /// Received preprocesses for the specified signing protocol. + /// + /// This is sent by the Coordinator's Tributary scanner. Preprocesses { id: SignId, preprocesses: HashMap> }, // Received shares for the specified signing protocol. + /// + /// This is sent by the Coordinator's Tributary scanner. Shares { id: SignId, shares: HashMap> }, // Re-attempt a signing protocol. + /// + /// This is sent by the Coordinator's Tributary re-attempt scheduling logic. Reattempt { id: SignId }, } @@ -157,7 +167,13 @@ pub mod coordinator { #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub enum CoordinatorMessage { + /// Cosign the specified Substrate block. + /// + /// This is sent by the Coordinator's Tributary scanner. CosignSubstrateBlock { session: Session, block_number: u64, block: [u8; 32] }, + /// Sign the slash report for this session. + /// + /// This is sent by the Coordinator's Tributary scanner. SignSlashReport { session: Session, report: Vec }, } @@ -196,12 +212,18 @@ pub mod substrate { #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub enum CoordinatorMessage { /// Keys set on the Serai blockchain. + /// + /// This is set by the Coordinator's Substrate canonical event stream. SetKeys { serai_time: u64, session: Session, key_pair: KeyPair }, /// Slashes reported on the Serai blockchain OR the process timed out. /// /// This is the final message for a session, + /// + /// This is set by the Coordinator's Substrate canonical event stream. SlashesReported { session: Session }, /// A block from Serai with relevance to this processor. + /// + /// This is set by the Coordinator's Substrate canonical event stream. Block { serai_block_number: u64, batch: Option, diff --git a/substrate/validator-sets/primitives/src/lib.rs b/substrate/validator-sets/primitives/src/lib.rs index 341d211f..fe78fbca 100644 --- a/substrate/validator-sets/primitives/src/lib.rs +++ b/substrate/validator-sets/primitives/src/lib.rs @@ -114,8 +114,8 @@ pub struct Slash { deserialize_with = "serai_primitives::borsh_deserialize_public" ) )] - key: Public, - points: u32, + pub key: Public, + pub points: u32, } #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]