Correct accumulated errors in the processor

This commit is contained in:
Luke Parker
2025-01-18 12:41:57 -05:00
parent cb906242e7
commit 8222ce78d8
16 changed files with 133 additions and 98 deletions

View File

@@ -40,6 +40,7 @@ serai-db = { path = "../../common/db" }
log = { version = "0.4", default-features = false, features = ["std"] }
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] }
serai-cosign = { path = "../../coordinator/cosign" }
messages = { package = "serai-processor-messages", path = "../messages" }
primitives = { package = "serai-processor-primitives", path = "../primitives" }
scanner = { package = "serai-processor-scanner", path = "../scanner" }

View File

@@ -69,7 +69,12 @@ impl<D: Db, E: GroupEncoding> BatchSignerTask<D, E> {
let mut machines = Vec::with_capacity(keys.len());
for keys in &keys {
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch)));
// TODO: Fetch the context for this from a constant instead of re-defining it
machines.push(WrappedSchnorrkelMachine::new(
keys.clone(),
b"substrate",
batch_message(&batch),
));
}
attempt_manager.register(VariantSignId::Batch(id), machines);
}
@@ -106,7 +111,12 @@ impl<D: Db, E: Send + GroupEncoding> ContinuallyRan for BatchSignerTask<D, E> {
let mut machines = Vec::with_capacity(self.keys.len());
for keys in &self.keys {
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch)));
// TODO: Also fetch the constant here
machines.push(WrappedSchnorrkelMachine::new(
keys.clone(),
b"substrate",
batch_message(&batch),
));
}
for msg in self.attempt_manager.register(VariantSignId::Batch(batch_hash), machines) {
BatchSignerToCoordinatorMessages::send(&mut txn, self.session, &msg);

View File

@@ -1,6 +1,7 @@
use core::future::Future;
use scale::Decode;
use serai_primitives::Signature;
use serai_db::{DbTxn, Db};
use primitives::task::ContinuallyRan;
@@ -99,17 +100,11 @@ impl<D: Db, C: Coordinator> ContinuallyRan for CoordinatorTask<D, C> {
// Publish the cosigns from this session
{
let mut txn = self.db.txn();
while let Some(((block_number, block_id), signature)) =
Cosign::try_recv(&mut txn, session)
{
while let Some(signed_cosign) = Cosign::try_recv(&mut txn, session) {
iterated = true;
self
.coordinator
.publish_cosign(
block_number,
block_id,
<_>::decode(&mut signature.as_slice()).unwrap(),
)
.publish_cosign(signed_cosign)
.await
.map_err(|e| format!("couldn't publish Cosign: {e:?}"))?;
}
@@ -119,15 +114,12 @@ impl<D: Db, C: Coordinator> ContinuallyRan for CoordinatorTask<D, C> {
// If this session signed its slash report, publish its signature
{
let mut txn = self.db.txn();
if let Some(slash_report_signature) = SlashReportSignature::try_recv(&mut txn, session) {
if let Some((slash_report, signature)) = SignedSlashReport::try_recv(&mut txn, session) {
iterated = true;
self
.coordinator
.publish_slash_report_signature(
session,
<_>::decode(&mut slash_report_signature.as_slice()).unwrap(),
)
.publish_slash_report_signature(session, slash_report, Signature(signature))
.await
.map_err(|e| {
format!("couldn't send slash report signature to the coordinator: {e:?}")

View File

@@ -9,7 +9,8 @@ use serai_validator_sets_primitives::Session;
use serai_db::{DbTxn, Db};
use messages::{sign::VariantSignId, coordinator::cosign_block_msg};
use serai_cosign::{COSIGN_CONTEXT, Cosign as CosignStruct, SignedCosign};
use messages::sign::VariantSignId;
use primitives::task::{DoesNotError, ContinuallyRan};
@@ -34,7 +35,7 @@ pub(crate) struct CosignerTask<D: Db> {
session: Session,
keys: Vec<ThresholdKeys<Ristretto>>,
current_cosign: Option<(u64, [u8; 32])>,
current_cosign: Option<CosignStruct>,
attempt_manager: AttemptManager<D, WrappedSchnorrkelMachine>,
}
@@ -62,26 +63,34 @@ impl<D: Db> ContinuallyRan for CosignerTask<D> {
let mut txn = self.db.txn();
if let Some(cosign) = ToCosign::get(&txn, self.session) {
// If this wasn't already signed for...
if LatestCosigned::get(&txn, self.session) < Some(cosign.0) {
if LatestCosigned::get(&txn, self.session) < Some(cosign.block_number) {
// If this isn't the cosign we're currently working on, meaning it's fresh
if self.current_cosign != Some(cosign) {
if self.current_cosign.as_ref() != Some(&cosign) {
// Retire the current cosign
if let Some(current_cosign) = self.current_cosign {
assert!(current_cosign.0 < cosign.0);
self.attempt_manager.retire(&mut txn, VariantSignId::Cosign(current_cosign.0));
if let Some(current_cosign) = &self.current_cosign {
assert!(current_cosign.block_number < cosign.block_number);
self
.attempt_manager
.retire(&mut txn, VariantSignId::Cosign(current_cosign.block_number));
}
// Set the cosign being worked on
self.current_cosign = Some(cosign);
self.current_cosign = Some(cosign.clone());
let mut machines = Vec::with_capacity(self.keys.len());
{
let message = cosign_block_msg(cosign.0, cosign.1);
let message = cosign.signature_message();
for keys in &self.keys {
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), message.clone()));
machines.push(WrappedSchnorrkelMachine::new(
keys.clone(),
COSIGN_CONTEXT,
message.clone(),
));
}
}
for msg in self.attempt_manager.register(VariantSignId::Cosign(cosign.0), machines) {
for msg in
self.attempt_manager.register(VariantSignId::Cosign(cosign.block_number), machines)
{
CosignerToCoordinatorMessages::send(&mut txn, self.session, &msg);
}
@@ -109,12 +118,19 @@ impl<D: Db> ContinuallyRan for CosignerTask<D> {
let VariantSignId::Cosign(block_number) = id else {
panic!("CosignerTask signed a non-Cosign")
};
assert_eq!(Some(block_number), self.current_cosign.map(|cosign| cosign.0));
assert_eq!(
Some(block_number),
self.current_cosign.as_ref().map(|cosign| cosign.block_number)
);
let cosign = self.current_cosign.take().unwrap();
LatestCosigned::set(&mut txn, self.session, &cosign.0);
LatestCosigned::set(&mut txn, self.session, &cosign.block_number);
let cosign = SignedCosign {
cosign,
signature: Signature::from(signature).encode().try_into().unwrap(),
};
// Send the cosign
Cosign::send(&mut txn, self.session, &(cosign, Signature::from(signature).encode()));
Cosign::send(&mut txn, self.session, &cosign);
}
}

View File

@@ -1,7 +1,9 @@
use serai_validator_sets_primitives::{Session, Slash};
use serai_validator_sets_primitives::{Session, SlashReport as SlashReportStruct};
use serai_db::{Get, DbTxn, create_db, db_channel};
use serai_cosign::{Cosign as CosignStruct, SignedCosign};
use messages::sign::{ProcessorMessage, CoordinatorMessage};
create_db! {
@@ -11,16 +13,16 @@ create_db! {
LatestRetiredSession: () -> Session,
ToCleanup: () -> Vec<(Session, Vec<u8>)>,
ToCosign: (session: Session) -> (u64, [u8; 32]),
ToCosign: (session: Session) -> CosignStruct,
}
}
db_channel! {
SignersGlobal {
Cosign: (session: Session) -> ((u64, [u8; 32]), Vec<u8>),
Cosign: (session: Session) -> SignedCosign,
SlashReport: (session: Session) -> Vec<Slash>,
SlashReportSignature: (session: Session) -> Vec<u8>,
SlashReport: (session: Session) -> SlashReportStruct,
SignedSlashReport: (session: Session) -> (SlashReportStruct, [u8; 64]),
/*
TODO: Most of these are pointless? We drop all active signing sessions on reboot. It's

View File

@@ -11,11 +11,13 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use frost::dkg::{ThresholdCore, ThresholdKeys};
use serai_primitives::Signature;
use serai_validator_sets_primitives::{Session, Slash};
use serai_validator_sets_primitives::{Session, SlashReport};
use serai_in_instructions_primitives::SignedBatch;
use serai_db::{DbTxn, Db};
use serai_cosign::{Cosign, SignedCosign};
use messages::sign::{VariantSignId, ProcessorMessage, CoordinatorMessage};
use primitives::task::{Task, TaskHandle, ContinuallyRan};
@@ -59,9 +61,7 @@ pub trait Coordinator: 'static + Send + Sync {
/// Publish a cosign.
fn publish_cosign(
&mut self,
block_number: u64,
block_id: [u8; 32],
signature: Signature,
signed_cosign: SignedCosign,
) -> impl Send + Future<Output = Result<(), Self::EphemeralError>>;
/// Publish a `SignedBatch`.
@@ -74,6 +74,7 @@ pub trait Coordinator: 'static + Send + Sync {
fn publish_slash_report_signature(
&mut self,
session: Session,
slash_report: SlashReport,
signature: Signature,
) -> impl Send + Future<Output = Result<(), Self::EphemeralError>>;
}
@@ -408,19 +409,13 @@ impl<
/// Cosign a block.
///
/// This is a cheap call and able to be done inline from a higher-level loop.
pub fn cosign_block(
&mut self,
mut txn: impl DbTxn,
session: Session,
block_number: u64,
block: [u8; 32],
) {
pub fn cosign_block(&mut self, mut txn: impl DbTxn, session: Session, cosign: &Cosign) {
// Don't cosign blocks with already retired keys
if Some(session.0) <= db::LatestRetiredSession::get(&txn).map(|session| session.0) {
return;
}
db::ToCosign::set(&mut txn, session, &(block_number, block));
db::ToCosign::set(&mut txn, session, cosign);
txn.commit();
if let Some(tasks) = self.tasks.get(&session) {
@@ -435,7 +430,7 @@ impl<
&mut self,
mut txn: impl DbTxn,
session: Session,
slash_report: &Vec<Slash>,
slash_report: &SlashReport,
) {
// Don't sign slash reports with already retired keys
if Some(session.0) <= db::LatestRetiredSession::get(&txn).map(|session| session.0) {

View File

@@ -3,11 +3,8 @@ use core::{marker::PhantomData, future::Future};
use ciphersuite::Ristretto;
use frost::dkg::ThresholdKeys;
use scale::Encode;
use serai_primitives::Signature;
use serai_validator_sets_primitives::{
Session, ValidatorSet, SlashReport as SlashReportStruct, report_slashes_message,
};
use serai_validator_sets_primitives::Session;
use serai_db::{DbTxn, Db};
@@ -20,7 +17,7 @@ use frost_attempt_manager::*;
use crate::{
db::{
SlashReport, SlashReportSignature, CoordinatorToSlashReportSignerMessages,
SlashReport, SignedSlashReport, CoordinatorToSlashReportSignerMessages,
SlashReportSignerToCoordinatorMessages,
},
WrappedSchnorrkelMachine,
@@ -72,12 +69,14 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SlashReportSignerTask<D, S> {
let mut machines = Vec::with_capacity(self.keys.len());
{
let message = report_slashes_message(
&ValidatorSet { network: S::NETWORK, session: self.session },
&SlashReportStruct(slash_report.try_into().unwrap()),
);
let message = slash_report.report_slashes_message();
for keys in &self.keys {
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), message.clone()));
// TODO: Fetch this constant from somewhere instead of inlining it
machines.push(WrappedSchnorrkelMachine::new(
keys.clone(),
b"substrate",
message.clone(),
));
}
}
let mut txn = self.db.txn();
@@ -105,12 +104,12 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SlashReportSignerTask<D, S> {
Response::Signature { id, signature } => {
assert_eq!(id, VariantSignId::SlashReport);
// Drain the channel
SlashReport::try_recv(&mut txn, self.session).unwrap();
let slash_report = SlashReport::try_recv(&mut txn, self.session).unwrap();
// Send the signature
SlashReportSignature::send(
SignedSlashReport::send(
&mut txn,
self.session,
&Signature::from(signature).encode(),
&(slash_report, Signature::from(signature).0),
);
}
}

View File

@@ -16,10 +16,10 @@ use frost_schnorrkel::Schnorrkel;
// This wraps a Schnorrkel sign machine into one with a preset message.
#[derive(Clone)]
pub(crate) struct WrappedSchnorrkelMachine(ThresholdKeys<Ristretto>, Vec<u8>);
pub(crate) struct WrappedSchnorrkelMachine(ThresholdKeys<Ristretto>, &'static [u8], Vec<u8>);
impl WrappedSchnorrkelMachine {
pub(crate) fn new(keys: ThresholdKeys<Ristretto>, msg: Vec<u8>) -> Self {
Self(keys, msg)
pub(crate) fn new(keys: ThresholdKeys<Ristretto>, context: &'static [u8], msg: Vec<u8>) -> Self {
Self(keys, context, msg)
}
}
@@ -39,10 +39,10 @@ impl PreprocessMachine for WrappedSchnorrkelMachine {
rng: &mut R,
) -> (Self::SignMachine, Preprocess<Ristretto, <Schnorrkel as Algorithm<Ristretto>>::Addendum>)
{
let WrappedSchnorrkelMachine(keys, batch) = self;
let WrappedSchnorrkelMachine(keys, context, msg) = self;
let (machine, preprocess) =
AlgorithmMachine::new(Schnorrkel::new(b"substrate"), keys).preprocess(rng);
(WrappedSchnorrkelSignMachine(machine, batch), preprocess)
AlgorithmMachine::new(Schnorrkel::new(context), keys).preprocess(rng);
(WrappedSchnorrkelSignMachine(machine, msg), preprocess)
}
}