mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Delay cosign acknowledgments
This commit is contained in:
@@ -66,7 +66,7 @@ to exhibit the same behavior), yet prevents interaction with it.
|
|||||||
|
|
||||||
If cosigns representing 17% of the non-Serai validators sets by weight are
|
If cosigns representing 17% of the non-Serai validators sets by weight are
|
||||||
detected for distinct blocks at the same position, the protocol halts. An
|
detected for distinct blocks at the same position, the protocol halts. An
|
||||||
explicit latency period of five seconds is enacted after receiving a cosign
|
explicit latency period of seventy seconds is enacted after receiving a cosign
|
||||||
commit for the detection of such an equivocation. This is largely redundant
|
commit for the detection of such an equivocation. This is largely redundant
|
||||||
given how the Serai blockchain node will presumably have halted itself by this
|
given how the Serai blockchain node will presumably have halted itself by this
|
||||||
time.
|
time.
|
||||||
@@ -114,8 +114,8 @@ asynchronous network or 11.33% of non-Serai validator sets' stake.
|
|||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
The Serai node no longer responding to RPC requests upon detecting any
|
The Serai node no longer responding to RPC requests upon detecting any
|
||||||
equivocation, the delayed acknowledgement of cosigns, and the fallback protocol
|
equivocation, and the fallback protocol where validators individually produce
|
||||||
where validators individually produce signatures, are not implemented at this
|
signatures, are not implemented at this time. The former means the detection of
|
||||||
time. The former means the detection of equivocating cosigns not redundant and
|
equivocating cosigns is not redundant and the latter makes 5.67% of non-Serai
|
||||||
the latter makes 5.67% of non-Serai validator sets' stake the DoS threshold,
|
validator sets' stake the DoS threshold, even without control of an
|
||||||
even without control of an asynchronous network.
|
asynchronous network.
|
||||||
|
|||||||
55
coordinator/cosign/src/delay.rs
Normal file
55
coordinator/cosign/src/delay.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use core::future::Future;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
use serai_db::*;
|
||||||
|
use serai_task::ContinuallyRan;
|
||||||
|
|
||||||
|
use crate::evaluator::CosignedBlocks;
|
||||||
|
|
||||||
|
/// How often callers should broadcast the cosigns flagged for rebroadcasting.
|
||||||
|
pub const BROADCAST_FREQUENCY: Duration = Duration::from_secs(60);
|
||||||
|
const SYNCHRONY_EXPECTATION: Duration = Duration::from_secs(10);
|
||||||
|
const ACKNOWLEDGEMENT_DELAY: Duration =
|
||||||
|
Duration::from_secs(BROADCAST_FREQUENCY.as_secs() + SYNCHRONY_EXPECTATION.as_secs());
|
||||||
|
|
||||||
|
create_db!(
|
||||||
|
SubstrateCosignDelay {
|
||||||
|
// The latest cosigned block number.
|
||||||
|
LatestCosignedBlockNumber: () -> u64,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A task to delay acknowledgement of cosigns.
|
||||||
|
pub(crate) struct CosignDelayTask<D: Db> {
|
||||||
|
pub(crate) db: D,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Db> ContinuallyRan for CosignDelayTask<D> {
|
||||||
|
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
||||||
|
async move {
|
||||||
|
let mut made_progress = false;
|
||||||
|
loop {
|
||||||
|
let mut txn = self.db.txn();
|
||||||
|
|
||||||
|
// Receive the next block to mark as cosigned
|
||||||
|
let Some((block_number, time_evaluated)) = CosignedBlocks::try_recv(&mut txn) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
// Calculate when we should mark it as valid
|
||||||
|
let time_valid =
|
||||||
|
SystemTime::UNIX_EPOCH + Duration::from_secs(time_evaluated) + ACKNOWLEDGEMENT_DELAY;
|
||||||
|
// Sleep until then
|
||||||
|
tokio::time::sleep(SystemTime::now().duration_since(time_valid).unwrap_or(Duration::ZERO))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Set the cosigned block
|
||||||
|
LatestCosignedBlockNumber::set(&mut txn, &block_number);
|
||||||
|
txn.commit();
|
||||||
|
|
||||||
|
made_progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(made_progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use serai_db::*;
|
use serai_db::*;
|
||||||
use serai_task::ContinuallyRan;
|
use serai_task::ContinuallyRan;
|
||||||
@@ -10,13 +11,18 @@ use crate::{
|
|||||||
|
|
||||||
create_db!(
|
create_db!(
|
||||||
SubstrateCosignEvaluator {
|
SubstrateCosignEvaluator {
|
||||||
// The latest cosigned block number.
|
|
||||||
LatestCosignedBlockNumber: () -> u64,
|
|
||||||
// The global session currently being evaluated.
|
// The global session currently being evaluated.
|
||||||
CurrentlyEvaluatedGlobalSession: () -> ([u8; 32], GlobalSession),
|
CurrentlyEvaluatedGlobalSession: () -> ([u8; 32], GlobalSession),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
db_channel!(
|
||||||
|
SubstrateCosignEvaluatorChannels {
|
||||||
|
// (cosigned block, time cosign was evaluated)
|
||||||
|
CosignedBlocks: () -> (u64, u64),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// This is a strict function which won't panic, even with a malicious Serai node, so long as:
|
// This is a strict function which won't panic, even with a malicious Serai node, so long as:
|
||||||
// - It's called incrementally
|
// - 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 we've completed indexing on within the intend task
|
||||||
@@ -72,8 +78,6 @@ pub(crate) struct CosignEvaluatorTask<D: Db, R: RequestNotableCosigns> {
|
|||||||
impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D, R> {
|
impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D, R> {
|
||||||
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
||||||
async move {
|
async move {
|
||||||
let latest_cosigned_block_number = LatestCosignedBlockNumber::get(&self.db).unwrap_or(0);
|
|
||||||
|
|
||||||
let mut known_cosign = None;
|
let mut known_cosign = None;
|
||||||
let mut made_progress = false;
|
let mut made_progress = false;
|
||||||
loop {
|
loop {
|
||||||
@@ -82,11 +86,6 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
|||||||
else {
|
else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
// 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 {
|
match has_events {
|
||||||
// Because this had notable events, we require an explicit cosign for this block by a
|
// Because this had notable events, we require an explicit cosign for this block by a
|
||||||
@@ -201,8 +200,17 @@ impl<D: Db, R: RequestNotableCosigns> ContinuallyRan for CosignEvaluatorTask<D,
|
|||||||
HasEvents::No => {}
|
HasEvents::No => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we checked we had the necessary cosigns, increment the latest cosigned block
|
// Since we checked we had the necessary cosigns, send it for delay before acknowledgement
|
||||||
LatestCosignedBlockNumber::set(&mut txn, &block_number);
|
CosignedBlocks::send(
|
||||||
|
&mut txn,
|
||||||
|
&(
|
||||||
|
block_number,
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)
|
||||||
|
.unwrap_or(Duration::ZERO)
|
||||||
|
.as_secs(),
|
||||||
|
),
|
||||||
|
);
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
made_progress = true;
|
made_progress = true;
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ use serai_task::*;
|
|||||||
mod intend;
|
mod intend;
|
||||||
/// The evaluator of the cosigns.
|
/// The evaluator of the cosigns.
|
||||||
mod evaluator;
|
mod evaluator;
|
||||||
use evaluator::LatestCosignedBlockNumber;
|
/// The task to delay acknowledgement of the cosigns.
|
||||||
|
mod delay;
|
||||||
|
pub use delay::BROADCAST_FREQUENCY;
|
||||||
|
use delay::LatestCosignedBlockNumber;
|
||||||
|
|
||||||
/// The schnorrkel context to used when signing a cosign.
|
/// The schnorrkel context to used when signing a cosign.
|
||||||
pub const COSIGN_CONTEXT: &[u8] = b"serai-cosign";
|
pub const COSIGN_CONTEXT: &[u8] = b"serai-cosign";
|
||||||
@@ -235,13 +238,18 @@ impl<D: Db> Cosigning<D> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
let (intend_task, _intend_task_handle) = Task::new();
|
let (intend_task, _intend_task_handle) = Task::new();
|
||||||
let (evaluator_task, evaluator_task_handle) = Task::new();
|
let (evaluator_task, evaluator_task_handle) = Task::new();
|
||||||
|
let (delay_task, delay_task_handle) = Task::new();
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
(intend::CosignIntendTask { db: db.clone(), serai })
|
(intend::CosignIntendTask { db: db.clone(), serai })
|
||||||
.continually_run(intend_task, vec![evaluator_task_handle]),
|
.continually_run(intend_task, vec![evaluator_task_handle]),
|
||||||
);
|
);
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
(evaluator::CosignEvaluatorTask { db: db.clone(), request })
|
(evaluator::CosignEvaluatorTask { db: db.clone(), request })
|
||||||
.continually_run(evaluator_task, tasks_to_run_upon_cosigning),
|
.continually_run(evaluator_task, vec![delay_task_handle]),
|
||||||
|
);
|
||||||
|
tokio::spawn(
|
||||||
|
(delay::CosignDelayTask { db: db.clone() })
|
||||||
|
.continually_run(delay_task, tasks_to_run_upon_cosigning),
|
||||||
);
|
);
|
||||||
Self { db }
|
Self { db }
|
||||||
}
|
}
|
||||||
@@ -269,7 +277,7 @@ impl<D: Db> Cosigning<D> {
|
|||||||
cosigns
|
cosigns
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cosigns to rebroadcast ever so often.
|
/// The cosigns to rebroadcast every `BROADCAST_FREQUENCY` seconds.
|
||||||
///
|
///
|
||||||
/// This will be the most recent cosigns, in case the initial broadcast failed, or the faulty
|
/// 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.
|
/// cosigns, in case of a fault, to induce identification of the fault by others.
|
||||||
@@ -348,6 +356,8 @@ impl<D: Db> Cosigning<D> {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
if !faulty {
|
if !faulty {
|
||||||
|
// This prevents a malicious validator set, on the same chain, from producing a cosign after
|
||||||
|
// their final block, replacing their notable cosign
|
||||||
if let Some(last_block) = GlobalSessionsLastBlock::get(&self.db, cosign.global_session) {
|
if let Some(last_block) = GlobalSessionsLastBlock::get(&self.db, cosign.global_session) {
|
||||||
if cosign.block_number > last_block {
|
if cosign.block_number > last_block {
|
||||||
// Cosign is for a block after the last block this global session should have signed
|
// Cosign is for a block after the last block this global session should have signed
|
||||||
|
|||||||
Reference in New Issue
Block a user