mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Remove serai from the cosign evaluator
This commit is contained in:
@@ -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<ValidatorSet>),
|
||||
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<D: Db, R: RequestNotableCosigns> {
|
||||
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<ValidatorSet>), 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<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D, R> {
|
||||
@@ -54,8 +69,7 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
||||
let mut made_progress = false;
|
||||
loop {
|
||||
let mut txn = self.db.txn();
|
||||
let Some(BlockEventData { block_number, parent_hash, block_hash, has_events }) =
|
||||
BlockEvents::try_recv(&mut txn)
|
||||
let Some(BlockEventData { block_number, has_events }) = BlockEvents::try_recv(&mut txn)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
@@ -69,16 +83,11 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
||||
// Because this had notable events, we require an explicit cosign for this block by a
|
||||
// supermajority of the prior block's validator sets
|
||||
HasEvents::Notable => {
|
||||
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<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
||||
"notable block (#{block_number}) wasn't yet cosigned. this should resolve shortly",
|
||||
));
|
||||
}
|
||||
|
||||
// Since this block changes the global session, update it
|
||||
{
|
||||
let sets = cosigning_sets(&self.serai.as_of(block_hash)).await?;
|
||||
let sets = sets.into_iter().map(|(set, _key)| set).collect::<Vec<_>>();
|
||||
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<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
||||
*/
|
||||
|
||||
// Get the global session for this block
|
||||
let (global_session, sets) =
|
||||
get_latest_global_session_evaluated(&mut txn, &self.serai, parent_hash).await?;
|
||||
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()
|
||||
})?;
|
||||
let (global_session, global_session_info) =
|
||||
get_latest_global_session_evaluated(&mut txn, block_number);
|
||||
|
||||
let mut weight_cosigned = 0;
|
||||
let mut lowest_common_block: Option<u64> = 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)
|
||||
|
||||
@@ -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<D: Db> ContinuallyRan for CosignIntendTask<D> {
|
||||
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();
|
||||
|
||||
@@ -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<ValidatorSet>,
|
||||
pub(crate) keys: HashMap<NetworkId, SeraiAddress>,
|
||||
pub(crate) stakes: HashMap<NetworkId, u64>,
|
||||
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<Vec<(ValidatorSet,
|
||||
Ok(sets)
|
||||
}
|
||||
|
||||
/// Fetch the `ValidatorSet`s, and their associated keys, used for cosigning a block by the block's
|
||||
/// parent hash.
|
||||
async fn cosigning_sets_by_parent_hash(
|
||||
serai: &Serai,
|
||||
parent_hash: [u8; 32],
|
||||
) -> Result<Vec<ValidatorSet>, 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::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// Fetch the `ValidatorSet`s, and their associated keys, used for cosigning this block.
|
||||
async fn cosigning_sets_for_block(
|
||||
serai: &Serai,
|
||||
block: &Block,
|
||||
) -> Result<Vec<ValidatorSet>, 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<D: Db> Cosigning<D> {
|
||||
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<D: Db> Cosigning<D> {
|
||||
// TODO: Don't overload bool here
|
||||
pub fn intake_cosign(&mut self, signed_cosign: &SignedCosign) -> Result<bool, String> {
|
||||
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<D: Db> Cosigning<D> {
|
||||
// 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<D: Db> Cosigning<D> {
|
||||
|
||||
// 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<D: Db> Cosigning<D> {
|
||||
|
||||
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<D: Db> Cosigning<D> {
|
||||
} 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;
|
||||
|
||||
Reference in New Issue
Block a user