mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Publish SlashReport onto the Tributary
This commit is contained in:
@@ -17,7 +17,7 @@ use message_queue::{Service, client::MessageQueue};
|
|||||||
use serai_task::{Task, TaskHandle, ContinuallyRan};
|
use serai_task::{Task, TaskHandle, ContinuallyRan};
|
||||||
|
|
||||||
use serai_cosign::{SignedCosign, Cosigning};
|
use serai_cosign::{SignedCosign, Cosigning};
|
||||||
use serai_coordinator_substrate::{CanonicalEventStream, EphemeralEventStream};
|
use serai_coordinator_substrate::{CanonicalEventStream, EphemeralEventStream, SignSlashReport};
|
||||||
use serai_coordinator_tributary::Transaction;
|
use serai_coordinator_tributary::Transaction;
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
@@ -148,6 +148,8 @@ async fn main() {
|
|||||||
prune_tributary_db(to_cleanup);
|
prune_tributary_db(to_cleanup);
|
||||||
// Drain the cosign intents created for this set
|
// Drain the cosign intents created for this set
|
||||||
while !Cosigning::<Db>::intended_cosigns(&mut txn, to_cleanup).is_empty() {}
|
while !Cosigning::<Db>::intended_cosigns(&mut txn, to_cleanup).is_empty() {}
|
||||||
|
// Remove the SignSlashReport notification
|
||||||
|
SignSlashReport::try_recv(&mut txn, to_cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove retired Tributaries from ActiveTributaries
|
// Remove retired Tributaries from ActiveTributaries
|
||||||
@@ -198,7 +200,6 @@ async fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Spawn the Substrate scanners
|
// Spawn the Substrate scanners
|
||||||
// TODO: SignSlashReport
|
|
||||||
let (substrate_task_def, substrate_task) = Task::new();
|
let (substrate_task_def, substrate_task) = Task::new();
|
||||||
let (substrate_canonical_task_def, substrate_canonical_task) = Task::new();
|
let (substrate_canonical_task_def, substrate_canonical_task) = Task::new();
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use core::{future::Future, time::Duration};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
use rand_core::OsRng;
|
||||||
use blake2::{digest::typenum::U32, Digest, Blake2s};
|
use blake2::{digest::typenum::U32, Digest, Blake2s};
|
||||||
use ciphersuite::{Ciphersuite, Ristretto};
|
use ciphersuite::{Ciphersuite, Ristretto};
|
||||||
|
|
||||||
@@ -12,14 +13,14 @@ use serai_db::{DbTxn, Db as DbTrait};
|
|||||||
use scale::Encode;
|
use scale::Encode;
|
||||||
use serai_client::validator_sets::primitives::ValidatorSet;
|
use serai_client::validator_sets::primitives::ValidatorSet;
|
||||||
|
|
||||||
use tributary_sdk::{ProvidedError, Tributary};
|
use tributary_sdk::{TransactionError, ProvidedError, Tributary};
|
||||||
|
|
||||||
use serai_task::{Task, TaskHandle, ContinuallyRan};
|
use serai_task::{Task, TaskHandle, ContinuallyRan};
|
||||||
|
|
||||||
use message_queue::{Service, Metadata, client::MessageQueue};
|
use message_queue::{Service, Metadata, client::MessageQueue};
|
||||||
|
|
||||||
use serai_cosign::Cosigning;
|
use serai_cosign::Cosigning;
|
||||||
use serai_coordinator_substrate::NewSetInformation;
|
use serai_coordinator_substrate::{NewSetInformation, SignSlashReport};
|
||||||
use serai_coordinator_tributary::{Transaction, ProcessorMessages, ScanTributaryTask};
|
use serai_coordinator_tributary::{Transaction, ProcessorMessages, ScanTributaryTask};
|
||||||
use serai_coordinator_p2p::P2p;
|
use serai_coordinator_p2p::P2p;
|
||||||
|
|
||||||
@@ -27,9 +28,9 @@ use crate::Db;
|
|||||||
|
|
||||||
/// Provides Cosign/Cosigned Transactions onto the Tributary.
|
/// Provides Cosign/Cosigned Transactions onto the Tributary.
|
||||||
pub(crate) struct ProvideCosignCosignedTransactionsTask<CD: DbTrait, TD: DbTrait, P: P2p> {
|
pub(crate) struct ProvideCosignCosignedTransactionsTask<CD: DbTrait, TD: DbTrait, P: P2p> {
|
||||||
pub(crate) db: CD,
|
db: CD,
|
||||||
pub(crate) set: NewSetInformation,
|
set: NewSetInformation,
|
||||||
pub(crate) tributary: Tributary<TD, Transaction, P>,
|
tributary: Tributary<TD, Transaction, P>,
|
||||||
}
|
}
|
||||||
impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan
|
impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan
|
||||||
for ProvideCosignCosignedTransactionsTask<CD, TD, P>
|
for ProvideCosignCosignedTransactionsTask<CD, TD, P>
|
||||||
@@ -128,9 +129,9 @@ impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan
|
|||||||
|
|
||||||
/// Takes the messages from ScanTributaryTask and publishes them to the message-queue.
|
/// Takes the messages from ScanTributaryTask and publishes them to the message-queue.
|
||||||
pub(crate) struct TributaryProcessorMessagesTask<TD: DbTrait> {
|
pub(crate) struct TributaryProcessorMessagesTask<TD: DbTrait> {
|
||||||
pub(crate) tributary_db: TD,
|
tributary_db: TD,
|
||||||
pub(crate) set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
pub(crate) message_queue: Arc<MessageQueue>,
|
message_queue: Arc<MessageQueue>,
|
||||||
}
|
}
|
||||||
impl<TD: DbTrait> ContinuallyRan for TributaryProcessorMessagesTask<TD> {
|
impl<TD: DbTrait> ContinuallyRan for TributaryProcessorMessagesTask<TD> {
|
||||||
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
||||||
@@ -155,6 +156,51 @@ impl<TD: DbTrait> ContinuallyRan for TributaryProcessorMessagesTask<TD> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks for the notification to sign a slash report and does so if present.
|
||||||
|
pub(crate) struct SignSlashReportTask<CD: DbTrait, TD: DbTrait, P: P2p> {
|
||||||
|
db: CD,
|
||||||
|
tributary_db: TD,
|
||||||
|
tributary: Tributary<TD, Transaction, P>,
|
||||||
|
set: NewSetInformation,
|
||||||
|
key: Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||||
|
}
|
||||||
|
impl<CD: DbTrait, TD: DbTrait, P: P2p> ContinuallyRan for SignSlashReportTask<CD, TD, P> {
|
||||||
|
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, String>> {
|
||||||
|
async move {
|
||||||
|
let mut txn = self.db.txn();
|
||||||
|
let Some(()) = SignSlashReport::try_recv(&mut txn, self.set.set) else { return Ok(false) };
|
||||||
|
|
||||||
|
// Fetch the slash report for this Tributary
|
||||||
|
let mut tx =
|
||||||
|
serai_coordinator_tributary::slash_report_transaction(&self.tributary_db, &self.set);
|
||||||
|
tx.sign(&mut OsRng, self.tributary.genesis(), &self.key);
|
||||||
|
|
||||||
|
let res = self.tributary.add_transaction(tx.clone()).await;
|
||||||
|
match &res {
|
||||||
|
// Fresh publication, already published
|
||||||
|
Ok(true | false) => {}
|
||||||
|
Err(
|
||||||
|
TransactionError::TooLargeTransaction |
|
||||||
|
TransactionError::InvalidSigner |
|
||||||
|
TransactionError::InvalidNonce |
|
||||||
|
TransactionError::InvalidSignature |
|
||||||
|
TransactionError::InvalidContent,
|
||||||
|
) => {
|
||||||
|
panic!("created an invalid SlashReport transaction, tx: {tx:?}, err: {res:?}");
|
||||||
|
}
|
||||||
|
// We've published too many transactions recently
|
||||||
|
// Drop this txn to try to publish it again later on a future iteration
|
||||||
|
Err(TransactionError::TooManyInMempool) => return Ok(false),
|
||||||
|
// This isn't a Provided transaction so this should never be hit
|
||||||
|
Err(TransactionError::ProvidedAddedToMempool) => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Run the scan task whenever the Tributary adds a new block.
|
/// Run the scan task whenever the Tributary adds a new block.
|
||||||
async fn scan_on_new_block<CD: DbTrait, TD: DbTrait, P: P2p>(
|
async fn scan_on_new_block<CD: DbTrait, TD: DbTrait, P: P2p>(
|
||||||
db: CD,
|
db: CD,
|
||||||
@@ -183,8 +229,14 @@ async fn scan_on_new_block<CD: DbTrait, TD: DbTrait, P: P2p>(
|
|||||||
|
|
||||||
/// Spawn a Tributary.
|
/// Spawn a Tributary.
|
||||||
///
|
///
|
||||||
/// This will spawn the Tributary, the Tributary scan task, forward the messages from the scan task
|
/// This will:
|
||||||
/// to the message queue, provide Cosign/Cosigned transactions, and inform the P2P network.
|
/// - Spawn the Tributary
|
||||||
|
/// - Inform the P2P network of the Tributary
|
||||||
|
/// - Spawn the ScanTributaryTask
|
||||||
|
/// - Spawn the ProvideCosignCosignedTransactionsTask
|
||||||
|
/// - Spawn the TributaryProcessorMessagesTask
|
||||||
|
/// - Spawn the SignSlashReportTask
|
||||||
|
/// - Iterate the scan task whenever a new block occurs (not just on the standard interval)
|
||||||
pub(crate) async fn spawn_tributary<P: P2p>(
|
pub(crate) async fn spawn_tributary<P: P2p>(
|
||||||
db: Db,
|
db: Db,
|
||||||
message_queue: Arc<MessageQueue>,
|
message_queue: Arc<MessageQueue>,
|
||||||
@@ -219,8 +271,14 @@ pub(crate) async fn spawn_tributary<P: P2p>(
|
|||||||
|
|
||||||
// Spawn the Tributary
|
// Spawn the Tributary
|
||||||
let tributary_db = crate::db::tributary_db(set.set);
|
let tributary_db = crate::db::tributary_db(set.set);
|
||||||
let tributary =
|
let tributary = Tributary::new(
|
||||||
Tributary::new(tributary_db.clone(), genesis, start_time, serai_key, tributary_validators, p2p)
|
tributary_db.clone(),
|
||||||
|
genesis,
|
||||||
|
start_time,
|
||||||
|
serai_key.clone(),
|
||||||
|
tributary_validators,
|
||||||
|
p2p,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let reader = tributary.reader();
|
let reader = tributary.reader();
|
||||||
@@ -256,12 +314,25 @@ pub(crate) async fn spawn_tributary<P: P2p>(
|
|||||||
// Spawn the scan task
|
// Spawn the scan task
|
||||||
let (scan_tributary_task_def, scan_tributary_task) = Task::new();
|
let (scan_tributary_task_def, scan_tributary_task) = Task::new();
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
ScanTributaryTask::<_, _, P>::new(db.clone(), tributary_db, &set, reader)
|
ScanTributaryTask::<_, _, P>::new(db.clone(), tributary_db.clone(), &set, reader)
|
||||||
// This is the only handle for this TributaryProcessorMessagesTask, so when this task is
|
// This is the only handle for this TributaryProcessorMessagesTask, so when this task is
|
||||||
// dropped, it will be too
|
// dropped, it will be too
|
||||||
.continually_run(scan_tributary_task_def, vec![scan_tributary_messages_task]),
|
.continually_run(scan_tributary_task_def, vec![scan_tributary_messages_task]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Spawn the sign slash report task
|
||||||
|
let (sign_slash_report_task_def, sign_slash_report_task) = Task::new();
|
||||||
|
tokio::spawn(
|
||||||
|
(SignSlashReportTask {
|
||||||
|
db: db.clone(),
|
||||||
|
tributary_db,
|
||||||
|
tributary: tributary.clone(),
|
||||||
|
set: set.clone(),
|
||||||
|
key: serai_key,
|
||||||
|
})
|
||||||
|
.continually_run(sign_slash_report_task_def, vec![]),
|
||||||
|
);
|
||||||
|
|
||||||
// Whenever a new block occurs, immediately run the scan task
|
// Whenever a new block occurs, immediately run the scan task
|
||||||
// This function also preserves the ProvideCosignCosignedTransactionsTask handle until the
|
// This function also preserves the ProvideCosignCosignedTransactionsTask handle until the
|
||||||
// Tributary is retired, ensuring it isn't dropped prematurely and that the task don't run ad
|
// Tributary is retired, ensuring it isn't dropped prematurely and that the task don't run ad
|
||||||
@@ -271,6 +342,6 @@ pub(crate) async fn spawn_tributary<P: P2p>(
|
|||||||
set.set,
|
set.set,
|
||||||
tributary,
|
tributary,
|
||||||
scan_tributary_task,
|
scan_tributary_task,
|
||||||
vec![provide_cosign_cosigned_transactions_task],
|
vec![provide_cosign_cosigned_transactions_task, sign_slash_report_task],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
|
|||||||
else {
|
else {
|
||||||
panic!("AcceptedHandover event wasn't a AcceptedHandover event: {accepted_handover:?}");
|
panic!("AcceptedHandover event wasn't a AcceptedHandover event: {accepted_handover:?}");
|
||||||
};
|
};
|
||||||
crate::SignSlashReport::send(&mut txn, set);
|
crate::SignSlashReport::send(&mut txn, *set);
|
||||||
}
|
}
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ mod _public_db {
|
|||||||
|
|
||||||
// Relevant new set, from an ephemeral event stream
|
// Relevant new set, from an ephemeral event stream
|
||||||
NewSet: () -> NewSetInformation,
|
NewSet: () -> NewSetInformation,
|
||||||
// Relevant sign slash report, from an ephemeral event stream
|
// Potentially relevant sign slash report, from an ephemeral event stream
|
||||||
SignSlashReport: () -> ValidatorSet,
|
SignSlashReport: (set: ValidatorSet) -> (),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,12 +109,12 @@ impl NewSet {
|
|||||||
/// notifications for all relevant validator sets will be included.
|
/// notifications for all relevant validator sets will be included.
|
||||||
pub struct SignSlashReport;
|
pub struct SignSlashReport;
|
||||||
impl SignSlashReport {
|
impl SignSlashReport {
|
||||||
pub(crate) fn send(txn: &mut impl DbTxn, set: &ValidatorSet) {
|
pub(crate) fn send(txn: &mut impl DbTxn, set: ValidatorSet) {
|
||||||
_public_db::SignSlashReport::send(txn, set);
|
_public_db::SignSlashReport::send(txn, set, &());
|
||||||
}
|
}
|
||||||
/// Try to receive a notification to sign a slash report, returning `None` if there is none to
|
/// Try to receive a notification to sign a slash report, returning `None` if there is none to
|
||||||
/// receive.
|
/// receive.
|
||||||
pub fn try_recv(txn: &mut impl DbTxn) -> Option<ValidatorSet> {
|
pub fn try_recv(txn: &mut impl DbTxn, set: ValidatorSet) -> Option<()> {
|
||||||
_public_db::SignSlashReport::try_recv(txn)
|
_public_db::SignSlashReport::try_recv(txn, set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,8 +184,8 @@ create_db!(
|
|||||||
// The last handled tributary block's (number, hash)
|
// The last handled tributary block's (number, hash)
|
||||||
LastHandledTributaryBlock: (set: ValidatorSet) -> (u64, [u8; 32]),
|
LastHandledTributaryBlock: (set: ValidatorSet) -> (u64, [u8; 32]),
|
||||||
|
|
||||||
// The slash points a validator has accrued, with u64::MAX representing a fatal slash.
|
// The slash points a validator has accrued, with u32::MAX representing a fatal slash.
|
||||||
SlashPoints: (set: ValidatorSet, validator: SeraiAddress) -> u64,
|
SlashPoints: (set: ValidatorSet, validator: SeraiAddress) -> u32,
|
||||||
|
|
||||||
// The latest Substrate block to cosign.
|
// The latest Substrate block to cosign.
|
||||||
LatestSubstrateBlockToCosign: (set: ValidatorSet) -> [u8; 32],
|
LatestSubstrateBlockToCosign: (set: ValidatorSet) -> [u8; 32],
|
||||||
@@ -316,7 +316,7 @@ impl TributaryDb {
|
|||||||
reason: &str,
|
reason: &str,
|
||||||
) {
|
) {
|
||||||
log::warn!("{validator} fatally slashed: {reason}");
|
log::warn!("{validator} fatally slashed: {reason}");
|
||||||
SlashPoints::set(txn, set, validator, &u64::MAX);
|
SlashPoints::set(txn, set, validator, &u32::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_fatally_slashed(
|
pub(crate) fn is_fatally_slashed(
|
||||||
@@ -324,7 +324,7 @@ impl TributaryDb {
|
|||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
validator: SeraiAddress,
|
validator: SeraiAddress,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
SlashPoints::get(getter, set, validator).unwrap_or(0) == u64::MAX
|
SlashPoints::get(getter, set, validator).unwrap_or(0) == u32::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|||||||
@@ -511,3 +511,13 @@ impl<CD: Db, TD: Db, P: P2p> ContinuallyRan for ScanTributaryTask<CD, TD, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the Transaction::SlashReport to publish per the local view.
|
||||||
|
pub fn slash_report_transaction(getter: &impl Get, set: &NewSetInformation) -> Transaction {
|
||||||
|
let mut slash_points = Vec::with_capacity(set.validators.len());
|
||||||
|
for (validator, _weight) in set.validators.iter().copied() {
|
||||||
|
let validator = SeraiAddress::from(validator);
|
||||||
|
slash_points.push(SlashPoints::get(getter, set.set, validator).unwrap_or(0));
|
||||||
|
}
|
||||||
|
Transaction::SlashReport { slash_points, signed: Signed::default() }
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use rand_core::{RngCore, CryptoRng};
|
|||||||
|
|
||||||
use blake2::{digest::typenum::U32, Digest, Blake2b};
|
use blake2::{digest::typenum::U32, Digest, Blake2b};
|
||||||
use ciphersuite::{
|
use ciphersuite::{
|
||||||
group::{ff::Field, GroupEncoding},
|
group::{ff::Field, Group, GroupEncoding},
|
||||||
Ciphersuite, Ristretto,
|
Ciphersuite, Ristretto,
|
||||||
};
|
};
|
||||||
use schnorr::SchnorrSignature;
|
use schnorr::SchnorrSignature;
|
||||||
@@ -80,6 +80,18 @@ impl Signed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Signed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
signer: <Ristretto as Ciphersuite>::G::identity(),
|
||||||
|
signature: SchnorrSignature {
|
||||||
|
R: <Ristretto as Ciphersuite>::G::identity(),
|
||||||
|
s: <Ristretto as Ciphersuite>::F::ZERO,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The Tributary transaction definition used by Serai
|
/// The Tributary transaction definition used by Serai
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Transaction {
|
pub enum Transaction {
|
||||||
|
|||||||
Reference in New Issue
Block a user