2024-12-22 06:41:55 -05:00
|
|
|
use core::future::Future;
|
|
|
|
|
|
|
|
|
|
use serai_db::*;
|
|
|
|
|
use serai_task::ContinuallyRan;
|
|
|
|
|
|
2024-12-25 01:45:37 -05:00
|
|
|
use crate::{
|
2024-12-25 23:21:25 -05:00
|
|
|
HasEvents, GlobalSession, NetworksLatestCosignedBlock, RequestNotableCosigns,
|
2024-12-26 00:15:49 -05:00
|
|
|
intend::{GlobalSessionsChannel, BlockEventData, BlockEvents},
|
2024-12-25 01:45:37 -05:00
|
|
|
};
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
create_db!(
|
|
|
|
|
SubstrateCosignEvaluator {
|
2024-12-25 01:45:37 -05:00
|
|
|
// The latest cosigned block number.
|
2024-12-22 06:41:55 -05:00
|
|
|
LatestCosignedBlockNumber: () -> u64,
|
2024-12-25 23:51:24 -05:00
|
|
|
// The global session currently being evaluated.
|
|
|
|
|
CurrentlyEvaluatedGlobalSession: () -> ([u8; 32], GlobalSession),
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2024-12-25 23:51:24 -05:00
|
|
|
// This is a strict function which won't panic, even with a malicious Serai node, so long as:
|
|
|
|
|
// - It's called incrementally
|
|
|
|
|
// - It's only called for block numbers we've completed indexing on within the intend task
|
|
|
|
|
// - It's only called for block numbers after a global session has started
|
|
|
|
|
// - The global sessions channel is populated as the block declaring the session is indexed
|
|
|
|
|
// Which all hold true within the context of this task and the intend task.
|
|
|
|
|
//
|
|
|
|
|
// This function will also ensure the currently evaluated global session is incremented once we
|
|
|
|
|
// finish evaluation of the prior session.
|
|
|
|
|
fn currently_evaluated_global_session_strict(
|
2024-12-25 01:45:37 -05:00
|
|
|
txn: &mut impl DbTxn,
|
2024-12-25 23:21:25 -05:00
|
|
|
block_number: u64,
|
|
|
|
|
) -> ([u8; 32], GlobalSession) {
|
|
|
|
|
let mut res = {
|
2024-12-25 23:51:24 -05:00
|
|
|
let existing = match CurrentlyEvaluatedGlobalSession::get(txn) {
|
2024-12-25 23:21:25 -05:00
|
|
|
Some(existing) => existing,
|
|
|
|
|
None => {
|
2024-12-26 00:15:49 -05:00
|
|
|
let first = GlobalSessionsChannel::try_recv(txn)
|
|
|
|
|
.expect("fetching latest global session yet none declared");
|
2024-12-25 23:51:24 -05:00
|
|
|
CurrentlyEvaluatedGlobalSession::set(txn, &first);
|
2024-12-25 23:21:25 -05:00
|
|
|
first
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
assert!(
|
|
|
|
|
existing.1.start_block_number <= block_number,
|
|
|
|
|
"candidate's start block number exceeds our block number"
|
|
|
|
|
);
|
|
|
|
|
existing
|
|
|
|
|
};
|
|
|
|
|
|
2024-12-26 00:15:49 -05:00
|
|
|
if let Some(next) = GlobalSessionsChannel::peek(txn) {
|
2024-12-25 23:21:25 -05:00
|
|
|
assert!(
|
|
|
|
|
block_number <= next.1.start_block_number,
|
2024-12-25 23:51:24 -05:00
|
|
|
"currently_evaluated_global_session_strict wasn't called incrementally"
|
2024-12-25 23:21:25 -05:00
|
|
|
);
|
|
|
|
|
// 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 {
|
2024-12-26 00:15:49 -05:00
|
|
|
GlobalSessionsChannel::try_recv(txn).unwrap();
|
2024-12-25 23:51:24 -05:00
|
|
|
CurrentlyEvaluatedGlobalSession::set(txn, &next);
|
2024-12-25 23:21:25 -05:00
|
|
|
res = next;
|
2024-12-25 01:45:37 -05:00
|
|
|
}
|
2024-12-25 23:21:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res
|
2024-12-25 01:45:37 -05:00
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
|
2024-12-25 23:51:24 -05:00
|
|
|
/// A task to determine if a block has been cosigned and we should handle it.
|
|
|
|
|
pub(crate) struct CosignEvaluatorTask<D: Db, R: RequestNotableCosigns> {
|
|
|
|
|
pub(crate) db: D,
|
|
|
|
|
pub(crate) request: R,
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-22 06:41:55 -05:00
|
|
|
impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D, R> {
|
|
|
|
|
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
|
|
|
|
async move {
|
|
|
|
|
let latest_cosigned_block_number = LatestCosignedBlockNumber::get(&self.db).unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
let mut known_cosign = None;
|
|
|
|
|
let mut made_progress = false;
|
|
|
|
|
loop {
|
|
|
|
|
let mut txn = self.db.txn();
|
2024-12-25 23:21:25 -05:00
|
|
|
let Some(BlockEventData { block_number, has_events }) = BlockEvents::try_recv(&mut txn)
|
2024-12-25 01:45:37 -05:00
|
|
|
else {
|
|
|
|
|
break;
|
|
|
|
|
};
|
2024-12-22 06:41:55 -05:00
|
|
|
// Make sure these two feeds haven't desynchronized somehow
|
|
|
|
|
// We could remove our `LatestCosignedBlockNumber`, making the latest cosigned block number
|
|
|
|
|
// the next message in the channel's block number minus one, but that'd only work when the
|
|
|
|
|
// channel isn't empty
|
|
|
|
|
assert_eq!(block_number, latest_cosigned_block_number + 1);
|
|
|
|
|
|
|
|
|
|
match has_events {
|
|
|
|
|
// 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 => {
|
2024-12-25 23:21:25 -05:00
|
|
|
let (global_session, global_session_info) =
|
2024-12-25 23:51:24 -05:00
|
|
|
currently_evaluated_global_session_strict(&mut txn, block_number);
|
2024-12-25 01:45:37 -05:00
|
|
|
|
2024-12-22 06:41:55 -05:00
|
|
|
let mut weight_cosigned = 0;
|
2024-12-25 23:21:25 -05:00
|
|
|
for set in global_session_info.sets {
|
2024-12-22 06:41:55 -05:00
|
|
|
// Check if we have the cosign from this set
|
2024-12-25 01:45:37 -05:00
|
|
|
if NetworksLatestCosignedBlock::get(&txn, global_session, set.network)
|
|
|
|
|
.map(|signed_cosign| signed_cosign.cosign.block_number) ==
|
|
|
|
|
Some(block_number)
|
2024-12-22 06:41:55 -05:00
|
|
|
{
|
|
|
|
|
// Since have this cosign, add the set's weight to the weight which has cosigned
|
2024-12-25 21:19:04 -05:00
|
|
|
weight_cosigned +=
|
|
|
|
|
global_session_info.stakes.get(&set.network).ok_or_else(|| {
|
|
|
|
|
"ValidatorSet in global session yet didn't have its stake".to_string()
|
|
|
|
|
})?;
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Check if the sum weight doesn't cross the required threshold
|
2024-12-25 21:19:04 -05:00
|
|
|
if weight_cosigned < (((global_session_info.total_stake * 83) / 100) + 1) {
|
2024-12-22 06:41:55 -05:00
|
|
|
// Request the necessary cosigns over the network
|
|
|
|
|
// TODO: Add a timer to ensure this isn't called too often
|
|
|
|
|
self
|
|
|
|
|
.request
|
|
|
|
|
.request_notable_cosigns(global_session)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("{e:?}"))?;
|
|
|
|
|
// We return an error so the delay before this task is run again increases
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"notable block (#{block_number}) wasn't yet cosigned. this should resolve shortly",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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
|
|
|
|
|
HasEvents::NonNotable => {
|
|
|
|
|
// Check if this was satisfied by a cached result which wasn't calculated incrementally
|
|
|
|
|
let known_cosigned = if let Some(known_cosign) = known_cosign {
|
|
|
|
|
known_cosign >= block_number
|
|
|
|
|
} else {
|
|
|
|
|
// Clear `known_cosign` which is no longer helpful
|
|
|
|
|
known_cosign = None;
|
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If it isn't already known to be cosigned, evaluate the latest cosigns
|
|
|
|
|
if !known_cosigned {
|
|
|
|
|
/*
|
|
|
|
|
LatestCosign is populated with the latest cosigns for each network which don't
|
|
|
|
|
exceed the latest global session we've evaluated the start of. This current block
|
|
|
|
|
is during the latest global session we've evaluated the start of.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Get the global session for this block
|
2024-12-25 23:21:25 -05:00
|
|
|
let (global_session, global_session_info) =
|
2024-12-25 23:51:24 -05:00
|
|
|
currently_evaluated_global_session_strict(&mut txn, block_number);
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
let mut weight_cosigned = 0;
|
|
|
|
|
let mut lowest_common_block: Option<u64> = None;
|
2024-12-25 23:21:25 -05:00
|
|
|
for set in global_session_info.sets {
|
2024-12-22 06:41:55 -05:00
|
|
|
// Check if this set cosigned this block or not
|
2024-12-25 01:45:37 -05:00
|
|
|
let Some(cosign) =
|
|
|
|
|
NetworksLatestCosignedBlock::get(&txn, global_session, set.network)
|
|
|
|
|
else {
|
2024-12-22 06:41:55 -05:00
|
|
|
continue;
|
|
|
|
|
};
|
2024-12-25 00:06:46 -05:00
|
|
|
if cosign.cosign.block_number >= block_number {
|
2024-12-25 21:19:04 -05:00
|
|
|
weight_cosigned +=
|
|
|
|
|
global_session_info.stakes.get(&set.network).ok_or_else(|| {
|
|
|
|
|
"ValidatorSet in global session yet didn't have its stake".to_string()
|
|
|
|
|
})?;
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the lowest block common to all of these cosigns
|
|
|
|
|
lowest_common_block = lowest_common_block
|
2024-12-25 00:06:46 -05:00
|
|
|
.map(|existing| existing.min(cosign.cosign.block_number))
|
|
|
|
|
.or(Some(cosign.cosign.block_number));
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the sum weight doesn't cross the required threshold
|
2024-12-25 21:19:04 -05:00
|
|
|
if weight_cosigned < (((global_session_info.total_stake * 83) / 100) + 1) {
|
2024-12-22 06:41:55 -05:00
|
|
|
// Request the superseding notable cosigns over the network
|
|
|
|
|
// If this session hasn't yet produced notable cosigns, then we presume we'll see
|
|
|
|
|
// the desired non-notable cosigns as part of normal operations, without needing to
|
|
|
|
|
// explicitly request them
|
|
|
|
|
self
|
|
|
|
|
.request
|
|
|
|
|
.request_notable_cosigns(global_session)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("{e:?}"))?;
|
|
|
|
|
// We return an error so the delay before this task is run again increases
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"block (#{block_number}) wasn't yet cosigned. this should resolve shortly",
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the cached result for the block we know is cosigned
|
2024-12-25 01:45:37 -05:00
|
|
|
/*
|
|
|
|
|
There may be a higher block which was cosigned, but once we get to this block,
|
|
|
|
|
we'll re-evaluate and find it then. The alternative would be an optimistic
|
|
|
|
|
re-evaluation now. Both are fine, so the lower-complexity option is preferred.
|
|
|
|
|
*/
|
2024-12-22 06:41:55 -05:00
|
|
|
known_cosign = lowest_common_block;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If this block has no events necessitating cosigning, we can immediately consider the
|
|
|
|
|
// block cosigned (making this block a NOP)
|
|
|
|
|
HasEvents::No => {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since we checked we had the necessary cosigns, increment the latest cosigned block
|
|
|
|
|
LatestCosignedBlockNumber::set(&mut txn, &block_number);
|
|
|
|
|
txn.commit();
|
|
|
|
|
|
|
|
|
|
made_progress = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(made_progress)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|