2025-11-04 10:20:17 -05:00
|
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
2024-12-22 06:41:55 -05:00
|
|
|
#![doc = include_str!("../README.md")]
|
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
|
|
|
|
|
|
use core::{fmt::Debug, future::Future};
|
2025-01-15 05:59:56 -05:00
|
|
|
use std::{sync::Arc, collections::HashMap, time::Instant};
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
use blake2::{Digest, Blake2s256};
|
|
|
|
|
|
|
|
|
|
use borsh::{BorshSerialize, BorshDeserialize};
|
|
|
|
|
|
2025-11-16 11:50:24 -05:00
|
|
|
use serai_client_serai::{
|
|
|
|
|
abi::{
|
|
|
|
|
primitives::{
|
|
|
|
|
BlockHash,
|
|
|
|
|
crypto::{Public, KeyPair},
|
|
|
|
|
network_id::ExternalNetworkId,
|
|
|
|
|
validator_sets::{Session, ExternalValidatorSet},
|
|
|
|
|
address::SeraiAddress,
|
|
|
|
|
},
|
|
|
|
|
Block,
|
2025-11-04 19:39:40 -05:00
|
|
|
},
|
2025-11-16 11:50:24 -05:00
|
|
|
Serai, TemporalSerai,
|
2024-12-22 06:41:55 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use serai_db::*;
|
|
|
|
|
use serai_task::*;
|
|
|
|
|
|
2025-11-16 11:50:24 -05:00
|
|
|
pub use serai_cosign_types::*;
|
2025-09-02 02:16:21 -04:00
|
|
|
|
2024-12-22 06:41:55 -05:00
|
|
|
/// The cosigns which are intended to be performed.
|
|
|
|
|
mod intend;
|
|
|
|
|
/// The evaluator of the cosigns.
|
|
|
|
|
mod evaluator;
|
2024-12-26 01:02:44 -05:00
|
|
|
/// The task to delay acknowledgement of the cosigns.
|
|
|
|
|
mod delay;
|
|
|
|
|
pub use delay::BROADCAST_FREQUENCY;
|
|
|
|
|
use delay::LatestCosignedBlockNumber;
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
/// A 'global session', defined as all validator sets used for cosigning at a given moment.
|
|
|
|
|
///
|
|
|
|
|
/// We evaluate cosign faults within a global session. This ensures even if cosigners cosign
|
|
|
|
|
/// distinct blocks at distinct positions within a global session, we still identify the faults.
|
|
|
|
|
/*
|
|
|
|
|
There is the attack where a validator set is given an alternate blockchain with a key generation
|
|
|
|
|
event at block #n, while most validator sets are given a blockchain with a key generation event
|
|
|
|
|
at block number #(n+1). This prevents whoever has the alternate blockchain from verifying the
|
|
|
|
|
cosigns on the primary blockchain, and detecting the faults, if they use the keys as of the block
|
|
|
|
|
prior to the block being cosigned.
|
|
|
|
|
|
|
|
|
|
We solve this by binding cosigns to a global session ID, which has a specific start block, and
|
|
|
|
|
reading the keys from the start block. This means that so long as all validator sets agree on the
|
|
|
|
|
start of a global session, they can verify all cosigns produced by that session, regardless of
|
|
|
|
|
how it advances. Since agreeing on the start of a global session is mandated, there's no way to
|
|
|
|
|
have validator sets follow two distinct global sessions without breaking the bounds of the
|
|
|
|
|
cosigning protocol.
|
|
|
|
|
*/
|
2024-12-26 00:15:49 -05:00
|
|
|
#[derive(Debug, BorshSerialize, BorshDeserialize)]
|
2024-12-25 21:19:04 -05:00
|
|
|
pub(crate) struct GlobalSession {
|
|
|
|
|
pub(crate) start_block_number: u64,
|
2025-01-30 03:14:24 -05:00
|
|
|
pub(crate) sets: Vec<ExternalValidatorSet>,
|
|
|
|
|
pub(crate) keys: HashMap<ExternalNetworkId, SeraiAddress>,
|
|
|
|
|
pub(crate) stakes: HashMap<ExternalNetworkId, u64>,
|
2024-12-25 21:19:04 -05:00
|
|
|
pub(crate) total_stake: u64,
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
impl GlobalSession {
|
2025-01-30 03:14:24 -05:00
|
|
|
fn id(mut cosigners: Vec<ExternalValidatorSet>) -> [u8; 32] {
|
2024-12-22 06:41:55 -05:00
|
|
|
cosigners.sort_by_key(|a| borsh::to_vec(a).unwrap());
|
2024-12-25 21:19:04 -05:00
|
|
|
Blake2s256::digest(borsh::to_vec(&cosigners).unwrap()).into()
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If the block has events.
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
|
|
|
|
enum HasEvents {
|
|
|
|
|
/// The block had a notable event.
|
|
|
|
|
///
|
|
|
|
|
/// This is a special case as blocks with key gen events change the keys used for cosigning, and
|
|
|
|
|
/// accordingly must be cosigned before we advance past them.
|
|
|
|
|
Notable,
|
|
|
|
|
/// The block had an non-notable event justifying a cosign.
|
|
|
|
|
NonNotable,
|
|
|
|
|
/// The block didn't have an event justifying a cosign.
|
|
|
|
|
No,
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-31 10:37:19 -05:00
|
|
|
create_db! {
|
|
|
|
|
Cosign {
|
|
|
|
|
// The following are populated by the intend task and used throughout the library
|
|
|
|
|
|
|
|
|
|
// An index of Substrate blocks
|
2025-11-15 16:10:25 -05:00
|
|
|
SubstrateBlockHash: (block_number: u64) -> BlockHash,
|
2024-12-31 10:37:19 -05:00
|
|
|
// A mapping from a global session's ID to its relevant information.
|
|
|
|
|
GlobalSessions: (global_session: [u8; 32]) -> GlobalSession,
|
|
|
|
|
// The last block to be cosigned by a global session.
|
|
|
|
|
GlobalSessionsLastBlock: (global_session: [u8; 32]) -> u64,
|
|
|
|
|
// The latest global session intended.
|
|
|
|
|
//
|
|
|
|
|
// This is distinct from the latest global session for which we've evaluated the cosigns for.
|
|
|
|
|
LatestGlobalSessionIntended: () -> [u8; 32],
|
|
|
|
|
|
|
|
|
|
// The following are managed by the `intake_cosign` function present in this file
|
|
|
|
|
|
|
|
|
|
// The latest cosigned block for each network.
|
|
|
|
|
//
|
|
|
|
|
// This will only be populated with cosigns predating or during the most recent global session
|
|
|
|
|
// to have its start cosigned.
|
|
|
|
|
//
|
|
|
|
|
// The global session changes upon a notable block, causing each global session to have exactly
|
|
|
|
|
// one notable block. All validator sets will explicitly produce a cosign for their notable
|
|
|
|
|
// block, causing the latest cosigned block for a global session to either be the global
|
|
|
|
|
// session's notable cosigns or the network's latest cosigns.
|
2025-01-30 03:14:24 -05:00
|
|
|
NetworksLatestCosignedBlock: (
|
|
|
|
|
global_session: [u8; 32],
|
|
|
|
|
network: ExternalNetworkId
|
|
|
|
|
) -> SignedCosign,
|
2024-12-31 10:37:19 -05:00
|
|
|
// Cosigns received for blocks not locally recognized as finalized.
|
|
|
|
|
Faults: (global_session: [u8; 32]) -> Vec<SignedCosign>,
|
|
|
|
|
// The global session which faulted.
|
|
|
|
|
FaultedSession: () -> [u8; 32],
|
2024-12-25 00:06:46 -05:00
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetch the keys used for cosigning by a specific network.
|
|
|
|
|
async fn keys_for_network(
|
|
|
|
|
serai: &TemporalSerai<'_>,
|
2025-01-30 03:14:24 -05:00
|
|
|
network: ExternalNetworkId,
|
2024-12-22 06:41:55 -05:00
|
|
|
) -> Result<Option<(Session, KeyPair)>, String> {
|
|
|
|
|
let Some(latest_session) =
|
2025-11-15 16:10:25 -05:00
|
|
|
serai.validator_sets().current_session(network.into()).await.map_err(|e| format!("{e:?}"))?
|
2024-12-22 06:41:55 -05:00
|
|
|
else {
|
|
|
|
|
// If this network hasn't had a session declared, move on
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Get the keys for the latest session
|
|
|
|
|
if let Some(keys) = serai
|
|
|
|
|
.validator_sets()
|
2025-01-30 03:14:24 -05:00
|
|
|
.keys(ExternalValidatorSet { network, session: latest_session })
|
2024-12-22 06:41:55 -05:00
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("{e:?}"))?
|
|
|
|
|
{
|
|
|
|
|
return Ok(Some((latest_session, keys)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the latest session has yet to set keys, use the prior session
|
|
|
|
|
if let Some(prior_session) = latest_session.0.checked_sub(1).map(Session) {
|
|
|
|
|
if let Some(keys) = serai
|
|
|
|
|
.validator_sets()
|
2025-01-30 03:14:24 -05:00
|
|
|
.keys(ExternalValidatorSet { network, session: prior_session })
|
2024-12-22 06:41:55 -05:00
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("{e:?}"))?
|
|
|
|
|
{
|
|
|
|
|
return Ok(Some((prior_session, keys)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 03:14:24 -05:00
|
|
|
/// Fetch the `ExternalValidatorSet`s, and their associated keys, used for cosigning as of this
|
|
|
|
|
/// block.
|
|
|
|
|
async fn cosigning_sets(
|
|
|
|
|
serai: &TemporalSerai<'_>,
|
|
|
|
|
) -> Result<Vec<(ExternalValidatorSet, Public)>, String> {
|
2025-11-04 19:39:40 -05:00
|
|
|
let mut sets = vec![];
|
|
|
|
|
for network in ExternalNetworkId::all() {
|
2024-12-25 21:19:04 -05:00
|
|
|
let Some((session, keys)) = keys_for_network(serai, network).await? else {
|
2024-12-22 06:41:55 -05:00
|
|
|
// If this network doesn't have usable keys, move on
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
2025-01-30 03:14:24 -05:00
|
|
|
sets.push((ExternalValidatorSet { network, session }, keys.0));
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
Ok(sets)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
|
type Error: Debug;
|
|
|
|
|
|
|
|
|
|
/// Request the notable cosigns for this global session.
|
|
|
|
|
fn request_notable_cosigns(
|
|
|
|
|
&self,
|
|
|
|
|
global_session: [u8; 32],
|
|
|
|
|
) -> impl Send + Future<Output = Result<(), Self::Error>>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An error used to indicate the cosigning protocol has faulted.
|
2024-12-31 10:37:19 -05:00
|
|
|
#[derive(Debug)]
|
2024-12-22 06:41:55 -05:00
|
|
|
pub struct Faulted;
|
|
|
|
|
|
2025-01-12 05:49:17 -05:00
|
|
|
/// An error incurred while intaking a cosign.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub enum IntakeCosignError {
|
|
|
|
|
/// Cosign is for a not-yet-indexed block
|
|
|
|
|
NotYetIndexedBlock,
|
|
|
|
|
/// A later cosign for this cosigner has already been handled
|
|
|
|
|
StaleCosign,
|
|
|
|
|
/// The cosign's global session isn't recognized
|
|
|
|
|
UnrecognizedGlobalSession,
|
|
|
|
|
/// The cosign is for a block before its global session starts
|
|
|
|
|
BeforeGlobalSessionStart,
|
|
|
|
|
/// The cosign is for a block after its global session ends
|
|
|
|
|
AfterGlobalSessionEnd,
|
|
|
|
|
/// The cosign's signing network wasn't a participant in this global session
|
|
|
|
|
NonParticipatingNetwork,
|
|
|
|
|
/// The cosign had an invalid signature
|
|
|
|
|
InvalidSignature,
|
|
|
|
|
/// The cosign is for a global session which has yet to have its declaration block cosigned
|
|
|
|
|
FutureGlobalSession,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl IntakeCosignError {
|
|
|
|
|
/// If this error is temporal to the local view
|
|
|
|
|
pub fn temporal(&self) -> bool {
|
|
|
|
|
match self {
|
|
|
|
|
IntakeCosignError::NotYetIndexedBlock |
|
|
|
|
|
IntakeCosignError::StaleCosign |
|
|
|
|
|
IntakeCosignError::UnrecognizedGlobalSession |
|
|
|
|
|
IntakeCosignError::FutureGlobalSession => true,
|
|
|
|
|
IntakeCosignError::BeforeGlobalSessionStart |
|
|
|
|
|
IntakeCosignError::AfterGlobalSessionEnd |
|
|
|
|
|
IntakeCosignError::NonParticipatingNetwork |
|
|
|
|
|
IntakeCosignError::InvalidSignature => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-22 06:41:55 -05:00
|
|
|
/// The interface to manage cosigning with.
|
|
|
|
|
pub struct Cosigning<D: Db> {
|
|
|
|
|
db: D,
|
|
|
|
|
}
|
|
|
|
|
impl<D: Db> Cosigning<D> {
|
|
|
|
|
/// Spawn the tasks to intend and evaluate cosigns.
|
|
|
|
|
///
|
|
|
|
|
/// The database specified must only be used with a singular instance of the Serai network, and
|
|
|
|
|
/// only used once at any given time.
|
|
|
|
|
pub fn spawn<R: RequestNotableCosigns>(
|
|
|
|
|
db: D,
|
2025-01-10 01:20:26 -05:00
|
|
|
serai: Arc<Serai>,
|
2024-12-22 06:41:55 -05:00
|
|
|
request: R,
|
|
|
|
|
tasks_to_run_upon_cosigning: Vec<TaskHandle>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
let (intend_task, _intend_task_handle) = Task::new();
|
|
|
|
|
let (evaluator_task, evaluator_task_handle) = Task::new();
|
2024-12-26 01:02:44 -05:00
|
|
|
let (delay_task, delay_task_handle) = Task::new();
|
2024-12-22 06:41:55 -05:00
|
|
|
tokio::spawn(
|
2024-12-25 23:21:25 -05:00
|
|
|
(intend::CosignIntendTask { db: db.clone(), serai })
|
2024-12-22 06:41:55 -05:00
|
|
|
.continually_run(intend_task, vec![evaluator_task_handle]),
|
|
|
|
|
);
|
|
|
|
|
tokio::spawn(
|
2025-01-15 05:59:56 -05:00
|
|
|
(evaluator::CosignEvaluatorTask {
|
|
|
|
|
db: db.clone(),
|
|
|
|
|
request,
|
|
|
|
|
last_request_for_cosigns: Instant::now(),
|
|
|
|
|
})
|
|
|
|
|
.continually_run(evaluator_task, vec![delay_task_handle]),
|
2024-12-26 01:02:44 -05:00
|
|
|
);
|
|
|
|
|
tokio::spawn(
|
|
|
|
|
(delay::CosignDelayTask { db: db.clone() })
|
|
|
|
|
.continually_run(delay_task, tasks_to_run_upon_cosigning),
|
2024-12-22 06:41:55 -05:00
|
|
|
);
|
2024-12-25 21:19:04 -05:00
|
|
|
Self { db }
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The latest cosigned block number.
|
2024-12-31 10:37:19 -05:00
|
|
|
pub fn latest_cosigned_block_number(getter: &impl Get) -> Result<u64, Faulted> {
|
|
|
|
|
if FaultedSession::get(getter).is_some() {
|
2024-12-22 06:41:55 -05:00
|
|
|
Err(Faulted)?;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-31 10:37:19 -05:00
|
|
|
Ok(LatestCosignedBlockNumber::get(getter).unwrap_or(0))
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-09 06:57:26 -05:00
|
|
|
/// Fetch a cosigned Substrate block's hash by its block number.
|
2025-11-15 16:10:25 -05:00
|
|
|
pub fn cosigned_block(
|
|
|
|
|
getter: &impl Get,
|
|
|
|
|
block_number: u64,
|
|
|
|
|
) -> Result<Option<BlockHash>, Faulted> {
|
2024-12-31 10:37:19 -05:00
|
|
|
if block_number > Self::latest_cosigned_block_number(getter)? {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(Some(
|
2025-01-09 06:57:26 -05:00
|
|
|
SubstrateBlockHash::get(getter, block_number).expect("cosigned block but didn't index it"),
|
2024-12-31 10:37:19 -05:00
|
|
|
))
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Fetch the notable cosigns for a global session in order to respond to requests.
|
2024-12-25 01:45:37 -05:00
|
|
|
///
|
|
|
|
|
/// If this global session hasn't produced any notable cosigns, this will return the latest
|
|
|
|
|
/// cosigns for this session.
|
2025-01-05 01:23:28 -05:00
|
|
|
pub fn notable_cosigns(getter: &impl Get, global_session: [u8; 32]) -> Vec<SignedCosign> {
|
2025-11-04 19:39:40 -05:00
|
|
|
let mut cosigns = vec![];
|
|
|
|
|
for network in ExternalNetworkId::all() {
|
2025-01-05 01:23:28 -05:00
|
|
|
if let Some(cosign) = NetworksLatestCosignedBlock::get(getter, global_session, network) {
|
2024-12-25 01:45:37 -05:00
|
|
|
cosigns.push(cosign);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cosigns
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-26 01:02:44 -05:00
|
|
|
/// The cosigns to rebroadcast every `BROADCAST_FREQUENCY` seconds.
|
2024-12-22 06:41:55 -05:00
|
|
|
///
|
|
|
|
|
/// This will be the most recent cosigns, in case the initial broadcast failed, or the faulty
|
|
|
|
|
/// cosigns, in case of a fault, to induce identification of the fault by others.
|
2024-12-25 00:06:46 -05:00
|
|
|
pub fn cosigns_to_rebroadcast(&self) -> Vec<SignedCosign> {
|
2024-12-22 06:41:55 -05:00
|
|
|
if let Some(faulted) = FaultedSession::get(&self.db) {
|
2024-12-25 02:11:05 -05:00
|
|
|
let mut cosigns = Faults::get(&self.db, faulted).expect("faulted with no faults");
|
2024-12-22 06:41:55 -05:00
|
|
|
// Also include all of our recognized-as-honest cosigns in an attempt to induce fault
|
|
|
|
|
// identification in those who see the faulty cosigns as honest
|
2025-11-04 19:39:40 -05:00
|
|
|
for network in ExternalNetworkId::all() {
|
2024-12-25 01:45:37 -05:00
|
|
|
if let Some(cosign) = NetworksLatestCosignedBlock::get(&self.db, faulted, network) {
|
2024-12-25 00:06:46 -05:00
|
|
|
if cosign.cosign.global_session == faulted {
|
2024-12-22 06:41:55 -05:00
|
|
|
cosigns.push(cosign);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cosigns
|
|
|
|
|
} else {
|
2024-12-31 17:17:12 -05:00
|
|
|
let Some(global_session) = evaluator::currently_evaluated_global_session(&self.db) else {
|
2024-12-25 01:45:37 -05:00
|
|
|
return vec![];
|
|
|
|
|
};
|
2025-11-04 19:39:40 -05:00
|
|
|
let mut cosigns = vec![];
|
|
|
|
|
for network in ExternalNetworkId::all() {
|
2024-12-31 17:17:12 -05:00
|
|
|
if let Some(cosign) = NetworksLatestCosignedBlock::get(&self.db, global_session, network) {
|
2024-12-22 06:41:55 -05:00
|
|
|
cosigns.push(cosign);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cosigns
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-10 01:20:26 -05:00
|
|
|
/// Intake a cosign.
|
2024-12-22 06:41:55 -05:00
|
|
|
//
|
|
|
|
|
// Takes `&mut self` as this should only be called once at any given moment.
|
2025-01-12 05:49:17 -05:00
|
|
|
pub fn intake_cosign(&mut self, signed_cosign: &SignedCosign) -> Result<(), IntakeCosignError> {
|
2024-12-25 00:06:46 -05:00
|
|
|
let cosign = &signed_cosign.cosign;
|
2024-12-25 23:21:25 -05:00
|
|
|
let network = cosign.cosigner;
|
2024-12-25 01:45:37 -05:00
|
|
|
|
2024-12-26 00:15:49 -05:00
|
|
|
// Check our indexed blockchain includes a block with this block number
|
2025-01-09 06:57:26 -05:00
|
|
|
let Some(our_block_hash) = SubstrateBlockHash::get(&self.db, cosign.block_number) else {
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::NotYetIndexedBlock)?
|
2024-12-26 00:15:49 -05:00
|
|
|
};
|
2024-12-26 00:24:48 -05:00
|
|
|
let faulty = cosign.block_hash != our_block_hash;
|
2024-12-26 00:15:49 -05:00
|
|
|
|
2024-12-25 23:51:24 -05:00
|
|
|
// Check this isn't a dated cosign within its global session (as it would be if rebroadcasted)
|
2024-12-26 00:24:48 -05:00
|
|
|
if !faulty {
|
|
|
|
|
if let Some(existing) =
|
|
|
|
|
NetworksLatestCosignedBlock::get(&self.db, cosign.global_session, network)
|
|
|
|
|
{
|
|
|
|
|
if existing.cosign.block_number >= cosign.block_number {
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::StaleCosign)?;
|
2024-12-26 00:24:48 -05:00
|
|
|
}
|
2024-12-25 01:45:37 -05:00
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-26 00:15:49 -05:00
|
|
|
let Some(global_session) = GlobalSessions::get(&self.db, cosign.global_session) else {
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::UnrecognizedGlobalSession)?
|
2024-12-22 06:41:55 -05:00
|
|
|
};
|
2024-12-25 23:51:24 -05:00
|
|
|
|
|
|
|
|
// Check the cosigned block number is in range to the global session
|
2024-12-26 00:15:49 -05:00
|
|
|
if cosign.block_number < global_session.start_block_number {
|
2024-12-25 01:45:37 -05:00
|
|
|
// Cosign is for a block predating the global session
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::BeforeGlobalSessionStart)?;
|
2024-12-25 01:45:37 -05:00
|
|
|
}
|
2024-12-26 00:26:48 -05:00
|
|
|
if !faulty {
|
2024-12-26 01:02:44 -05:00
|
|
|
// This prevents a malicious validator set, on the same chain, from producing a cosign after
|
|
|
|
|
// their final block, replacing their notable cosign
|
2024-12-26 00:26:48 -05:00
|
|
|
if let Some(last_block) = GlobalSessionsLastBlock::get(&self.db, cosign.global_session) {
|
|
|
|
|
if cosign.block_number > last_block {
|
|
|
|
|
// Cosign is for a block after the last block this global session should have signed
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::AfterGlobalSessionEnd)?;
|
2024-12-26 00:26:48 -05:00
|
|
|
}
|
2024-12-25 21:19:04 -05:00
|
|
|
}
|
2024-12-25 02:11:05 -05:00
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
// Check the cosign's signature
|
2024-12-25 01:45:37 -05:00
|
|
|
{
|
2024-12-25 23:21:25 -05:00
|
|
|
let key = Public::from({
|
2024-12-26 00:15:49 -05:00
|
|
|
let Some(key) = global_session.keys.get(&network) else {
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::NonParticipatingNetwork)?
|
2024-12-25 23:21:25 -05:00
|
|
|
};
|
|
|
|
|
*key
|
2024-12-25 21:19:04 -05:00
|
|
|
});
|
2024-12-22 06:41:55 -05:00
|
|
|
|
2024-12-25 01:45:37 -05:00
|
|
|
if !signed_cosign.verify_signature(key) {
|
2025-01-12 05:49:17 -05:00
|
|
|
Err(IntakeCosignError::InvalidSignature)?;
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
2024-12-25 01:45:37 -05:00
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
|
|
|
|
|
// Since we verified this cosign's signature, and have a chain sufficiently long, handle the
|
|
|
|
|
// cosign
|
|
|
|
|
|
2024-12-25 02:11:05 -05:00
|
|
|
let mut txn = self.db.txn();
|
|
|
|
|
|
2024-12-26 00:24:48 -05:00
|
|
|
if !faulty {
|
2024-12-26 00:15:49 -05:00
|
|
|
// If this is for a future global session, we don't acknowledge this cosign at this time
|
|
|
|
|
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);
|
2025-01-12 05:49:17 -05:00
|
|
|
return Err(IntakeCosignError::FutureGlobalSession);
|
2024-12-26 00:15:49 -05:00
|
|
|
}
|
|
|
|
|
|
2024-12-26 00:24:48 -05:00
|
|
|
// This is safe as it's in-range and newer, as prior checked since it isn't faulty
|
2024-12-25 21:19:04 -05:00
|
|
|
NetworksLatestCosignedBlock::set(&mut txn, cosign.global_session, network, signed_cosign);
|
2024-12-22 06:41:55 -05:00
|
|
|
} 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
|
2024-12-25 23:21:25 -05:00
|
|
|
if !faults.iter().any(|cosign| cosign.cosign.cosigner == network) {
|
2024-12-25 00:06:46 -05:00
|
|
|
faults.push(signed_cosign.clone());
|
2024-12-22 06:41:55 -05:00
|
|
|
Faults::set(&mut txn, cosign.global_session, &faults);
|
|
|
|
|
|
|
|
|
|
let mut weight_cosigned = 0;
|
2024-12-25 21:19:04 -05:00
|
|
|
for fault in &faults {
|
2025-01-12 05:49:17 -05:00
|
|
|
let stake = global_session
|
|
|
|
|
.stakes
|
|
|
|
|
.get(&fault.cosign.cosigner)
|
|
|
|
|
.expect("cosigner with recognized key didn't have a stake entry saved");
|
2024-12-25 21:19:04 -05:00
|
|
|
weight_cosigned += stake;
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the sum weight means a fault has occurred
|
2024-12-26 00:15:49 -05:00
|
|
|
if weight_cosigned >= ((global_session.total_stake * 17) / 100) {
|
2024-12-22 06:41:55 -05:00
|
|
|
FaultedSession::set(&mut txn, &cosign.global_session);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
txn.commit();
|
2025-01-12 05:49:17 -05:00
|
|
|
Ok(())
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|
2024-12-31 10:37:19 -05:00
|
|
|
|
2025-01-30 03:14:24 -05:00
|
|
|
/// Receive intended cosigns to produce for this ExternalValidatorSet.
|
2024-12-31 10:37:19 -05:00
|
|
|
///
|
|
|
|
|
/// All cosigns intended, up to and including the next notable cosign, are returned.
|
|
|
|
|
///
|
|
|
|
|
/// This will drain the internal channel and not re-yield these intentions again.
|
2025-01-30 03:14:24 -05:00
|
|
|
pub fn intended_cosigns(txn: &mut impl DbTxn, set: ExternalValidatorSet) -> Vec<CosignIntent> {
|
2024-12-31 10:37:19 -05:00
|
|
|
let mut res: Vec<CosignIntent> = vec![];
|
|
|
|
|
// While we have yet to find a notable cosign...
|
|
|
|
|
while !res.last().map(|cosign| cosign.notable).unwrap_or(false) {
|
|
|
|
|
let Some(intent) = intend::IntendedCosigns::try_recv(txn, set) else { break };
|
|
|
|
|
res.push(intent);
|
|
|
|
|
}
|
|
|
|
|
res
|
|
|
|
|
}
|
2024-12-22 06:41:55 -05:00
|
|
|
}
|