From 2aebfb21af5036828c4b10d10b01e342d3f5bdf6 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 25 Dec 2024 23:21:25 -0500 Subject: [PATCH] Remove serai from the cosign evaluator --- common/db/src/create_db.rs | 13 +++ coordinator/cosign/src/evaluator.rs | 90 +++++++++-------- coordinator/cosign/src/intend.rs | 145 ++++++++++++++-------------- coordinator/cosign/src/lib.rs | 74 ++++---------- 4 files changed, 146 insertions(+), 176 deletions(-) diff --git a/common/db/src/create_db.rs b/common/db/src/create_db.rs index 50fe51f7..c2917e58 100644 --- a/common/db/src/create_db.rs +++ b/common/db/src/create_db.rs @@ -143,6 +143,19 @@ macro_rules! db_channel { Self::set(txn, $($arg,)* index_to_use, value); } + pub(crate) fn peek( + txn: &mut impl DbTxn + $(, $arg: $arg_type)* + ) -> Option<$field_type> { + let messages_recvd_key = Self::key($($arg,)* 1); + let messages_recvd = txn.get(&messages_recvd_key).map(|counter| { + u32::from_le_bytes(counter.try_into().unwrap()) + }).unwrap_or(0); + + let index_to_read = messages_recvd + 2; + + Self::get(txn, $($arg,)* index_to_read) + } pub(crate) fn try_recv( txn: &mut impl DbTxn $(, $arg: $arg_type)* diff --git a/coordinator/cosign/src/evaluator.rs b/coordinator/cosign/src/evaluator.rs index 06a3019f..64094f0a 100644 --- a/coordinator/cosign/src/evaluator.rs +++ b/coordinator/cosign/src/evaluator.rs @@ -1,13 +1,11 @@ use core::future::Future; -use serai_client::Serai; - use serai_db::*; use serai_task::ContinuallyRan; use crate::{ - *, - intend::{BlockEventData, BlockEvents}, + HasEvents, GlobalSession, NetworksLatestCosignedBlock, RequestNotableCosigns, + intend::{GlobalSessionsChannel, BlockEventData, BlockEvents}, }; create_db!( @@ -15,34 +13,51 @@ create_db!( // The latest cosigned block number. LatestCosignedBlockNumber: () -> u64, // The latest global session evaluated. - LatestGlobalSessionEvaluated: () -> ([u8; 32], Vec), + LatestGlobalSessionEvaluated: () -> ([u8; 32], GlobalSession), } ); /// A task to determine if a block has been cosigned and we should handle it. -// TODO: Remove `serai` from this pub(crate) struct CosignEvaluatorTask { pub(crate) db: D, - pub(crate) serai: Serai, pub(crate) request: R, } -async fn get_latest_global_session_evaluated( +fn get_latest_global_session_evaluated( txn: &mut impl DbTxn, - serai: &Serai, - parent_hash: [u8; 32], -) -> Result<([u8; 32], Vec), String> { - Ok(match LatestGlobalSessionEvaluated::get(txn) { - Some(res) => res, - None => { - // This is the initial global session - // Fetch the sets participating and declare it the latest value recognized - let sets = cosigning_sets_by_parent_hash(serai, parent_hash).await?; - let initial_global_session = GlobalSession::id(sets.clone()); - LatestGlobalSessionEvaluated::set(txn, &(initial_global_session, sets.clone())); - (initial_global_session, sets) + block_number: u64, +) -> ([u8; 32], GlobalSession) { + let mut res = { + let existing = match LatestGlobalSessionEvaluated::get(txn) { + Some(existing) => existing, + None => { + let first = GlobalSessionsChannel::try_recv(txn) + .expect("fetching latest global session yet none declared"); + LatestGlobalSessionEvaluated::set(txn, &first); + first + } + }; + assert!( + existing.1.start_block_number <= block_number, + "candidate's start block number exceeds our block number" + ); + existing + }; + + if let Some(next) = GlobalSessionsChannel::peek(txn) { + assert!( + block_number <= next.1.start_block_number, + "get_latest_global_session_evaluated wasn't called incrementally" + ); + // If it's time for this session to activate, take it from the channel and set it + if block_number == next.1.start_block_number { + GlobalSessionsChannel::try_recv(txn).unwrap(); + LatestGlobalSessionEvaluated::set(txn, &next); + res = next; } - }) + } + + res } impl ContinuallyRan for CosignEvaluatorTask { @@ -54,8 +69,7 @@ impl ContinuallyRan for CosignEvaluatorTask ContinuallyRan for CosignEvaluatorTask { - let (global_session, sets) = - get_latest_global_session_evaluated(&mut txn, &self.serai, parent_hash).await?; + let (global_session, global_session_info) = + get_latest_global_session_evaluated(&mut txn, block_number); let mut weight_cosigned = 0; - let global_session_info = - GlobalSessions::get(&txn, global_session).ok_or_else(|| { - "checking if intended cosign was satisfied within an unrecognized global session" - .to_string() - })?; - for set in sets { + for set in global_session_info.sets { // Check if we have the cosign from this set if NetworksLatestCosignedBlock::get(&txn, global_session, set.network) .map(|signed_cosign| signed_cosign.cosign.block_number) == @@ -105,14 +114,6 @@ impl ContinuallyRan for CosignEvaluatorTask>(); - let global_session = GlobalSession::id(sets.clone()); - LatestGlobalSessionEvaluated::set(&mut txn, &(global_session, sets)); - } } // Since this block didn't have any notable events, we simply require a cosign for this // block or a greater block by the current validator sets @@ -135,17 +136,12 @@ impl ContinuallyRan for CosignEvaluatorTask = None; - for set in sets { + for set in global_session_info.sets { // Check if this set cosigned this block or not let Some(cosign) = NetworksLatestCosignedBlock::get(&txn, global_session, set.network) diff --git a/coordinator/cosign/src/intend.rs b/coordinator/cosign/src/intend.rs index 03c9d5c4..7466ae5a 100644 --- a/coordinator/cosign/src/intend.rs +++ b/coordinator/cosign/src/intend.rs @@ -21,13 +21,12 @@ create_db!( #[derive(Debug, BorshSerialize, BorshDeserialize)] pub(crate) struct BlockEventData { pub(crate) block_number: u64, - pub(crate) parent_hash: [u8; 32], - pub(crate) block_hash: [u8; 32], pub(crate) has_events: HasEvents, } db_channel! { CosignIntendChannels { + GlobalSessionsChannel: () -> ([u8; 32], GlobalSession), BlockEvents: () -> BlockEventData, IntendedCosigns: (set: ValidatorSet) -> CosignIntent, } @@ -87,88 +86,90 @@ impl ContinuallyRan for CosignIntendTask { block_number - 1 ))?; } - SubstrateBlocks::set(&mut txn, block_number, &block.hash()); + let global_session_for_this_block = LatestGlobalSessionIntended::get(&txn); + + // If this is notable, it creates a new global session, which we index into the database + // now + if has_events == HasEvents::Notable { + let serai = self.serai.as_of(block.hash()); + let sets_and_keys = cosigning_sets(&serai).await?; + let global_session = + GlobalSession::id(sets_and_keys.iter().map(|(set, _key)| *set).collect()); + + let mut sets = Vec::with_capacity(sets_and_keys.len()); + let mut keys = HashMap::with_capacity(sets_and_keys.len()); + let mut stakes = HashMap::with_capacity(sets_and_keys.len()); + let mut total_stake = 0; + for (set, key) in &sets_and_keys { + sets.push(*set); + keys.insert(set.network, SeraiAddress::from(*key)); + let stake = serai + .validator_sets() + .total_allocated_stake(set.network) + .await + .map_err(|e| format!("{e:?}"))? + .unwrap_or(Amount(0)) + .0; + stakes.insert(set.network, stake); + total_stake += stake; + } + if total_stake == 0 { + Err(format!("cosigning sets for block #{block_number} had 0 stake in total"))?; + } + + let global_session_info = GlobalSession { + // This session starts cosigning after this block, as this block must be cosigned by + // the existing validators + start_block_number: block_number + 1, + sets, + keys, + stakes, + total_stake, + }; + GlobalSessions::set(&mut txn, global_session, &global_session_info); + if let Some(ending_global_session) = global_session_for_this_block { + GlobalSessionsLastBlock::set(&mut txn, ending_global_session, &block_number); + } + LatestGlobalSessionIntended::set(&mut txn, &global_session); + GlobalSessionsChannel::send(&mut txn, &(global_session, global_session_info)); + } + + // If there isn't anyone available to cosign this block, meaning it'll never be cosigned, + // we flag it as not having any events requiring cosigning so we don't attempt to + // sign/require a cosign for it + if global_session_for_this_block.is_none() { + has_events = HasEvents::No; + } + match has_events { HasEvents::Notable | HasEvents::NonNotable => { - // TODO: Replace with LatestGlobalSessionIntended, GlobalSessions - let sets = cosigning_sets_for_block(&self.serai, &block).await?; + let global_session_for_this_block = global_session_for_this_block + .expect("global session for this block was None but still attempting to cosign it"); + let global_session_info = GlobalSessions::get(&txn, global_session_for_this_block) + .expect("last global session intended wasn't saved to the database"); - // If this block doesn't have any cosigners, meaning it'll never be cosigned, we flag - // it as not having any events requiring cosigning so we don't attempt to sign/require - // a cosign for it - if sets.is_empty() { - has_events = HasEvents::No; - } - - // If this is notable, it creates a new global session, which we index into the - // database now - if has_events == HasEvents::Notable { - let serai = self.serai.as_of(block.hash()); - let sets = cosigning_sets(&serai).await?; - let global_session = GlobalSession::id(sets.iter().map(|(set, _key)| *set).collect()); - - let mut keys = HashMap::new(); - let mut stakes = HashMap::new(); - let mut total_stake = 0; - for (set, key) in &sets { - keys.insert(set.network, SeraiAddress::from(*key)); - let stake = serai - .validator_sets() - .total_allocated_stake(set.network) - .await - .map_err(|e| format!("{e:?}"))? - .unwrap_or(Amount(0)) - .0; - stakes.insert(set.network, stake); - total_stake += stake; - } - if total_stake == 0 { - Err(format!("cosigning sets for block #{block_number} had 0 stake in total"))?; - } - - GlobalSessions::set( + // Tell each set of their expectation to cosign this block + for set in global_session_info.sets { + log::debug!("{:?} will be cosigning block #{block_number}", set); + IntendedCosigns::send( &mut txn, - global_session, - &(GlobalSession { start_block_number: block_number, keys, stakes, total_stake }), + set, + &CosignIntent { + global_session: global_session_for_this_block, + block_number, + block_hash: block.hash(), + notable: has_events == HasEvents::Notable, + }, ); - if let Some(ending_global_session) = LatestGlobalSessionIntended::get(&txn) { - GlobalSessionsLastBlock::set(&mut txn, ending_global_session, &block_number); - } - LatestGlobalSessionIntended::set(&mut txn, &global_session); - } - - if has_events != HasEvents::No { - let global_session = GlobalSession::id(sets.clone()); - // Tell each set of their expectation to cosign this block - for set in sets { - log::debug!("{:?} will be cosigning block #{block_number}", set); - IntendedCosigns::send( - &mut txn, - set, - &CosignIntent { - global_session, - block_number, - block_hash: block.hash(), - notable: has_events == HasEvents::Notable, - }, - ); - } } } HasEvents::No => {} } + // Populate a singular feed with every block's status for the evluator to work off of - BlockEvents::send( - &mut txn, - &(BlockEventData { - block_number, - parent_hash: block.header.parent_hash.into(), - block_hash: block.hash(), - has_events, - }), - ); + BlockEvents::send(&mut txn, &(BlockEventData { block_number, has_events })); // Mark this block as handled, meaning we should scan from the next block moving on ScanCosignFrom::set(&mut txn, &(block_number + 1)); txn.commit(); diff --git a/coordinator/cosign/src/lib.rs b/coordinator/cosign/src/lib.rs index baeee8ea..b6f163ab 100644 --- a/coordinator/cosign/src/lib.rs +++ b/coordinator/cosign/src/lib.rs @@ -48,6 +48,7 @@ pub const COSIGN_CONTEXT: &[u8] = b"serai-cosign"; #[derive(Debug, BorshSerialize, BorshDeserialize)] pub(crate) struct GlobalSession { pub(crate) start_block_number: u64, + pub(crate) sets: Vec, pub(crate) keys: HashMap, pub(crate) stakes: HashMap, pub(crate) total_stake: u64, @@ -120,15 +121,6 @@ struct CosignIntent { notable: bool, } -/// The identification of a cosigner. -#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] -pub enum Cosigner { - /// The network which produced this cosign. - ValidatorSet(NetworkId), - /// The individual validator which produced this cosign. - Validator(SeraiAddress), -} - /// A cosign. #[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub struct Cosign { @@ -139,7 +131,7 @@ pub struct Cosign { /// The hash of the block to cosign. pub block_hash: [u8; 32], /// The actual cosigner. - pub cosigner: Cosigner, + pub cosigner: NetworkId, } /// A signed cosign. @@ -211,29 +203,6 @@ async fn cosigning_sets(serai: &TemporalSerai<'_>) -> Result Result, String> { - /* - If we're cosigning block `n`, it's cosigned by the sets as of block `n-1` (as block `n` may - update the sets declared but that update shouldn't take effect until block `n` is cosigned). - That's why fetching the cosigning sets for a block by its parent hash is valid. - */ - let sets = cosigning_sets(&serai.as_of(parent_hash)).await?; - Ok(sets.into_iter().map(|(set, _key)| set).collect::>()) -} - -/// Fetch the `ValidatorSet`s, and their associated keys, used for cosigning this block. -async fn cosigning_sets_for_block( - serai: &Serai, - block: &Block, -) -> Result, String> { - cosigning_sets_by_parent_hash(serai, block.header.parent_hash.into()).await -} - /// An object usable to request notable cosigns for a block. pub trait RequestNotableCosigns: 'static + Send { /// The error type which may be encountered when requesting notable cosigns. @@ -267,11 +236,11 @@ impl Cosigning { let (intend_task, _intend_task_handle) = Task::new(); let (evaluator_task, evaluator_task_handle) = Task::new(); tokio::spawn( - (intend::CosignIntendTask { db: db.clone(), serai: serai.clone() }) + (intend::CosignIntendTask { db: db.clone(), serai }) .continually_run(intend_task, vec![evaluator_task_handle]), ); tokio::spawn( - (evaluator::CosignEvaluatorTask { db: db.clone(), serai, request }) + (evaluator::CosignEvaluatorTask { db: db.clone(), request }) .continually_run(evaluator_task, tasks_to_run_upon_cosigning), ); Self { db } @@ -349,12 +318,7 @@ impl Cosigning { // TODO: Don't overload bool here pub fn intake_cosign(&mut self, signed_cosign: &SignedCosign) -> Result { let cosign = &signed_cosign.cosign; - - let Cosigner::ValidatorSet(network) = cosign.cosigner else { - // TODO - // Individually signed cosign despite that protocol not being implemented - return Ok(false); - }; + let network = cosign.cosigner; // Check this isn't a dated cosign if let Some(existing) = @@ -374,7 +338,7 @@ impl Cosigning { // Unrecognized global session return Ok(true); }; - if cosign.block_number <= global_session.start_block_number { + if cosign.block_number < global_session.start_block_number { // Cosign is for a block predating the global session return Ok(false); } @@ -387,14 +351,11 @@ impl Cosigning { // Check the cosign's signature { - let key = Public::from(match cosign.cosigner { - Cosigner::ValidatorSet(network) => { - let Some(key) = global_session.keys.get(&network) else { - return Ok(false); - }; - *key - } - Cosigner::Validator(signer) => signer, + let key = Public::from({ + let Some(key) = global_session.keys.get(&network) else { + return Ok(false); + }; + *key }); if !signed_cosign.verify_signature(key) { @@ -409,7 +370,10 @@ impl Cosigning { if our_block_hash == cosign.block_hash { // If this is for a future global session, we don't acknowledge this cosign at this time - if global_session.start_block_number > LatestCosignedBlockNumber::get(&txn).unwrap_or(0) { + let latest_cosigned_block_number = LatestCosignedBlockNumber::get(&txn).unwrap_or(0); + // This global session starts the block *after* its declaration, so we want to check if the + // block declaring it was cosigned + if (global_session.start_block_number - 1) > latest_cosigned_block_number { drop(txn); return Ok(true); } @@ -418,17 +382,13 @@ impl Cosigning { } else { let mut faults = Faults::get(&txn, cosign.global_session).unwrap_or(vec![]); // Only handle this as a fault if this set wasn't prior faulty - if !faults.iter().any(|cosign| cosign.cosign.cosigner == Cosigner::ValidatorSet(network)) { + if !faults.iter().any(|cosign| cosign.cosign.cosigner == network) { faults.push(signed_cosign.clone()); Faults::set(&mut txn, cosign.global_session, &faults); let mut weight_cosigned = 0; for fault in &faults { - let Cosigner::ValidatorSet(network) = fault.cosign.cosigner else { - // TODO when we implement the non-ValidatorSet cosigner protocol - Err("non-ValidatorSet cosigner had a fault".to_string())? - }; - let Some(stake) = global_session.stakes.get(&network) else { + let Some(stake) = global_session.stakes.get(&fault.cosign.cosigner) else { Err("cosigner with recognized key didn't have a stake entry saved".to_string())? }; weight_cosigned += stake;