mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Further flesh out tributary scanning
Renames `label` to `round` since `Label` was renamed to `SigningProtocolRound`. Adds some more context-less validation to transactions which used to be done within the custom decode function which was simplified via the usage of borsh. Documents in processor-messages where the Coordinator sends each of its messages.
This commit is contained in:
@@ -39,7 +39,7 @@ serai-db = { path = "../common/db" }
|
|||||||
serai-env = { path = "../common/env" }
|
serai-env = { path = "../common/env" }
|
||||||
serai-task = { path = "../common/task", version = "0.1" }
|
serai-task = { path = "../common/task", version = "0.1" }
|
||||||
|
|
||||||
processor-messages = { package = "serai-processor-messages", path = "../processor/messages" }
|
messages = { package = "serai-processor-messages", path = "../processor/messages" }
|
||||||
message-queue = { package = "serai-message-queue", path = "../message-queue" }
|
message-queue = { package = "serai-message-queue", path = "../message-queue" }
|
||||||
tributary = { package = "tributary-chain", path = "./tributary" }
|
tributary = { package = "tributary-chain", path = "./tributary" }
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
|||||||
|
|
||||||
use serai_client::{primitives::SeraiAddress, validator_sets::primitives::ValidatorSet};
|
use serai_client::{primitives::SeraiAddress, validator_sets::primitives::ValidatorSet};
|
||||||
|
|
||||||
use processor_messages::sign::VariantSignId;
|
use messages::sign::{VariantSignId, SignId};
|
||||||
|
|
||||||
use serai_db::*;
|
use serai_db::*;
|
||||||
|
|
||||||
@@ -13,20 +13,20 @@ use crate::tributary::transaction::SigningProtocolRound;
|
|||||||
|
|
||||||
/// A topic within the database which the group participates in
|
/// A topic within the database which the group participates in
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Topic {
|
pub(crate) enum Topic {
|
||||||
/// Vote to remove a participant
|
/// Vote to remove a participant
|
||||||
RemoveParticipant { participant: SeraiAddress },
|
RemoveParticipant { participant: SeraiAddress },
|
||||||
|
|
||||||
// DkgParticipation isn't represented here as participations are immediately sent to the
|
// DkgParticipation isn't represented here as participations are immediately sent to the
|
||||||
// processor, not accumulated within this databse
|
// processor, not accumulated within this databse
|
||||||
/// Participation in the signing protocol to confirm the DKG results on Substrate
|
/// Participation in the signing protocol to confirm the DKG results on Substrate
|
||||||
DkgConfirmation { attempt: u32, label: SigningProtocolRound },
|
DkgConfirmation { attempt: u32, round: SigningProtocolRound },
|
||||||
|
|
||||||
/// The local view of the SlashReport, to be aggregated into the final SlashReport
|
/// The local view of the SlashReport, to be aggregated into the final SlashReport
|
||||||
SlashReport,
|
SlashReport,
|
||||||
|
|
||||||
/// Participation in a signing protocol
|
/// Participation in a signing protocol
|
||||||
Sign { id: VariantSignId, attempt: u32, label: SigningProtocolRound },
|
Sign { id: VariantSignId, attempt: u32, round: SigningProtocolRound },
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Participating {
|
enum Participating {
|
||||||
@@ -40,13 +40,13 @@ impl Topic {
|
|||||||
#[allow(clippy::match_same_arms)]
|
#[allow(clippy::match_same_arms)]
|
||||||
match self {
|
match self {
|
||||||
Topic::RemoveParticipant { .. } => None,
|
Topic::RemoveParticipant { .. } => None,
|
||||||
Topic::DkgConfirmation { attempt, label: _ } => Some(Topic::DkgConfirmation {
|
Topic::DkgConfirmation { attempt, round: _ } => Some(Topic::DkgConfirmation {
|
||||||
attempt: attempt + 1,
|
attempt: attempt + 1,
|
||||||
label: SigningProtocolRound::Preprocess,
|
round: SigningProtocolRound::Preprocess,
|
||||||
}),
|
}),
|
||||||
Topic::SlashReport { .. } => None,
|
Topic::SlashReport { .. } => None,
|
||||||
Topic::Sign { id, attempt, label: _ } => {
|
Topic::Sign { id, attempt, round: _ } => {
|
||||||
Some(Topic::Sign { id, attempt: attempt + 1, label: SigningProtocolRound::Preprocess })
|
Some(Topic::Sign { id, attempt: attempt + 1, round: SigningProtocolRound::Preprocess })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,27 +56,40 @@ impl Topic {
|
|||||||
#[allow(clippy::match_same_arms)]
|
#[allow(clippy::match_same_arms)]
|
||||||
match self {
|
match self {
|
||||||
Topic::RemoveParticipant { .. } => None,
|
Topic::RemoveParticipant { .. } => None,
|
||||||
Topic::DkgConfirmation { attempt, label } => match label {
|
Topic::DkgConfirmation { attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => {
|
SigningProtocolRound::Preprocess => {
|
||||||
let attempt = attempt + 1;
|
let attempt = attempt + 1;
|
||||||
Some((
|
Some((
|
||||||
attempt,
|
attempt,
|
||||||
Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Preprocess },
|
Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Preprocess },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
SigningProtocolRound::Share => None,
|
SigningProtocolRound::Share => None,
|
||||||
},
|
},
|
||||||
Topic::SlashReport { .. } => None,
|
Topic::SlashReport { .. } => None,
|
||||||
Topic::Sign { id, attempt, label } => match label {
|
Topic::Sign { id, attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => {
|
SigningProtocolRound::Preprocess => {
|
||||||
let attempt = attempt + 1;
|
let attempt = attempt + 1;
|
||||||
Some((attempt, Topic::Sign { id, attempt, label: SigningProtocolRound::Preprocess }))
|
Some((attempt, Topic::Sign { id, attempt, round: SigningProtocolRound::Preprocess }))
|
||||||
}
|
}
|
||||||
SigningProtocolRound::Share => None,
|
SigningProtocolRound::Share => None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The SignId for this topic
|
||||||
|
//
|
||||||
|
// Returns None if Topic isn't Topic::Sign
|
||||||
|
pub(crate) fn sign_id(self, set: ValidatorSet) -> Option<messages::sign::SignId> {
|
||||||
|
#[allow(clippy::match_same_arms)]
|
||||||
|
match self {
|
||||||
|
Topic::RemoveParticipant { .. } => None,
|
||||||
|
Topic::DkgConfirmation { .. } => None,
|
||||||
|
Topic::SlashReport { .. } => None,
|
||||||
|
Topic::Sign { id, attempt, round: _ } => Some(SignId { session: set.session, id, attempt }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The topic which precedes this topic as a prerequisite
|
/// The topic which precedes this topic as a prerequisite
|
||||||
///
|
///
|
||||||
/// The preceding topic must define this topic as succeeding
|
/// The preceding topic must define this topic as succeeding
|
||||||
@@ -84,17 +97,17 @@ impl Topic {
|
|||||||
#[allow(clippy::match_same_arms)]
|
#[allow(clippy::match_same_arms)]
|
||||||
match self {
|
match self {
|
||||||
Topic::RemoveParticipant { .. } => None,
|
Topic::RemoveParticipant { .. } => None,
|
||||||
Topic::DkgConfirmation { attempt, label } => match label {
|
Topic::DkgConfirmation { attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => None,
|
SigningProtocolRound::Preprocess => None,
|
||||||
SigningProtocolRound::Share => {
|
SigningProtocolRound::Share => {
|
||||||
Some(Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Preprocess })
|
Some(Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Preprocess })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Topic::SlashReport { .. } => None,
|
Topic::SlashReport { .. } => None,
|
||||||
Topic::Sign { id, attempt, label } => match label {
|
Topic::Sign { id, attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => None,
|
SigningProtocolRound::Preprocess => None,
|
||||||
SigningProtocolRound::Share => {
|
SigningProtocolRound::Share => {
|
||||||
Some(Topic::Sign { id, attempt, label: SigningProtocolRound::Preprocess })
|
Some(Topic::Sign { id, attempt, round: SigningProtocolRound::Preprocess })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -107,16 +120,16 @@ impl Topic {
|
|||||||
#[allow(clippy::match_same_arms)]
|
#[allow(clippy::match_same_arms)]
|
||||||
match self {
|
match self {
|
||||||
Topic::RemoveParticipant { .. } => None,
|
Topic::RemoveParticipant { .. } => None,
|
||||||
Topic::DkgConfirmation { attempt, label } => match label {
|
Topic::DkgConfirmation { attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => {
|
SigningProtocolRound::Preprocess => {
|
||||||
Some(Topic::DkgConfirmation { attempt, label: SigningProtocolRound::Share })
|
Some(Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Share })
|
||||||
}
|
}
|
||||||
SigningProtocolRound::Share => None,
|
SigningProtocolRound::Share => None,
|
||||||
},
|
},
|
||||||
Topic::SlashReport { .. } => None,
|
Topic::SlashReport { .. } => None,
|
||||||
Topic::Sign { id, attempt, label } => match label {
|
Topic::Sign { id, attempt, round } => match round {
|
||||||
SigningProtocolRound::Preprocess => {
|
SigningProtocolRound::Preprocess => {
|
||||||
Some(Topic::Sign { id, attempt, label: SigningProtocolRound::Share })
|
Some(Topic::Sign { id, attempt, round: SigningProtocolRound::Share })
|
||||||
}
|
}
|
||||||
SigningProtocolRound::Share => None,
|
SigningProtocolRound::Share => None,
|
||||||
},
|
},
|
||||||
@@ -155,7 +168,7 @@ impl Topic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The resulting data set from an accumulation
|
/// The resulting data set from an accumulation
|
||||||
pub enum DataSet<D: Borshy> {
|
pub(crate) enum DataSet<D: Borshy> {
|
||||||
/// Accumulating this did not produce a data set to act on
|
/// Accumulating this did not produce a data set to act on
|
||||||
/// (non-existent, not ready, prior handled, not participating, etc.)
|
/// (non-existent, not ready, prior handled, not participating, etc.)
|
||||||
None,
|
None,
|
||||||
@@ -187,15 +200,21 @@ create_db!(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
pub struct TributaryDb;
|
db_channel!(
|
||||||
|
CoordinatorTributary {
|
||||||
|
ProcessorMessages: (set: ValidatorSet) -> messages::CoordinatorMessage,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) struct TributaryDb;
|
||||||
impl TributaryDb {
|
impl TributaryDb {
|
||||||
pub fn last_handled_tributary_block(
|
pub(crate) fn last_handled_tributary_block(
|
||||||
getter: &impl Get,
|
getter: &impl Get,
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
) -> Option<(u64, [u8; 32])> {
|
) -> Option<(u64, [u8; 32])> {
|
||||||
LastHandledTributaryBlock::get(getter, set)
|
LastHandledTributaryBlock::get(getter, set)
|
||||||
}
|
}
|
||||||
pub fn set_last_handled_tributary_block(
|
pub(crate) fn set_last_handled_tributary_block(
|
||||||
txn: &mut impl DbTxn,
|
txn: &mut impl DbTxn,
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
block_number: u64,
|
block_number: u64,
|
||||||
@@ -204,18 +223,35 @@ impl TributaryDb {
|
|||||||
LastHandledTributaryBlock::set(txn, set, &(block_number, block_hash));
|
LastHandledTributaryBlock::set(txn, set, &(block_number, block_hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recognize_topic(txn: &mut impl DbTxn, set: ValidatorSet, topic: Topic) {
|
pub(crate) fn latest_substrate_block_to_cosign(
|
||||||
|
getter: &impl Get,
|
||||||
|
set: ValidatorSet,
|
||||||
|
) -> Option<[u8; 32]> {
|
||||||
|
LatestSubstrateBlockToCosign::get(getter, set)
|
||||||
|
}
|
||||||
|
pub(crate) fn set_latest_substrate_block_to_cosign(
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
set: ValidatorSet,
|
||||||
|
substrate_block_hash: [u8; 32],
|
||||||
|
) {
|
||||||
|
LatestSubstrateBlockToCosign::set(txn, set, &substrate_block_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn recognize_topic(txn: &mut impl DbTxn, set: ValidatorSet, topic: Topic) {
|
||||||
AccumulatedWeight::set(txn, set, topic, &0);
|
AccumulatedWeight::set(txn, set, topic, &0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_of_block(txn: &mut impl DbTxn, set: ValidatorSet, block_number: u64) {
|
pub(crate) fn start_of_block(txn: &mut impl DbTxn, set: ValidatorSet, block_number: u64) {
|
||||||
for topic in Reattempt::take(txn, set, block_number).unwrap_or(vec![]) {
|
for topic in Reattempt::take(txn, set, block_number).unwrap_or(vec![]) {
|
||||||
// TODO: Slash all people who preprocessed but didn't share
|
// TODO: Slash all people who preprocessed but didn't share
|
||||||
Self::recognize_topic(txn, set, topic);
|
Self::recognize_topic(txn, set, topic);
|
||||||
|
if let Some(id) = topic.sign_id(set) {
|
||||||
|
Self::send_message(txn, set, messages::sign::CoordinatorMessage::Reattempt { id });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fatal_slash(
|
pub(crate) fn fatal_slash(
|
||||||
txn: &mut impl DbTxn,
|
txn: &mut impl DbTxn,
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
validator: SeraiAddress,
|
validator: SeraiAddress,
|
||||||
@@ -225,12 +261,16 @@ impl TributaryDb {
|
|||||||
SlashPoints::set(txn, set, validator, &u64::MAX);
|
SlashPoints::set(txn, set, validator, &u64::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_fatally_slashed(getter: &impl Get, set: ValidatorSet, validator: SeraiAddress) -> bool {
|
pub(crate) fn is_fatally_slashed(
|
||||||
|
getter: &impl Get,
|
||||||
|
set: ValidatorSet,
|
||||||
|
validator: SeraiAddress,
|
||||||
|
) -> bool {
|
||||||
SlashPoints::get(getter, set, validator).unwrap_or(0) == u64::MAX
|
SlashPoints::get(getter, set, validator).unwrap_or(0) == u64::MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn accumulate<D: Borshy>(
|
pub(crate) fn accumulate<D: Borshy>(
|
||||||
txn: &mut impl DbTxn,
|
txn: &mut impl DbTxn,
|
||||||
set: ValidatorSet,
|
set: ValidatorSet,
|
||||||
validators: &[SeraiAddress],
|
validators: &[SeraiAddress],
|
||||||
@@ -286,7 +326,8 @@ impl TributaryDb {
|
|||||||
// Check if we now cross the weight threshold
|
// Check if we now cross the weight threshold
|
||||||
if accumulated_weight >= topic.required_participation(total_weight) {
|
if accumulated_weight >= topic.required_participation(total_weight) {
|
||||||
// Queue this for re-attempt after enough time passes
|
// Queue this for re-attempt after enough time passes
|
||||||
if let Some((attempt, reattempt_topic)) = topic.reattempt_topic() {
|
let reattempt_topic = topic.reattempt_topic();
|
||||||
|
if let Some((attempt, reattempt_topic)) = reattempt_topic {
|
||||||
// 5 minutes
|
// 5 minutes
|
||||||
#[cfg(not(feature = "longer-reattempts"))]
|
#[cfg(not(feature = "longer-reattempts"))]
|
||||||
const BASE_REATTEMPT_DELAY: u32 =
|
const BASE_REATTEMPT_DELAY: u32 =
|
||||||
@@ -316,15 +357,11 @@ impl TributaryDb {
|
|||||||
let mut data_set = HashMap::with_capacity(validators.len());
|
let mut data_set = HashMap::with_capacity(validators.len());
|
||||||
for validator in validators {
|
for validator in validators {
|
||||||
if let Some(data) = Accumulated::<D>::get(txn, set, topic, *validator) {
|
if let Some(data) = Accumulated::<D>::get(txn, set, topic, *validator) {
|
||||||
// Clean this data up if there's not a succeeding topic
|
// Clean this data up if there's not a re-attempt topic
|
||||||
// If there is, we wait as the succeeding topic checks our participation in this topic
|
// If there is a re-attempt topic, we clean it up upon re-attempt
|
||||||
if succeeding_topic.is_none() {
|
if reattempt_topic.is_none() {
|
||||||
Accumulated::<D>::del(txn, set, topic, *validator);
|
Accumulated::<D>::del(txn, set, topic, *validator);
|
||||||
}
|
}
|
||||||
// If this *was* the succeeding topic, clean up the preceding topic's data
|
|
||||||
if let Some(preceding_topic) = preceding_topic {
|
|
||||||
Accumulated::<D>::del(txn, set, preceding_topic, *validator);
|
|
||||||
}
|
|
||||||
data_set.insert(*validator, data);
|
data_set.insert(*validator, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,4 +380,12 @@ impl TributaryDb {
|
|||||||
DataSet::None
|
DataSet::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send_message(
|
||||||
|
txn: &mut impl DbTxn,
|
||||||
|
set: ValidatorSet,
|
||||||
|
message: impl Into<messages::CoordinatorMessage>,
|
||||||
|
) {
|
||||||
|
ProcessorMessages::send(txn, set, &message.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use ciphersuite::group::GroupEncoding;
|
use ciphersuite::group::GroupEncoding;
|
||||||
|
|
||||||
use serai_client::{primitives::SeraiAddress, validator_sets::primitives::ValidatorSet};
|
use serai_client::{
|
||||||
|
primitives::SeraiAddress,
|
||||||
|
validator_sets::primitives::{ValidatorSet, Slash},
|
||||||
|
};
|
||||||
|
|
||||||
use tributary::{
|
use tributary::{
|
||||||
Signed as TributarySigned, TransactionError, TransactionKind, TransactionTrait,
|
Signed as TributarySigned, TransactionKind, TransactionTrait,
|
||||||
Transaction as TributaryTransaction, Block, TributaryReader,
|
Transaction as TributaryTransaction, Block, TributaryReader,
|
||||||
tendermint::{
|
tendermint::{
|
||||||
tx::{TendermintTx, Evidence, decode_signed_message},
|
tx::{TendermintTx, Evidence, decode_signed_message},
|
||||||
@@ -17,9 +20,11 @@ use tributary::{
|
|||||||
use serai_db::*;
|
use serai_db::*;
|
||||||
use serai_task::ContinuallyRan;
|
use serai_task::ContinuallyRan;
|
||||||
|
|
||||||
|
use messages::sign::VariantSignId;
|
||||||
|
|
||||||
use crate::tributary::{
|
use crate::tributary::{
|
||||||
db::*,
|
db::*,
|
||||||
transaction::{Signed, Transaction},
|
transaction::{SigningProtocolRound, Signed, Transaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScanBlock<'a, D: DbTxn, TD: Db> {
|
struct ScanBlock<'a, D: DbTxn, TD: Db> {
|
||||||
@@ -43,9 +48,21 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match tx {
|
match tx {
|
||||||
|
// Accumulate this vote and fatally slash the participant if past the threshold
|
||||||
Transaction::RemoveParticipant { participant, signed } => {
|
Transaction::RemoveParticipant { participant, signed } => {
|
||||||
// Accumulate this vote and fatally slash the participant if past the threshold
|
|
||||||
let signer = signer(signed);
|
let signer = signer(signed);
|
||||||
|
|
||||||
|
// Check the participant voted to be removed actually exists
|
||||||
|
if !self.validators.iter().any(|validator| *validator == participant) {
|
||||||
|
TributaryDb::fatal_slash(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
signer,
|
||||||
|
"voted to remove non-existent participant",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match TributaryDb::accumulate(
|
match TributaryDb::accumulate(
|
||||||
self.txn,
|
self.txn,
|
||||||
self.set,
|
self.set,
|
||||||
@@ -59,14 +76,22 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> {
|
|||||||
) {
|
) {
|
||||||
DataSet::None => {}
|
DataSet::None => {}
|
||||||
DataSet::Participating(_) => {
|
DataSet::Participating(_) => {
|
||||||
TributaryDb::fatal_slash(self.txn, self.set, participant, "voted to remove")
|
TributaryDb::fatal_slash(self.txn, self.set, participant, "voted to remove");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the participation to the processor
|
||||||
Transaction::DkgParticipation { participation, signed } => {
|
Transaction::DkgParticipation { participation, signed } => {
|
||||||
// Send the participation to the processor
|
TributaryDb::send_message(
|
||||||
todo!("TODO")
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
messages::key_gen::CoordinatorMessage::Participation {
|
||||||
|
session: self.set.session,
|
||||||
|
participant: todo!("TODO"),
|
||||||
|
participation,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Transaction::DkgConfirmationPreprocess { attempt, preprocess, signed } => {
|
Transaction::DkgConfirmationPreprocess { attempt, preprocess, signed } => {
|
||||||
// Accumulate the preprocesses into our own FROST attempt manager
|
// Accumulate the preprocesses into our own FROST attempt manager
|
||||||
@@ -79,11 +104,42 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> {
|
|||||||
|
|
||||||
Transaction::Cosign { substrate_block_hash } => {
|
Transaction::Cosign { substrate_block_hash } => {
|
||||||
// Update the latest intended-to-be-cosigned Substrate block
|
// Update the latest intended-to-be-cosigned Substrate block
|
||||||
todo!("TODO")
|
TributaryDb::set_latest_substrate_block_to_cosign(self.txn, self.set, substrate_block_hash);
|
||||||
|
|
||||||
|
// TODO: If we aren't currently cosigning a block, start cosigning this one
|
||||||
}
|
}
|
||||||
Transaction::Cosigned { substrate_block_hash } => {
|
Transaction::Cosigned { substrate_block_hash } => {
|
||||||
// Start cosigning the latest intended-to-be-cosigned block
|
// Start cosigning the latest intended-to-be-cosigned block
|
||||||
todo!("TODO")
|
let Some(latest_substrate_block_to_cosign) =
|
||||||
|
TributaryDb::latest_substrate_block_to_cosign(self.txn, self.set)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// If this is the block we just cosigned, return
|
||||||
|
if latest_substrate_block_to_cosign == substrate_block_hash {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let substrate_block_number = todo!("TODO");
|
||||||
|
// Whitelist the topic
|
||||||
|
TributaryDb::recognize_topic(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
Topic::Sign {
|
||||||
|
id: VariantSignId::Cosign(substrate_block_number),
|
||||||
|
attempt: 0,
|
||||||
|
round: SigningProtocolRound::Preprocess,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Send the message for the processor to start signing
|
||||||
|
TributaryDb::send_message(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
messages::coordinator::CoordinatorMessage::CosignSubstrateBlock {
|
||||||
|
session: self.set.session,
|
||||||
|
block_number: substrate_block_number,
|
||||||
|
block: substrate_block_hash,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Transaction::SubstrateBlock { hash } => {
|
Transaction::SubstrateBlock { hash } => {
|
||||||
// Whitelist all of the IDs this Substrate block causes to be signed
|
// Whitelist all of the IDs this Substrate block causes to be signed
|
||||||
@@ -95,11 +151,148 @@ impl<'a, D: DbTxn, TD: Db> ScanBlock<'a, D, TD> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Transaction::SlashReport { slash_points, signed } => {
|
Transaction::SlashReport { slash_points, signed } => {
|
||||||
|
let signer = signer(signed);
|
||||||
|
|
||||||
|
if slash_points.len() != self.validators.len() {
|
||||||
|
TributaryDb::fatal_slash(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
signer,
|
||||||
|
"slash report was for a distinct amount of signers",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Accumulate, and if past the threshold, calculate *the* slash report and start signing it
|
// Accumulate, and if past the threshold, calculate *the* slash report and start signing it
|
||||||
todo!("TODO")
|
match TributaryDb::accumulate(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
self.validators,
|
||||||
|
self.total_weight,
|
||||||
|
block_number,
|
||||||
|
Topic::SlashReport,
|
||||||
|
signer,
|
||||||
|
self.validator_weights[&signer],
|
||||||
|
&slash_points,
|
||||||
|
) {
|
||||||
|
DataSet::None => {}
|
||||||
|
DataSet::Participating(data_set) => {
|
||||||
|
// Find the median reported slashes for this validator
|
||||||
|
// TODO: This lets 34% perform a fatal slash. Should that be allowed?
|
||||||
|
let mut median_slash_report = Vec::with_capacity(self.validators.len());
|
||||||
|
for i in 0 .. self.validators.len() {
|
||||||
|
let mut this_validator =
|
||||||
|
data_set.values().map(|report| report[i]).collect::<Vec<_>>();
|
||||||
|
this_validator.sort_unstable();
|
||||||
|
// Choose the median, where if there are two median values, the lower one is chosen
|
||||||
|
let median_index = if (this_validator.len() % 2) == 1 {
|
||||||
|
this_validator.len() / 2
|
||||||
|
} else {
|
||||||
|
(this_validator.len() / 2) - 1
|
||||||
|
};
|
||||||
|
median_slash_report.push(this_validator[median_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only publish slashes for the `f` worst performers to:
|
||||||
|
// 1) Effect amnesty if there were network disruptions which affected everyone
|
||||||
|
// 2) Ensure the signing threshold doesn't have a disincentive to do their job
|
||||||
|
|
||||||
|
// Find the worst performer within the signing threshold's slash points
|
||||||
|
let f = (self.validators.len() - 1) / 3;
|
||||||
|
let worst_validator_in_supermajority_slash_points = {
|
||||||
|
let mut sorted_slash_points = median_slash_report.clone();
|
||||||
|
sorted_slash_points.sort_unstable();
|
||||||
|
// This won't be a valid index if `f == 0`, which means we don't have any validators
|
||||||
|
// to slash
|
||||||
|
let index_of_first_validator_to_slash = self.validators.len() - f;
|
||||||
|
let index_of_worst_validator_in_supermajority = index_of_first_validator_to_slash - 1;
|
||||||
|
sorted_slash_points[index_of_worst_validator_in_supermajority]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform the amortization
|
||||||
|
for slash_points in &mut median_slash_report {
|
||||||
|
*slash_points =
|
||||||
|
slash_points.saturating_sub(worst_validator_in_supermajority_slash_points)
|
||||||
|
}
|
||||||
|
let amortized_slash_report = median_slash_report;
|
||||||
|
|
||||||
|
// Create the resulting slash report
|
||||||
|
let mut slash_report = vec![];
|
||||||
|
for (validator, points) in self.validators.iter().copied().zip(amortized_slash_report) {
|
||||||
|
if points != 0 {
|
||||||
|
slash_report.push(Slash { key: validator.into(), points });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(slash_report.len() <= f);
|
||||||
|
|
||||||
|
// Recognize the topic for signing the slash report
|
||||||
|
TributaryDb::recognize_topic(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
Topic::Sign {
|
||||||
|
id: VariantSignId::SlashReport,
|
||||||
|
attempt: 0,
|
||||||
|
round: SigningProtocolRound::Preprocess,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Send the message for the processor to start signing
|
||||||
|
TributaryDb::send_message(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
messages::coordinator::CoordinatorMessage::SignSlashReport {
|
||||||
|
session: self.set.session,
|
||||||
|
report: slash_report,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction::Sign { id, attempt, label, data, signed } => todo!("TODO"),
|
Transaction::Sign { id, attempt, round, data, signed } => {
|
||||||
|
let topic = Topic::Sign { id, attempt, round };
|
||||||
|
let signer = signer(signed);
|
||||||
|
|
||||||
|
if u64::try_from(data.len()).unwrap() != self.validator_weights[&signer] {
|
||||||
|
TributaryDb::fatal_slash(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
signer,
|
||||||
|
"signer signed with a distinct amount of key shares than they had key shares",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match TributaryDb::accumulate(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
self.validators,
|
||||||
|
self.total_weight,
|
||||||
|
block_number,
|
||||||
|
topic,
|
||||||
|
signer,
|
||||||
|
self.validator_weights[&signer],
|
||||||
|
&data,
|
||||||
|
) {
|
||||||
|
DataSet::None => {}
|
||||||
|
DataSet::Participating(data_set) => {
|
||||||
|
let id = topic.sign_id(self.set).expect("Topic::Sign didn't have SignId");
|
||||||
|
let flatten_data_set = |data_set| todo!("TODO");
|
||||||
|
let data_set = flatten_data_set(data_set);
|
||||||
|
TributaryDb::send_message(
|
||||||
|
self.txn,
|
||||||
|
self.set,
|
||||||
|
match round {
|
||||||
|
SigningProtocolRound::Preprocess => {
|
||||||
|
messages::sign::CoordinatorMessage::Preprocesses { id, preprocesses: data_set }
|
||||||
|
}
|
||||||
|
SigningProtocolRound::Share => {
|
||||||
|
messages::sign::CoordinatorMessage::Shares { id, shares: data_set }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ use schnorr::SchnorrSignature;
|
|||||||
use scale::Encode;
|
use scale::Encode;
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
use serai_client::primitives::SeraiAddress;
|
use serai_client::{primitives::SeraiAddress, validator_sets::primitives::MAX_KEY_SHARES_PER_SET};
|
||||||
|
|
||||||
use processor_messages::sign::VariantSignId;
|
use messages::sign::VariantSignId;
|
||||||
|
|
||||||
use tributary::{
|
use tributary::{
|
||||||
ReadWrite,
|
ReadWrite,
|
||||||
@@ -25,7 +25,7 @@ use tributary::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The label for data from a signing protocol.
|
/// The round this data is for, within a signing protocol.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum SigningProtocolRound {
|
pub enum SigningProtocolRound {
|
||||||
/// A preprocess.
|
/// A preprocess.
|
||||||
@@ -182,8 +182,8 @@ pub enum Transaction {
|
|||||||
id: VariantSignId,
|
id: VariantSignId,
|
||||||
/// The attempt number of this signing protocol
|
/// The attempt number of this signing protocol
|
||||||
attempt: u32,
|
attempt: u32,
|
||||||
/// The label for this data within the signing protocol
|
/// The round this data is for, within the signing protocol
|
||||||
label: SigningProtocolRound,
|
round: SigningProtocolRound,
|
||||||
/// The data itself
|
/// The data itself
|
||||||
///
|
///
|
||||||
/// There will be `n` blobs of data where `n` is the amount of key shares the validator sending
|
/// There will be `n` blobs of data where `n` is the amount of key shares the validator sending
|
||||||
@@ -234,8 +234,8 @@ impl TransactionTrait for Transaction {
|
|||||||
Transaction::SubstrateBlock { .. } => TransactionKind::Provided("SubstrateBlock"),
|
Transaction::SubstrateBlock { .. } => TransactionKind::Provided("SubstrateBlock"),
|
||||||
Transaction::Batch { .. } => TransactionKind::Provided("Batch"),
|
Transaction::Batch { .. } => TransactionKind::Provided("Batch"),
|
||||||
|
|
||||||
Transaction::Sign { id, attempt, label, signed, .. } => {
|
Transaction::Sign { id, attempt, round, signed, .. } => {
|
||||||
TransactionKind::Signed((b"Sign", id, attempt).encode(), signed.nonce(label.nonce()))
|
TransactionKind::Signed((b"Sign", id, attempt).encode(), signed.nonce(round.nonce()))
|
||||||
}
|
}
|
||||||
|
|
||||||
Transaction::SlashReport { signed, .. } => {
|
Transaction::SlashReport { signed, .. } => {
|
||||||
@@ -253,9 +253,37 @@ impl TransactionTrait for Transaction {
|
|||||||
Blake2b::<U32>::digest(&tx).into()
|
Blake2b::<U32>::digest(&tx).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't have any verification logic embedded into the transaction. We just slash anyone who
|
// This is a stateless verification which we use to enforce some size limits.
|
||||||
// publishes an invalid transaction.
|
|
||||||
fn verify(&self) -> Result<(), TransactionError> {
|
fn verify(&self) -> Result<(), TransactionError> {
|
||||||
|
#[allow(clippy::match_same_arms)]
|
||||||
|
match self {
|
||||||
|
// Fixed-length TX
|
||||||
|
Transaction::RemoveParticipant { .. } => {}
|
||||||
|
|
||||||
|
// TODO: MAX_DKG_PARTICIPATION_LEN
|
||||||
|
Transaction::DkgParticipation { .. } => {}
|
||||||
|
// These are fixed-length TXs
|
||||||
|
Transaction::DkgConfirmationPreprocess { .. } | Transaction::DkgConfirmationShare { .. } => {}
|
||||||
|
|
||||||
|
// Provided TXs
|
||||||
|
Transaction::Cosign { .. } |
|
||||||
|
Transaction::Cosigned { .. } |
|
||||||
|
Transaction::SubstrateBlock { .. } |
|
||||||
|
Transaction::Batch { .. } => {}
|
||||||
|
|
||||||
|
Transaction::Sign { data, .. } => {
|
||||||
|
if data.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() {
|
||||||
|
Err(TransactionError::InvalidContent)?
|
||||||
|
}
|
||||||
|
// TODO: MAX_SIGN_LEN
|
||||||
|
}
|
||||||
|
|
||||||
|
Transaction::SlashReport { slash_points, .. } => {
|
||||||
|
if slash_points.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() {
|
||||||
|
Err(TransactionError::InvalidContent)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ pub mod key_gen {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
// Instructs the Processor to begin the key generation process.
|
/// Instructs the Processor to begin the key generation process.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator when it creates the Tributary (TODO).
|
||||||
GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec<u8>)> },
|
GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec<u8>)> },
|
||||||
// Received participations for the specified key generation protocol.
|
/// Received participations for the specified key generation protocol.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary scanner.
|
||||||
Participation { session: Session, participant: Participant, participation: Vec<u8> },
|
Participation { session: Session, participant: Participant, participation: Vec<u8> },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,11 +117,17 @@ pub mod sign {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
// Received preprocesses for the specified signing protocol.
|
/// Received preprocesses for the specified signing protocol.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary scanner.
|
||||||
Preprocesses { id: SignId, preprocesses: HashMap<Participant, Vec<u8>> },
|
Preprocesses { id: SignId, preprocesses: HashMap<Participant, Vec<u8>> },
|
||||||
// Received shares for the specified signing protocol.
|
// Received shares for the specified signing protocol.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary scanner.
|
||||||
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
|
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
|
||||||
// Re-attempt a signing protocol.
|
// Re-attempt a signing protocol.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary re-attempt scheduling logic.
|
||||||
Reattempt { id: SignId },
|
Reattempt { id: SignId },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +167,13 @@ pub mod coordinator {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
|
/// Cosign the specified Substrate block.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary scanner.
|
||||||
CosignSubstrateBlock { session: Session, block_number: u64, block: [u8; 32] },
|
CosignSubstrateBlock { session: Session, block_number: u64, block: [u8; 32] },
|
||||||
|
/// Sign the slash report for this session.
|
||||||
|
///
|
||||||
|
/// This is sent by the Coordinator's Tributary scanner.
|
||||||
SignSlashReport { session: Session, report: Vec<Slash> },
|
SignSlashReport { session: Session, report: Vec<Slash> },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,12 +212,18 @@ pub mod substrate {
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum CoordinatorMessage {
|
pub enum CoordinatorMessage {
|
||||||
/// Keys set on the Serai blockchain.
|
/// Keys set on the Serai blockchain.
|
||||||
|
///
|
||||||
|
/// This is set by the Coordinator's Substrate canonical event stream.
|
||||||
SetKeys { serai_time: u64, session: Session, key_pair: KeyPair },
|
SetKeys { serai_time: u64, session: Session, key_pair: KeyPair },
|
||||||
/// Slashes reported on the Serai blockchain OR the process timed out.
|
/// Slashes reported on the Serai blockchain OR the process timed out.
|
||||||
///
|
///
|
||||||
/// This is the final message for a session,
|
/// This is the final message for a session,
|
||||||
|
///
|
||||||
|
/// This is set by the Coordinator's Substrate canonical event stream.
|
||||||
SlashesReported { session: Session },
|
SlashesReported { session: Session },
|
||||||
/// A block from Serai with relevance to this processor.
|
/// A block from Serai with relevance to this processor.
|
||||||
|
///
|
||||||
|
/// This is set by the Coordinator's Substrate canonical event stream.
|
||||||
Block {
|
Block {
|
||||||
serai_block_number: u64,
|
serai_block_number: u64,
|
||||||
batch: Option<ExecutedBatch>,
|
batch: Option<ExecutedBatch>,
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ pub struct Slash {
|
|||||||
deserialize_with = "serai_primitives::borsh_deserialize_public"
|
deserialize_with = "serai_primitives::borsh_deserialize_public"
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
key: Public,
|
pub key: Public,
|
||||||
points: u32,
|
pub points: u32,
|
||||||
}
|
}
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
|||||||
Reference in New Issue
Block a user