mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-22 18:54:40 +00:00
Add BatchSignerTask
Uses a wrapper around AlgorithmMachine Schnorrkel to let the message be &[].
This commit is contained in:
parent
4152bcacb2
commit
ed0221d804
13 changed files with 371 additions and 51 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -8724,10 +8724,13 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"borsh",
|
"borsh",
|
||||||
"ciphersuite",
|
"ciphersuite",
|
||||||
|
"frost-schnorrkel",
|
||||||
"log",
|
"log",
|
||||||
"modular-frost",
|
"modular-frost",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
|
"rand_core",
|
||||||
"serai-db",
|
"serai-db",
|
||||||
|
"serai-in-instructions-primitives",
|
||||||
"serai-processor-frost-attempt-manager",
|
"serai-processor-frost-attempt-manager",
|
||||||
"serai-processor-messages",
|
"serai-processor-messages",
|
||||||
"serai-processor-primitives",
|
"serai-processor-primitives",
|
||||||
|
|
|
@ -548,36 +548,36 @@ mod _public_db {
|
||||||
|
|
||||||
db_channel! {
|
db_channel! {
|
||||||
ScannerPublic {
|
ScannerPublic {
|
||||||
BatchToSign: (key: &[u8]) -> Batch,
|
BatchesToSign: (key: &[u8]) -> Batch,
|
||||||
AcknowledgedBatch: (key: &[u8]) -> u32,
|
AcknowledgedBatches: (key: &[u8]) -> u32,
|
||||||
CompletedEventualities: (key: &[u8]) -> [u8; 32],
|
CompletedEventualities: (key: &[u8]) -> [u8; 32],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The batches to sign and publish.
|
/// The batches to sign and publish.
|
||||||
pub struct BatchToSign<K: GroupEncoding>(PhantomData<K>);
|
pub struct BatchesToSign<K: GroupEncoding>(PhantomData<K>);
|
||||||
impl<K: GroupEncoding> BatchToSign<K> {
|
impl<K: GroupEncoding> BatchesToSign<K> {
|
||||||
pub(crate) fn send(txn: &mut impl DbTxn, key: &K, batch: &Batch) {
|
pub(crate) fn send(txn: &mut impl DbTxn, key: &K, batch: &Batch) {
|
||||||
_public_db::BatchToSign::send(txn, key.to_bytes().as_ref(), batch);
|
_public_db::BatchesToSign::send(txn, key.to_bytes().as_ref(), batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive a batch to sign and publish.
|
/// Receive a batch to sign and publish.
|
||||||
pub fn try_recv(txn: &mut impl DbTxn, key: &K) -> Option<Batch> {
|
pub fn try_recv(txn: &mut impl DbTxn, key: &K) -> Option<Batch> {
|
||||||
_public_db::BatchToSign::try_recv(txn, key.to_bytes().as_ref())
|
_public_db::BatchesToSign::try_recv(txn, key.to_bytes().as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The batches which were acknowledged on-chain.
|
/// The batches which were acknowledged on-chain.
|
||||||
pub struct AcknowledgedBatch<K: GroupEncoding>(PhantomData<K>);
|
pub struct AcknowledgedBatches<K: GroupEncoding>(PhantomData<K>);
|
||||||
impl<K: GroupEncoding> AcknowledgedBatch<K> {
|
impl<K: GroupEncoding> AcknowledgedBatches<K> {
|
||||||
pub(crate) fn send(txn: &mut impl DbTxn, key: &K, batch: u32) {
|
pub(crate) fn send(txn: &mut impl DbTxn, key: &K, batch: u32) {
|
||||||
_public_db::AcknowledgedBatch::send(txn, key.to_bytes().as_ref(), &batch);
|
_public_db::AcknowledgedBatches::send(txn, key.to_bytes().as_ref(), &batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive the ID of a batch which was acknowledged.
|
/// Receive the ID of a batch which was acknowledged.
|
||||||
pub fn try_recv(txn: &mut impl DbTxn, key: &K) -> Option<u32> {
|
pub fn try_recv(txn: &mut impl DbTxn, key: &K) -> Option<u32> {
|
||||||
_public_db::AcknowledgedBatch::try_recv(txn, key.to_bytes().as_ref())
|
_public_db::AcknowledgedBatches::try_recv(txn, key.to_bytes().as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub use lifetime::LifetimeStage;
|
||||||
// Database schema definition and associated functions.
|
// Database schema definition and associated functions.
|
||||||
mod db;
|
mod db;
|
||||||
use db::ScannerGlobalDb;
|
use db::ScannerGlobalDb;
|
||||||
pub use db::{BatchToSign, AcknowledgedBatch, CompletedEventualities};
|
pub use db::{BatchesToSign, AcknowledgedBatches, CompletedEventualities};
|
||||||
// Task to index the blockchain, ensuring we don't reorganize finalized blocks.
|
// Task to index the blockchain, ensuring we don't reorganize finalized blocks.
|
||||||
mod index;
|
mod index;
|
||||||
// Scans blocks for received coins.
|
// Scans blocks for received coins.
|
||||||
|
|
|
@ -8,7 +8,7 @@ use serai_in_instructions_primitives::{MAX_BATCH_SIZE, Batch};
|
||||||
|
|
||||||
use primitives::task::ContinuallyRan;
|
use primitives::task::ContinuallyRan;
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{Returnable, ScannerGlobalDb, InInstructionData, ScanToReportDb, BatchToSign},
|
db::{Returnable, ScannerGlobalDb, InInstructionData, ScanToReportDb, BatchesToSign},
|
||||||
index,
|
index,
|
||||||
scan::next_to_scan_for_outputs_block,
|
scan::next_to_scan_for_outputs_block,
|
||||||
ScannerFeed, KeyFor,
|
ScannerFeed, KeyFor,
|
||||||
|
@ -160,7 +160,7 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for ReportTask<D, S> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for batch in batches {
|
for batch in batches {
|
||||||
BatchToSign::send(&mut txn, &external_key_for_session_to_sign_batch, &batch);
|
BatchesToSign::send(&mut txn, &external_key_for_session_to_sign_batch, &batch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serai_coins_primitives::{OutInstruction, OutInstructionWithBalance};
|
||||||
|
|
||||||
use primitives::task::ContinuallyRan;
|
use primitives::task::ContinuallyRan;
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{ScannerGlobalDb, SubstrateToEventualityDb, AcknowledgedBatch},
|
db::{ScannerGlobalDb, SubstrateToEventualityDb, AcknowledgedBatches},
|
||||||
report, ScannerFeed, KeyFor,
|
report, ScannerFeed, KeyFor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SubstrateTask<D, S> {
|
||||||
{
|
{
|
||||||
let external_key_for_session_to_sign_batch =
|
let external_key_for_session_to_sign_batch =
|
||||||
report::take_external_key_for_session_to_sign_batch::<S>(&mut txn, batch_id).unwrap();
|
report::take_external_key_for_session_to_sign_batch::<S>(&mut txn, batch_id).unwrap();
|
||||||
AcknowledgedBatch::send(&mut txn, &external_key_for_session_to_sign_batch, batch_id);
|
AcknowledgedBatches::send(&mut txn, &external_key_for_session_to_sign_batch, batch_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark we made progress and handle this
|
// Mark we made progress and handle this
|
||||||
|
|
|
@ -21,15 +21,18 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = { version = "0.1", default-features = false }
|
async-trait = { version = "0.1", default-features = false }
|
||||||
|
rand_core = { version = "0.6", default-features = false }
|
||||||
zeroize = { version = "1", default-features = false, features = ["std"] }
|
zeroize = { version = "1", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||||
|
frost-schnorrkel = { path = "../../crypto/schnorrkel", default-features = false }
|
||||||
|
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||||
|
|
||||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||||
|
serai-in-instructions-primitives = { path = "../../substrate/in-instructions/primitives", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
serai-db = { path = "../../common/db" }
|
serai-db = { path = "../../common/db" }
|
||||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||||
|
|
13
processor/signers/src/batch/db.rs
Normal file
13
processor/signers/src/batch/db.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use serai_validator_sets_primitives::Session;
|
||||||
|
use serai_in_instructions_primitives::{Batch, SignedBatch};
|
||||||
|
|
||||||
|
use serai_db::{Get, DbTxn, create_db};
|
||||||
|
|
||||||
|
create_db! {
|
||||||
|
BatchSigner {
|
||||||
|
ActiveSigningProtocols: (session: Session) -> Vec<u32>,
|
||||||
|
Batches: (id: u32) -> Batch,
|
||||||
|
SignedBatches: (id: u32) -> SignedBatch,
|
||||||
|
LastAcknowledgedBatch: () -> u32,
|
||||||
|
}
|
||||||
|
}
|
180
processor/signers/src/batch/mod.rs
Normal file
180
processor/signers/src/batch/mod.rs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ristretto};
|
||||||
|
use frost::dkg::ThresholdKeys;
|
||||||
|
|
||||||
|
use serai_validator_sets_primitives::Session;
|
||||||
|
use serai_in_instructions_primitives::{SignedBatch, batch_message};
|
||||||
|
|
||||||
|
use serai_db::{DbTxn, Db};
|
||||||
|
|
||||||
|
use messages::sign::VariantSignId;
|
||||||
|
|
||||||
|
use primitives::task::ContinuallyRan;
|
||||||
|
use scanner::{BatchesToSign, AcknowledgedBatches};
|
||||||
|
|
||||||
|
use frost_attempt_manager::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::{CoordinatorToBatchSignerMessages, BatchSignerToCoordinatorMessages},
|
||||||
|
WrappedSchnorrkelMachine,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod db;
|
||||||
|
use db::*;
|
||||||
|
|
||||||
|
// Fetches batches to sign and signs them.
|
||||||
|
pub(crate) struct BatchSignerTask<D: Db, E: GroupEncoding> {
|
||||||
|
db: D,
|
||||||
|
|
||||||
|
session: Session,
|
||||||
|
external_key: E,
|
||||||
|
keys: Vec<ThresholdKeys<Ristretto>>,
|
||||||
|
|
||||||
|
active_signing_protocols: HashSet<u32>,
|
||||||
|
attempt_manager: AttemptManager<D, WrappedSchnorrkelMachine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Db, E: GroupEncoding> BatchSignerTask<D, E> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
db: D,
|
||||||
|
session: Session,
|
||||||
|
external_key: E,
|
||||||
|
keys: Vec<ThresholdKeys<Ristretto>>,
|
||||||
|
) -> Self {
|
||||||
|
let mut active_signing_protocols = HashSet::new();
|
||||||
|
let mut attempt_manager = AttemptManager::new(
|
||||||
|
db.clone(),
|
||||||
|
session,
|
||||||
|
keys.first().expect("creating a batch signer with 0 keys").params().i(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Re-register all active signing protocols
|
||||||
|
for id in ActiveSigningProtocols::get(&db, session).unwrap_or(vec![]) {
|
||||||
|
active_signing_protocols.insert(id);
|
||||||
|
|
||||||
|
let batch = Batches::get(&db, id).unwrap();
|
||||||
|
assert_eq!(batch.id, id);
|
||||||
|
|
||||||
|
let mut machines = Vec::with_capacity(keys.len());
|
||||||
|
for keys in &keys {
|
||||||
|
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch)));
|
||||||
|
}
|
||||||
|
attempt_manager.register(VariantSignId::Batch(id), machines);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { db, session, external_key, keys, active_signing_protocols, attempt_manager }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl<D: Db, E: Send + GroupEncoding> ContinuallyRan for BatchSignerTask<D, E> {
|
||||||
|
async fn run_iteration(&mut self) -> Result<bool, String> {
|
||||||
|
let mut iterated = false;
|
||||||
|
|
||||||
|
// Check for new batches to sign
|
||||||
|
loop {
|
||||||
|
let mut txn = self.db.txn();
|
||||||
|
let Some(batch) = BatchesToSign::try_recv(&mut txn, &self.external_key) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
iterated = true;
|
||||||
|
|
||||||
|
// Save this to the database as a transaction to sign
|
||||||
|
self.active_signing_protocols.insert(batch.id);
|
||||||
|
ActiveSigningProtocols::set(
|
||||||
|
&mut txn,
|
||||||
|
self.session,
|
||||||
|
&self.active_signing_protocols.iter().copied().collect(),
|
||||||
|
);
|
||||||
|
Batches::set(&mut txn, batch.id, &batch);
|
||||||
|
|
||||||
|
let mut machines = Vec::with_capacity(self.keys.len());
|
||||||
|
for keys in &self.keys {
|
||||||
|
machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch)));
|
||||||
|
}
|
||||||
|
for msg in self.attempt_manager.register(VariantSignId::Batch(batch.id), machines) {
|
||||||
|
BatchSignerToCoordinatorMessages::send(&mut txn, self.session, &msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for acknowledged Batches (meaning we should no longer sign for these Batches)
|
||||||
|
loop {
|
||||||
|
let mut txn = self.db.txn();
|
||||||
|
let Some(id) = AcknowledgedBatches::try_recv(&mut txn, &self.external_key) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let last_acknowledged = LastAcknowledgedBatch::get(&txn);
|
||||||
|
if Some(id) > last_acknowledged {
|
||||||
|
LastAcknowledgedBatch::set(&mut txn, &id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We may have yet to register this signing protocol.
|
||||||
|
|
||||||
|
While `BatchesToSign` is populated before `AcknowledgedBatches`, we could theoretically have
|
||||||
|
`BatchesToSign` populated with a new batch _while iterating over `AcknowledgedBatches`_, and
|
||||||
|
then have `AcknowledgedBatched` populated. In that edge case, we will see the
|
||||||
|
acknowledgement notification before we see the transaction.
|
||||||
|
|
||||||
|
In such a case, we break (dropping the txn, re-queueing the acknowledgement notification).
|
||||||
|
On the task's next iteration, we'll process the Batch from `BatchesToSign` and be
|
||||||
|
able to make progress.
|
||||||
|
*/
|
||||||
|
if !self.active_signing_protocols.remove(&id) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iterated = true;
|
||||||
|
|
||||||
|
// Since it was, remove this as an active signing protocol
|
||||||
|
ActiveSigningProtocols::set(
|
||||||
|
&mut txn,
|
||||||
|
self.session,
|
||||||
|
&self.active_signing_protocols.iter().copied().collect(),
|
||||||
|
);
|
||||||
|
// Clean up the database
|
||||||
|
Batches::del(&mut txn, id);
|
||||||
|
SignedBatches::del(&mut txn, id);
|
||||||
|
|
||||||
|
// We retire with a txn so we either successfully flag this Batch as acknowledged, and
|
||||||
|
// won't re-register it (making this retire safe), or we don't flag it, meaning we will
|
||||||
|
// re-register it, yet that's safe as we have yet to retire it
|
||||||
|
self.attempt_manager.retire(&mut txn, VariantSignId::Batch(id));
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle any messages sent to us
|
||||||
|
loop {
|
||||||
|
let mut txn = self.db.txn();
|
||||||
|
let Some(msg) = CoordinatorToBatchSignerMessages::try_recv(&mut txn, self.session) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
iterated = true;
|
||||||
|
|
||||||
|
match self.attempt_manager.handle(msg) {
|
||||||
|
Response::Messages(msgs) => {
|
||||||
|
for msg in msgs {
|
||||||
|
BatchSignerToCoordinatorMessages::send(&mut txn, self.session, &msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Response::Signature { id, signature } => {
|
||||||
|
let VariantSignId::Batch(id) = id else { panic!("BatchSignerTask signed a non-Batch") };
|
||||||
|
let batch =
|
||||||
|
Batches::get(&txn, id).expect("signed a Batch we didn't save to the database");
|
||||||
|
let signed_batch = SignedBatch { batch, signature: signature.into() };
|
||||||
|
SignedBatches::set(&mut txn, signed_batch.batch.id, &signed_batch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(iterated)
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,6 +93,8 @@ impl<D: Db, C: Coordinator> ContinuallyRan for CoordinatorTask<D, C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: For max(last acknowledged batch, last published batch) onwards, publish every batch
|
||||||
|
|
||||||
Ok(iterated)
|
Ok(iterated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
use frost::dkg::{ThresholdCore, ThresholdKeys};
|
use frost::dkg::{ThresholdCore, ThresholdKeys};
|
||||||
|
|
||||||
use serai_validator_sets_primitives::Session;
|
use serai_validator_sets_primitives::Session;
|
||||||
|
use serai_in_instructions_primitives::SignedBatch;
|
||||||
|
|
||||||
use serai_db::{DbTxn, Db};
|
use serai_db::{DbTxn, Db};
|
||||||
|
|
||||||
|
@ -19,25 +20,34 @@ use messages::sign::{VariantSignId, ProcessorMessage, CoordinatorMessage};
|
||||||
use primitives::task::{Task, TaskHandle, ContinuallyRan};
|
use primitives::task::{Task, TaskHandle, ContinuallyRan};
|
||||||
use scheduler::{Transaction, SignableTransaction, TransactionFor};
|
use scheduler::{Transaction, SignableTransaction, TransactionFor};
|
||||||
|
|
||||||
|
mod wrapped_schnorrkel;
|
||||||
|
pub(crate) use wrapped_schnorrkel::WrappedSchnorrkelMachine;
|
||||||
|
|
||||||
pub(crate) mod db;
|
pub(crate) mod db;
|
||||||
|
|
||||||
mod coordinator;
|
mod coordinator;
|
||||||
use coordinator::CoordinatorTask;
|
use coordinator::CoordinatorTask;
|
||||||
|
|
||||||
|
mod batch;
|
||||||
|
use batch::BatchSignerTask;
|
||||||
|
|
||||||
mod transaction;
|
mod transaction;
|
||||||
use transaction::TransactionTask;
|
use transaction::TransactionSignerTask;
|
||||||
|
|
||||||
/// A connection to the Coordinator which messages can be published with.
|
/// A connection to the Coordinator which messages can be published with.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Coordinator: 'static + Send + Sync {
|
pub trait Coordinator: 'static + Send + Sync {
|
||||||
/// An error encountered when sending a message.
|
/// An error encountered when interacting with a coordinator.
|
||||||
///
|
///
|
||||||
/// This MUST be an ephemeral error. Retrying sending a message MUST eventually resolve without
|
/// This MUST be an ephemeral error. Retrying an interaction MUST eventually resolve without
|
||||||
/// manual intervention/changing the arguments.
|
/// manual intervention/changing the arguments.
|
||||||
type EphemeralError: Debug;
|
type EphemeralError: Debug;
|
||||||
|
|
||||||
/// Send a `messages::sign::ProcessorMessage`.
|
/// Send a `messages::sign::ProcessorMessage`.
|
||||||
async fn send(&mut self, message: ProcessorMessage) -> Result<(), Self::EphemeralError>;
|
async fn send(&mut self, message: ProcessorMessage) -> Result<(), Self::EphemeralError>;
|
||||||
|
|
||||||
|
/// Publish a `SignedBatch`.
|
||||||
|
async fn publish_batch(&mut self, batch: SignedBatch) -> Result<(), Self::EphemeralError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An object capable of publishing a transaction.
|
/// An object capable of publishing a transaction.
|
||||||
|
@ -111,13 +121,18 @@ impl<ST: SignableTransaction> Signers<ST> {
|
||||||
<ST::Ciphersuite as Ciphersuite>::read_G(&mut external_key_bytes).unwrap();
|
<ST::Ciphersuite as Ciphersuite>::read_G(&mut external_key_bytes).unwrap();
|
||||||
assert!(external_key_bytes.is_empty());
|
assert!(external_key_bytes.is_empty());
|
||||||
|
|
||||||
|
// Drain the Batches to sign
|
||||||
|
// This will be fully populated by the scanner before retiry occurs, making this perfect
|
||||||
|
// in not leaving any pending blobs behind
|
||||||
|
while scanner::BatchesToSign::try_recv(&mut txn, &external_key).is_some() {}
|
||||||
|
// Drain the acknowledged batches to no longer sign
|
||||||
|
while scanner::AcknowledgedBatches::try_recv(&mut txn, &external_key).is_some() {}
|
||||||
|
|
||||||
// Drain the transactions to sign
|
// Drain the transactions to sign
|
||||||
// TransactionsToSign will be fully populated by the scheduler before retiry occurs, making
|
// This will be fully populated by the scheduler before retiry
|
||||||
// this perfect in not leaving any pending blobs behind
|
|
||||||
while scheduler::TransactionsToSign::<ST>::try_recv(&mut txn, &external_key).is_some() {}
|
while scheduler::TransactionsToSign::<ST>::try_recv(&mut txn, &external_key).is_some() {}
|
||||||
|
|
||||||
// Drain the completed Eventualities
|
// Drain the completed Eventualities
|
||||||
// This will be fully populated by the scanner before retiry
|
|
||||||
while scanner::CompletedEventualities::try_recv(&mut txn, &external_key).is_some() {}
|
while scanner::CompletedEventualities::try_recv(&mut txn, &external_key).is_some() {}
|
||||||
|
|
||||||
// Drain our DB channels
|
// Drain our DB channels
|
||||||
|
@ -156,18 +171,37 @@ impl<ST: SignableTransaction> Signers<ST> {
|
||||||
|
|
||||||
// TODO: Batch signer, cosigner, slash report signers
|
// TODO: Batch signer, cosigner, slash report signers
|
||||||
|
|
||||||
|
let (batch_task, batch_handle) = Task::new();
|
||||||
|
tokio::spawn(
|
||||||
|
BatchSignerTask::new(
|
||||||
|
db.clone(),
|
||||||
|
session,
|
||||||
|
external_keys[0].group_key(),
|
||||||
|
substrate_keys.clone(),
|
||||||
|
)
|
||||||
|
.continually_run(batch_task, vec![coordinator_handle.clone()]),
|
||||||
|
);
|
||||||
|
|
||||||
let (transaction_task, transaction_handle) = Task::new();
|
let (transaction_task, transaction_handle) = Task::new();
|
||||||
tokio::spawn(
|
tokio::spawn(
|
||||||
TransactionTask::<_, ST, _>::new(db.clone(), publisher.clone(), session, external_keys)
|
TransactionSignerTask::<_, ST, _>::new(
|
||||||
|
db.clone(),
|
||||||
|
publisher.clone(),
|
||||||
|
session,
|
||||||
|
external_keys,
|
||||||
|
)
|
||||||
.continually_run(transaction_task, vec![coordinator_handle.clone()]),
|
.continually_run(transaction_task, vec![coordinator_handle.clone()]),
|
||||||
);
|
);
|
||||||
|
|
||||||
tasks.insert(session, Tasks {
|
tasks.insert(
|
||||||
|
session,
|
||||||
|
Tasks {
|
||||||
cosigner: todo!("TODO"),
|
cosigner: todo!("TODO"),
|
||||||
batch: todo!("TODO"),
|
batch: batch_handle,
|
||||||
slash_report: todo!("TODO"),
|
slash_report: todo!("TODO"),
|
||||||
transaction: transaction_handle,
|
transaction: transaction_handle,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { coordinator_handle, tasks, _ST: PhantomData }
|
Self { coordinator_handle, tasks, _ST: PhantomData }
|
||||||
|
@ -246,19 +280,27 @@ impl<ST: SignableTransaction> Signers<ST> {
|
||||||
match sign_id.id {
|
match sign_id.id {
|
||||||
VariantSignId::Cosign(_) => {
|
VariantSignId::Cosign(_) => {
|
||||||
db::CoordinatorToCosignerMessages::send(txn, sign_id.session, message);
|
db::CoordinatorToCosignerMessages::send(txn, sign_id.session, message);
|
||||||
if let Some(tasks) = tasks { tasks.cosigner.run_now(); }
|
if let Some(tasks) = tasks {
|
||||||
|
tasks.cosigner.run_now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VariantSignId::Batch(_) => {
|
VariantSignId::Batch(_) => {
|
||||||
db::CoordinatorToBatchSignerMessages::send(txn, sign_id.session, message);
|
db::CoordinatorToBatchSignerMessages::send(txn, sign_id.session, message);
|
||||||
if let Some(tasks) = tasks { tasks.batch.run_now(); }
|
if let Some(tasks) = tasks {
|
||||||
|
tasks.batch.run_now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VariantSignId::SlashReport(_) => {
|
VariantSignId::SlashReport(_) => {
|
||||||
db::CoordinatorToSlashReportSignerMessages::send(txn, sign_id.session, message);
|
db::CoordinatorToSlashReportSignerMessages::send(txn, sign_id.session, message);
|
||||||
if let Some(tasks) = tasks { tasks.slash_report.run_now(); }
|
if let Some(tasks) = tasks {
|
||||||
|
tasks.slash_report.run_now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VariantSignId::Transaction(_) => {
|
VariantSignId::Transaction(_) => {
|
||||||
db::CoordinatorToTransactionSignerMessages::send(txn, sign_id.session, message);
|
db::CoordinatorToTransactionSignerMessages::send(txn, sign_id.session, message);
|
||||||
if let Some(tasks) = tasks { tasks.transaction.run_now(); }
|
if let Some(tasks) = tasks {
|
||||||
|
tasks.transaction.run_now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ mod db;
|
||||||
use db::*;
|
use db::*;
|
||||||
|
|
||||||
// Fetches transactions to sign and signs them.
|
// Fetches transactions to sign and signs them.
|
||||||
pub(crate) struct TransactionTask<
|
pub(crate) struct TransactionSignerTask<
|
||||||
D: Db,
|
D: Db,
|
||||||
ST: SignableTransaction,
|
ST: SignableTransaction,
|
||||||
P: TransactionPublisher<TransactionFor<ST>>,
|
P: TransactionPublisher<TransactionFor<ST>>,
|
||||||
|
@ -44,7 +44,7 @@ pub(crate) struct TransactionTask<
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>>
|
impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>>
|
||||||
TransactionTask<D, ST, P>
|
TransactionSignerTask<D, ST, P>
|
||||||
{
|
{
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
db: D,
|
db: D,
|
||||||
|
@ -90,7 +90,7 @@ impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>> ContinuallyRan
|
impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>> ContinuallyRan
|
||||||
for TransactionTask<D, ST, P>
|
for TransactionSignerTask<D, ST, P>
|
||||||
{
|
{
|
||||||
async fn run_iteration(&mut self) -> Result<bool, String> {
|
async fn run_iteration(&mut self) -> Result<bool, String> {
|
||||||
let mut iterated = false;
|
let mut iterated = false;
|
||||||
|
@ -193,17 +193,16 @@ impl<D: Db, ST: SignableTransaction, P: TransactionPublisher<TransactionFor<ST>>
|
||||||
&mut txn,
|
&mut txn,
|
||||||
match id {
|
match id {
|
||||||
VariantSignId::Transaction(id) => id,
|
VariantSignId::Transaction(id) => id,
|
||||||
_ => panic!("TransactionTask signed a non-transaction"),
|
_ => panic!("TransactionSignerTask signed a non-transaction"),
|
||||||
},
|
},
|
||||||
&buf,
|
&buf,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
match self.publisher.publish(signed_tx).await {
|
||||||
.publisher
|
Ok(()) => {}
|
||||||
.publish(signed_tx)
|
Err(e) => log::warn!("couldn't broadcast transaction: {e:?}"),
|
||||||
.await
|
}
|
||||||
.map_err(|e| format!("couldn't publish transaction: {e:?}"))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
86
processor/signers/src/wrapped_schnorrkel.rs
Normal file
86
processor/signers/src/wrapped_schnorrkel.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{self, Read},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
use ciphersuite::Ristretto;
|
||||||
|
use frost::{
|
||||||
|
dkg::{Participant, ThresholdKeys},
|
||||||
|
FrostError,
|
||||||
|
algorithm::Algorithm,
|
||||||
|
sign::*,
|
||||||
|
};
|
||||||
|
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>);
|
||||||
|
impl WrappedSchnorrkelMachine {
|
||||||
|
pub(crate) fn new(keys: ThresholdKeys<Ristretto>, msg: Vec<u8>) -> Self {
|
||||||
|
Self(keys, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WrappedSchnorrkelSignMachine(
|
||||||
|
<AlgorithmMachine<Ristretto, Schnorrkel> as PreprocessMachine>::SignMachine,
|
||||||
|
Vec<u8>,
|
||||||
|
);
|
||||||
|
|
||||||
|
type Signature = <AlgorithmMachine<Ristretto, Schnorrkel> as PreprocessMachine>::Signature;
|
||||||
|
impl PreprocessMachine for WrappedSchnorrkelMachine {
|
||||||
|
type Preprocess = <AlgorithmMachine<Ristretto, Schnorrkel> as PreprocessMachine>::Preprocess;
|
||||||
|
type Signature = Signature;
|
||||||
|
type SignMachine = WrappedSchnorrkelSignMachine;
|
||||||
|
|
||||||
|
fn preprocess<R: RngCore + CryptoRng>(
|
||||||
|
self,
|
||||||
|
rng: &mut R,
|
||||||
|
) -> (Self::SignMachine, Preprocess<Ristretto, <Schnorrkel as Algorithm<Ristretto>>::Addendum>)
|
||||||
|
{
|
||||||
|
let WrappedSchnorrkelMachine(keys, batch) = self;
|
||||||
|
let (machine, preprocess) =
|
||||||
|
AlgorithmMachine::new(Schnorrkel::new(b"substrate"), keys).preprocess(rng);
|
||||||
|
(WrappedSchnorrkelSignMachine(machine, batch), preprocess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignMachine<Signature> for WrappedSchnorrkelSignMachine {
|
||||||
|
type Params = <AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachine<Signature>>::Params;
|
||||||
|
type Keys = <AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachine<Signature>>::Keys;
|
||||||
|
type Preprocess =
|
||||||
|
<AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachine<Signature>>::Preprocess;
|
||||||
|
type SignatureShare =
|
||||||
|
<AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachine<Signature>>::SignatureShare;
|
||||||
|
type SignatureMachine =
|
||||||
|
<AlgorithmSignMachine<Ristretto, Schnorrkel> as SignMachine<Signature>>::SignatureMachine;
|
||||||
|
|
||||||
|
fn cache(self) -> CachedPreprocess {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_cache(
|
||||||
|
_algorithm: Schnorrkel,
|
||||||
|
_keys: ThresholdKeys<Ristretto>,
|
||||||
|
_cache: CachedPreprocess,
|
||||||
|
) -> (Self, Self::Preprocess) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_preprocess<R: Read>(&self, reader: &mut R) -> io::Result<Self::Preprocess> {
|
||||||
|
self.0.read_preprocess(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(
|
||||||
|
self,
|
||||||
|
preprocesses: HashMap<
|
||||||
|
Participant,
|
||||||
|
Preprocess<Ristretto, <Schnorrkel as Algorithm<Ristretto>>::Addendum>,
|
||||||
|
>,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> Result<(Self::SignatureMachine, SignatureShare<Ristretto>), FrostError> {
|
||||||
|
assert!(msg.is_empty());
|
||||||
|
self.0.sign(preprocesses, &self.1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum MultisigEvent<N: Network> {
|
|
||||||
// Batches to publish
|
|
||||||
Batches(Option<(<N::Curve as Ciphersuite>::G, <N::Curve as Ciphersuite>::G)>, Vec<Batch>),
|
|
||||||
// Eventuality completion found on-chain
|
|
||||||
Completed(Vec<u8>, [u8; 32], N::Eventuality),
|
|
||||||
}
|
|
Loading…
Reference in a new issue