mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Route blame between Processor and Coordinator (#427)
* Have processor report errors during the DKG to the coordinator * Add RemoveParticipant, InvalidDkgShare to coordinator * Route DKG blame around coordinator * Allow public construction of AdditionalBlameMachine Necessary for upcoming work on handling DKG blame in the processor and coordinator. Additionally fixes a publicly reachable panic when commitments parsed with one ThresholdParams are used in a machine using another set of ThresholdParams. Renames InvalidProofOfKnowledge to InvalidCommitments. * Remove unused error from dleq * Implement support for VerifyBlame in the processor * Have coordinator send the processor share message relevant to Blame * Remove desync between processors reporting InvalidShare and ones reporting GeneratedKeyPair * Route blame on sign between processor and coordinator Doesn't yet act on it in coordinator. * Move txn usage as needed for stable Rust to build * Correct InvalidDkgShare serialization
This commit is contained in:
@@ -181,12 +181,18 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
// in-set, making the Tributary relevant
|
||||
ProcessorMessage::KeyGen(inner_msg) => match inner_msg {
|
||||
key_gen::ProcessorMessage::Commitments { id, .. } => Some(id.set.session),
|
||||
key_gen::ProcessorMessage::InvalidCommitments { id, .. } => Some(id.set.session),
|
||||
key_gen::ProcessorMessage::Shares { id, .. } => Some(id.set.session),
|
||||
key_gen::ProcessorMessage::InvalidShare { id, .. } => Some(id.set.session),
|
||||
key_gen::ProcessorMessage::GeneratedKeyPair { id, .. } => Some(id.set.session),
|
||||
key_gen::ProcessorMessage::Blame { id, .. } => Some(id.set.session),
|
||||
},
|
||||
// TODO: Review replacing key with Session in messages?
|
||||
ProcessorMessage::Sign(inner_msg) => match inner_msg {
|
||||
// We'll only receive Preprocess and Share if we're actively signing
|
||||
// We'll only receive InvalidParticipant/Preprocess/Share if we're actively signing
|
||||
sign::ProcessorMessage::InvalidParticipant { id, .. } => {
|
||||
Some(SubstrateDb::<D>::session_for_key(&txn, &id.key).unwrap())
|
||||
}
|
||||
sign::ProcessorMessage::Preprocess { id, .. } => {
|
||||
Some(SubstrateDb::<D>::session_for_key(&txn, &id.key).unwrap())
|
||||
}
|
||||
@@ -261,6 +267,9 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
None
|
||||
}
|
||||
// We'll only fire these if we are the Substrate signer, making the Tributary relevant
|
||||
coordinator::ProcessorMessage::InvalidParticipant { id, .. } => {
|
||||
Some(SubstrateDb::<D>::session_for_key(&txn, &id.key).unwrap())
|
||||
}
|
||||
coordinator::ProcessorMessage::BatchPreprocess { id, .. } => {
|
||||
Some(SubstrateDb::<D>::session_for_key(&txn, &id.key).unwrap())
|
||||
}
|
||||
@@ -419,6 +428,15 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
key_gen::ProcessorMessage::Commitments { id, commitments } => {
|
||||
vec![Transaction::DkgCommitments(id.attempt, commitments, Transaction::empty_signed())]
|
||||
}
|
||||
key_gen::ProcessorMessage::InvalidCommitments { id: _, faulty } => {
|
||||
// This doesn't need the ID since it's a Provided transaction which everyone will provide
|
||||
// With this provision comes explicit ordering (with regards to other RemoveParticipant
|
||||
// transactions) and group consensus
|
||||
// Accordingly, this can't be replayed
|
||||
// It could be included on-chain early/late with regards to the chain's active attempt,
|
||||
// which attempt scheduling is written to avoid
|
||||
vec![Transaction::RemoveParticipant(faulty)]
|
||||
}
|
||||
key_gen::ProcessorMessage::Shares { id, mut shares } => {
|
||||
// Create a MuSig-based machine to inform Substrate of this key generation
|
||||
let nonces = crate::tributary::dkg_confirmation_nonces(key, spec, id.attempt);
|
||||
@@ -427,6 +445,9 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
.i(pub_key)
|
||||
.expect("processor message to DKG for a session we aren't a validator in");
|
||||
|
||||
// TODO: This is [receiver_share][sender_share] and is later transposed to
|
||||
// [sender_share][receiver_share]. Make this [sender_share][receiver_share] from the
|
||||
// start?
|
||||
// `tx_shares` needs to be done here as while it can be serialized from the HashMap
|
||||
// without further context, it can't be deserialized without context
|
||||
let mut tx_shares = Vec::with_capacity(shares.len());
|
||||
@@ -455,10 +476,38 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
signed: Transaction::empty_signed(),
|
||||
}]
|
||||
}
|
||||
key_gen::ProcessorMessage::InvalidShare { id, accuser, faulty, blame } => {
|
||||
assert_eq!(
|
||||
id.set.network, msg.network,
|
||||
"processor claimed to be a different network than it was for in InvalidShare",
|
||||
);
|
||||
|
||||
// Check if the MuSig signature had any errors as if so, we need to provide
|
||||
// RemoveParticipant
|
||||
// As for the safety of calling error_generating_key_pair, the processor is presumed
|
||||
// to only send InvalidShare or GeneratedKeyPair for a given attempt
|
||||
let mut txs = if let Some(faulty) =
|
||||
crate::tributary::error_generating_key_pair::<D, _>(&txn, key, spec, id.attempt)
|
||||
{
|
||||
vec![Transaction::RemoveParticipant(faulty)]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
txs.push(Transaction::InvalidDkgShare {
|
||||
attempt: id.attempt,
|
||||
accuser,
|
||||
faulty,
|
||||
blame,
|
||||
signed: Transaction::empty_signed(),
|
||||
});
|
||||
|
||||
txs
|
||||
}
|
||||
key_gen::ProcessorMessage::GeneratedKeyPair { id, substrate_key, network_key } => {
|
||||
assert_eq!(
|
||||
id.set.network, msg.network,
|
||||
"processor claimed to be a different network than it was for GeneratedKeyPair",
|
||||
"processor claimed to be a different network than it was for in GeneratedKeyPair",
|
||||
);
|
||||
// TODO2: Also check the other KeyGenId fields
|
||||
|
||||
@@ -476,12 +525,24 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
vec![Transaction::DkgConfirmed(id.attempt, share, Transaction::empty_signed())]
|
||||
}
|
||||
Err(p) => {
|
||||
todo!("participant {p:?} sent invalid DKG confirmation preprocesses")
|
||||
vec![Transaction::RemoveParticipant(p)]
|
||||
}
|
||||
}
|
||||
}
|
||||
key_gen::ProcessorMessage::Blame { id, participant } => {
|
||||
assert_eq!(
|
||||
id.set.network, msg.network,
|
||||
"processor claimed to be a different network than it was for in Blame",
|
||||
);
|
||||
vec![Transaction::RemoveParticipant(participant)]
|
||||
}
|
||||
},
|
||||
ProcessorMessage::Sign(msg) => match msg {
|
||||
sign::ProcessorMessage::InvalidParticipant { .. } => {
|
||||
// TODO: Locally increase slash points to maximum (distinct from an explicitly fatal
|
||||
// slash) and censor transactions (yet don't explicitly ban)
|
||||
vec![]
|
||||
}
|
||||
sign::ProcessorMessage::Preprocess { id, preprocesses } => {
|
||||
if id.attempt == 0 {
|
||||
MainDb::<D>::save_first_preprocess(
|
||||
@@ -532,6 +593,11 @@ async fn handle_processor_message<D: Db, P: P2p>(
|
||||
},
|
||||
ProcessorMessage::Coordinator(inner_msg) => match inner_msg {
|
||||
coordinator::ProcessorMessage::SubstrateBlockAck { .. } => unreachable!(),
|
||||
coordinator::ProcessorMessage::InvalidParticipant { .. } => {
|
||||
// TODO: Locally increase slash points to maximum (distinct from an explicitly fatal
|
||||
// slash) and censor transactions (yet don't explicitly ban)
|
||||
vec![]
|
||||
}
|
||||
coordinator::ProcessorMessage::BatchPreprocess { id, block, preprocesses } => {
|
||||
log::info!(
|
||||
"informed of batch (sign ID {}, attempt {}) for block {}",
|
||||
|
||||
@@ -88,6 +88,11 @@ fn serialize_sign_data() {
|
||||
|
||||
#[test]
|
||||
fn serialize_transaction() {
|
||||
test_read_write(Transaction::RemoveParticipant(
|
||||
frost::Participant::new(u16::try_from(OsRng.next_u64() >> 48).unwrap().saturating_add(1))
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
{
|
||||
let mut commitments = vec![random_vec(&mut OsRng, 512)];
|
||||
for _ in 0 .. (OsRng.next_u64() % 100) {
|
||||
@@ -133,6 +138,26 @@ fn serialize_transaction() {
|
||||
});
|
||||
}
|
||||
|
||||
for i in 0 .. 2 {
|
||||
test_read_write(Transaction::InvalidDkgShare {
|
||||
attempt: random_u32(&mut OsRng),
|
||||
accuser: frost::Participant::new(
|
||||
u16::try_from(OsRng.next_u64() >> 48).unwrap().saturating_add(1),
|
||||
)
|
||||
.unwrap(),
|
||||
faulty: frost::Participant::new(
|
||||
u16::try_from(OsRng.next_u64() >> 48).unwrap().saturating_add(1),
|
||||
)
|
||||
.unwrap(),
|
||||
blame: if i == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(random_vec(&mut OsRng, 500)).filter(|blame| !blame.is_empty())
|
||||
},
|
||||
signed: random_signed(&mut OsRng),
|
||||
});
|
||||
}
|
||||
|
||||
test_read_write(Transaction::DkgConfirmed(
|
||||
random_u32(&mut OsRng),
|
||||
{
|
||||
|
||||
@@ -87,11 +87,14 @@ impl<D: Db> TributaryDb<D> {
|
||||
fn fatal_slashes_key(genesis: [u8; 32]) -> Vec<u8> {
|
||||
Self::tributary_key(b"fatal_slashes", genesis)
|
||||
}
|
||||
fn fatally_slashed_key(account: [u8; 32]) -> Vec<u8> {
|
||||
Self::tributary_key(b"fatally_slashed", account)
|
||||
fn fatally_slashed_key(genesis: [u8; 32], account: [u8; 32]) -> Vec<u8> {
|
||||
Self::tributary_key(b"fatally_slashed", (genesis, account).encode())
|
||||
}
|
||||
pub fn is_fatally_slashed<G: Get>(getter: &G, genesis: [u8; 32], account: [u8; 32]) -> bool {
|
||||
getter.get(Self::fatally_slashed_key(genesis, account)).is_some()
|
||||
}
|
||||
pub fn set_fatally_slashed(txn: &mut D::Transaction<'_>, genesis: [u8; 32], account: [u8; 32]) {
|
||||
txn.put(Self::fatally_slashed_key(account), []);
|
||||
txn.put(Self::fatally_slashed_key(genesis, account), []);
|
||||
|
||||
let key = Self::fatal_slashes_key(genesis);
|
||||
let mut existing = txn.get(&key).unwrap_or(vec![]);
|
||||
@@ -105,6 +108,27 @@ impl<D: Db> TributaryDb<D> {
|
||||
txn.put(key, existing);
|
||||
}
|
||||
|
||||
fn share_for_blame_key(genesis: &[u8], from: Participant, to: Participant) -> Vec<u8> {
|
||||
Self::tributary_key(b"share_for_blame", (genesis, u16::from(from), u16::from(to)).encode())
|
||||
}
|
||||
pub fn save_share_for_blame(
|
||||
txn: &mut D::Transaction<'_>,
|
||||
genesis: &[u8],
|
||||
from: Participant,
|
||||
to: Participant,
|
||||
share: &[u8],
|
||||
) {
|
||||
txn.put(Self::share_for_blame_key(genesis, from, to), share);
|
||||
}
|
||||
pub fn share_for_blame<G: Get>(
|
||||
getter: &G,
|
||||
genesis: &[u8],
|
||||
from: Participant,
|
||||
to: Participant,
|
||||
) -> Option<Vec<u8>> {
|
||||
getter.get(Self::share_for_blame_key(genesis, from, to))
|
||||
}
|
||||
|
||||
// The plan IDs associated with a Substrate block
|
||||
fn plan_ids_key(genesis: &[u8], block: u64) -> Vec<u8> {
|
||||
Self::tributary_key(b"plan_ids", [genesis, block.to_le_bytes().as_ref()].concat())
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use core::{ops::Deref, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||
use frost::dkg::Participant;
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use serai_client::{
|
||||
Signature,
|
||||
Public, Signature,
|
||||
validator_sets::primitives::{ValidatorSet, KeyPair},
|
||||
subxt::utils::Encoded,
|
||||
SeraiValidatorSets,
|
||||
};
|
||||
|
||||
use tributary::Signed;
|
||||
use tributary::{Signed, TransactionKind, TransactionTrait};
|
||||
|
||||
use processor_messages::{
|
||||
key_gen::{self, KeyGenId},
|
||||
@@ -22,7 +22,7 @@ use processor_messages::{
|
||||
sign::{self, SignId},
|
||||
};
|
||||
|
||||
use serai_db::Db;
|
||||
use serai_db::{Get, Db};
|
||||
|
||||
use crate::{
|
||||
processors::Processors,
|
||||
@@ -56,7 +56,33 @@ pub fn dkg_confirmation_nonces(
|
||||
DkgConfirmer::preprocess(spec, key, attempt)
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_ref_mut)]
|
||||
// If there's an error generating a key pair, return any errors which would've occured when
|
||||
// executing the DkgConfirmer in order to stay in sync with those who did.
|
||||
//
|
||||
// The caller must ensure only error_generating_key_pair or generated_key_pair is called for a
|
||||
// given attempt.
|
||||
pub fn error_generating_key_pair<D: Db, G: Get>(
|
||||
getter: &G,
|
||||
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||
spec: &TributarySpec,
|
||||
attempt: u32,
|
||||
) -> Option<Participant> {
|
||||
let preprocesses =
|
||||
TributaryDb::<D>::confirmation_nonces(getter, spec.genesis(), attempt).unwrap();
|
||||
|
||||
// Sign a key pair which can't be valid
|
||||
// (0xff used as 0 would be the Ristretto identity point, 0-length for the network key)
|
||||
let key_pair = (Public([0xff; 32]), vec![0xffu8; 0].try_into().unwrap());
|
||||
match DkgConfirmer::share(spec, key, attempt, preprocesses, &key_pair) {
|
||||
Ok(mut share) => {
|
||||
// Zeroize the share to ensure it's not accessed
|
||||
share.zeroize();
|
||||
None
|
||||
}
|
||||
Err(p) => Some(p),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generated_key_pair<D: Db>(
|
||||
txn: &mut D::Transaction<'_>,
|
||||
key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
|
||||
@@ -69,7 +95,7 @@ pub fn generated_key_pair<D: Db>(
|
||||
DkgConfirmer::share(spec, key, attempt, preprocesses, key_pair)
|
||||
}
|
||||
|
||||
pub(crate) fn fatal_slash<D: Db>(
|
||||
pub(super) fn fatal_slash<D: Db>(
|
||||
txn: &mut D::Transaction<'_>,
|
||||
genesis: [u8; 32],
|
||||
account: [u8; 32],
|
||||
@@ -78,6 +104,33 @@ pub(crate) fn fatal_slash<D: Db>(
|
||||
log::warn!("fatally slashing {}. reason: {}", hex::encode(account), reason);
|
||||
TributaryDb::<D>::set_fatally_slashed(txn, genesis, account);
|
||||
// TODO: disconnect the node from network/ban from further participation in all Tributaries
|
||||
|
||||
// TODO: If during DKG, trigger a re-attempt
|
||||
}
|
||||
|
||||
// TODO: Once Substrate confirms a key, we need to rotate our validator set OR form a second
|
||||
// Tributary post-DKG
|
||||
// https://github.com/serai-dex/serai/issues/426
|
||||
|
||||
fn fatal_slash_with_participant_index<D: Db>(
|
||||
spec: &TributarySpec,
|
||||
txn: &mut <D as Db>::Transaction<'_>,
|
||||
i: Participant,
|
||||
reason: &str,
|
||||
) {
|
||||
// Resolve from Participant to <Ristretto as Ciphersuite>::G
|
||||
let i = u16::from(i);
|
||||
let mut validator = None;
|
||||
for (potential, _) in spec.validators() {
|
||||
let v_i = spec.i(potential).unwrap();
|
||||
if (u16::from(v_i.start) <= i) && (i < u16::from(v_i.end)) {
|
||||
validator = Some(potential);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let validator = validator.unwrap();
|
||||
|
||||
fatal_slash::<D>(txn, spec.genesis(), validator.to_bytes(), reason);
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_application_tx<
|
||||
@@ -98,6 +151,15 @@ pub(crate) async fn handle_application_tx<
|
||||
) {
|
||||
let genesis = spec.genesis();
|
||||
|
||||
// Don't handle transactions from fatally slashed participants
|
||||
// TODO: Because fatally slashed participants can still publish onto the blockchain, they have
|
||||
// a notable DoS ability
|
||||
if let TransactionKind::Signed(signed) = tx.kind() {
|
||||
if TributaryDb::<D>::is_fatally_slashed(txn, genesis, signed.signer.to_bytes()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let handle = |txn: &mut <D as Db>::Transaction<'_>,
|
||||
data_spec: &DataSpecification,
|
||||
bytes: Vec<u8>,
|
||||
@@ -178,6 +240,9 @@ pub(crate) async fn handle_application_tx<
|
||||
}
|
||||
|
||||
match tx {
|
||||
Transaction::RemoveParticipant(i) => {
|
||||
fatal_slash_with_participant_index::<D>(spec, txn, i, "RemoveParticipant Provided TX")
|
||||
}
|
||||
Transaction::DkgCommitments(attempt, commitments, signed) => {
|
||||
let Ok(_) = check_sign_data_len::<D>(txn, spec, signed.signer, commitments.len()) else {
|
||||
return;
|
||||
@@ -230,7 +295,28 @@ pub(crate) async fn handle_application_tx<
|
||||
}
|
||||
}
|
||||
|
||||
// Only save our share's bytes
|
||||
// Save each share as needed for blame
|
||||
{
|
||||
let from = spec.i(signed.signer).unwrap();
|
||||
for (to, shares) in shares.iter().enumerate() {
|
||||
// 0-indexed (the enumeration) to 1-indexed (Participant)
|
||||
let mut to = u16::try_from(to).unwrap() + 1;
|
||||
// Adjust for the omission of the sender's own shares
|
||||
if to >= u16::from(from.start) {
|
||||
to += u16::from(from.end) - u16::from(from.start);
|
||||
}
|
||||
let to = Participant::new(to).unwrap();
|
||||
|
||||
for (sender_share, share) in shares.iter().enumerate() {
|
||||
let from =
|
||||
Participant::new(u16::from(from.start) + u16::try_from(sender_share).unwrap())
|
||||
.unwrap();
|
||||
TributaryDb::<D>::save_share_for_blame(txn, &genesis, from, to, share);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter down to only our share's bytes for handle
|
||||
let our_i = spec
|
||||
.i(Ristretto::generator() * key.deref())
|
||||
.expect("in a tributary we're not a validator for");
|
||||
@@ -327,6 +413,49 @@ pub(crate) async fn handle_application_tx<
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Only accept one of either InvalidDkgShare/DkgConfirmed per signer
|
||||
// TODO: Ban self-accusals
|
||||
Transaction::InvalidDkgShare { attempt, accuser, faulty, blame, signed } => {
|
||||
let range = spec.i(signed.signer).unwrap();
|
||||
if (u16::from(accuser) < u16::from(range.start)) ||
|
||||
(u16::from(range.end) <= u16::from(accuser))
|
||||
{
|
||||
fatal_slash::<D>(
|
||||
txn,
|
||||
genesis,
|
||||
signed.signer.to_bytes(),
|
||||
"accused with a Participant index which wasn't theirs",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if !((u16::from(range.start) <= u16::from(faulty)) &&
|
||||
(u16::from(faulty) < u16::from(range.end)))
|
||||
{
|
||||
fatal_slash::<D>(
|
||||
txn,
|
||||
genesis,
|
||||
signed.signer.to_bytes(),
|
||||
"accused self of having an InvalidDkgShare",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let share = TributaryDb::<D>::share_for_blame(txn, &genesis, accuser, faulty).unwrap();
|
||||
processors
|
||||
.send(
|
||||
spec.set().network,
|
||||
key_gen::CoordinatorMessage::VerifyBlame {
|
||||
id: KeyGenId { set: spec.set(), attempt },
|
||||
accuser,
|
||||
accused: faulty,
|
||||
share,
|
||||
blame,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Transaction::DkgConfirmed(attempt, shares, signed) => {
|
||||
match handle(
|
||||
txn,
|
||||
@@ -347,11 +476,14 @@ pub(crate) async fn handle_application_tx<
|
||||
"(including us) fires DkgConfirmed, yet no confirming key pair"
|
||||
)
|
||||
});
|
||||
let Ok(sig) = DkgConfirmer::complete(spec, key, attempt, preprocesses, &key_pair, shares)
|
||||
else {
|
||||
// TODO: Full slash
|
||||
todo!();
|
||||
};
|
||||
let sig =
|
||||
match DkgConfirmer::complete(spec, key, attempt, preprocesses, &key_pair, shares) {
|
||||
Ok(sig) => sig,
|
||||
Err(p) => {
|
||||
fatal_slash_with_participant_index::<D>(spec, txn, p, "invalid DkgConfirmer share");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
publish_serai_tx(
|
||||
spec.set(),
|
||||
|
||||
@@ -233,6 +233,8 @@ impl<const N: usize> ReadWrite for SignData<N> {
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Transaction {
|
||||
RemoveParticipant(Participant),
|
||||
|
||||
// Once this completes successfully, no more instances should be created.
|
||||
DkgCommitments(u32, Vec<Vec<u8>>, Signed),
|
||||
DkgShares {
|
||||
@@ -242,6 +244,13 @@ pub enum Transaction {
|
||||
confirmation_nonces: [u8; 64],
|
||||
signed: Signed,
|
||||
},
|
||||
InvalidDkgShare {
|
||||
attempt: u32,
|
||||
accuser: Participant,
|
||||
faulty: Participant,
|
||||
blame: Option<Vec<u8>>,
|
||||
signed: Signed,
|
||||
},
|
||||
DkgConfirmed(u32, [u8; 32], Signed),
|
||||
|
||||
// When we have synchrony on a batch, we can allow signing it
|
||||
@@ -279,7 +288,15 @@ impl ReadWrite for Transaction {
|
||||
reader.read_exact(&mut kind)?;
|
||||
|
||||
match kind[0] {
|
||||
0 => {
|
||||
0 => Ok(Transaction::RemoveParticipant({
|
||||
let mut participant = [0; 2];
|
||||
reader.read_exact(&mut participant)?;
|
||||
Participant::new(u16::from_le_bytes(participant)).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "invalid participant in RemoveParticipant")
|
||||
})?
|
||||
})),
|
||||
|
||||
1 => {
|
||||
let mut attempt = [0; 4];
|
||||
reader.read_exact(&mut attempt)?;
|
||||
let attempt = u32::from_le_bytes(attempt);
|
||||
@@ -314,7 +331,7 @@ impl ReadWrite for Transaction {
|
||||
Ok(Transaction::DkgCommitments(attempt, commitments, signed))
|
||||
}
|
||||
|
||||
1 => {
|
||||
2 => {
|
||||
let mut attempt = [0; 4];
|
||||
reader.read_exact(&mut attempt)?;
|
||||
let attempt = u32::from_le_bytes(attempt);
|
||||
@@ -351,7 +368,40 @@ impl ReadWrite for Transaction {
|
||||
Ok(Transaction::DkgShares { attempt, shares, confirmation_nonces, signed })
|
||||
}
|
||||
|
||||
2 => {
|
||||
3 => {
|
||||
let mut attempt = [0; 4];
|
||||
reader.read_exact(&mut attempt)?;
|
||||
let attempt = u32::from_le_bytes(attempt);
|
||||
|
||||
let mut accuser = [0; 2];
|
||||
reader.read_exact(&mut accuser)?;
|
||||
let accuser = Participant::new(u16::from_le_bytes(accuser)).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "invalid participant in InvalidDkgShare")
|
||||
})?;
|
||||
|
||||
let mut faulty = [0; 2];
|
||||
reader.read_exact(&mut faulty)?;
|
||||
let faulty = Participant::new(u16::from_le_bytes(faulty)).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "invalid participant in InvalidDkgShare")
|
||||
})?;
|
||||
|
||||
let mut blame_len = [0; 2];
|
||||
reader.read_exact(&mut blame_len)?;
|
||||
let mut blame = vec![0; u16::from_le_bytes(blame_len).into()];
|
||||
reader.read_exact(&mut blame)?;
|
||||
|
||||
let signed = Signed::read(reader)?;
|
||||
|
||||
Ok(Transaction::InvalidDkgShare {
|
||||
attempt,
|
||||
accuser,
|
||||
faulty,
|
||||
blame: Some(blame).filter(|blame| !blame.is_empty()),
|
||||
signed,
|
||||
})
|
||||
}
|
||||
|
||||
4 => {
|
||||
let mut attempt = [0; 4];
|
||||
reader.read_exact(&mut attempt)?;
|
||||
let attempt = u32::from_le_bytes(attempt);
|
||||
@@ -364,7 +414,7 @@ impl ReadWrite for Transaction {
|
||||
Ok(Transaction::DkgConfirmed(attempt, confirmation_share, signed))
|
||||
}
|
||||
|
||||
3 => {
|
||||
5 => {
|
||||
let mut block = [0; 32];
|
||||
reader.read_exact(&mut block)?;
|
||||
let mut batch = [0; 5];
|
||||
@@ -372,19 +422,19 @@ impl ReadWrite for Transaction {
|
||||
Ok(Transaction::Batch(block, batch))
|
||||
}
|
||||
|
||||
4 => {
|
||||
6 => {
|
||||
let mut block = [0; 8];
|
||||
reader.read_exact(&mut block)?;
|
||||
Ok(Transaction::SubstrateBlock(u64::from_le_bytes(block)))
|
||||
}
|
||||
|
||||
5 => SignData::read(reader).map(Transaction::BatchPreprocess),
|
||||
6 => SignData::read(reader).map(Transaction::BatchShare),
|
||||
7 => SignData::read(reader).map(Transaction::BatchPreprocess),
|
||||
8 => SignData::read(reader).map(Transaction::BatchShare),
|
||||
|
||||
7 => SignData::read(reader).map(Transaction::SignPreprocess),
|
||||
8 => SignData::read(reader).map(Transaction::SignShare),
|
||||
9 => SignData::read(reader).map(Transaction::SignPreprocess),
|
||||
10 => SignData::read(reader).map(Transaction::SignShare),
|
||||
|
||||
9 => {
|
||||
11 => {
|
||||
let mut plan = [0; 32];
|
||||
reader.read_exact(&mut plan)?;
|
||||
|
||||
@@ -405,8 +455,13 @@ impl ReadWrite for Transaction {
|
||||
|
||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
Transaction::DkgCommitments(attempt, commitments, signed) => {
|
||||
Transaction::RemoveParticipant(i) => {
|
||||
writer.write_all(&[0])?;
|
||||
writer.write_all(&u16::from(*i).to_le_bytes())
|
||||
}
|
||||
|
||||
Transaction::DkgCommitments(attempt, commitments, signed) => {
|
||||
writer.write_all(&[1])?;
|
||||
writer.write_all(&attempt.to_le_bytes())?;
|
||||
if commitments.is_empty() {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "zero commitments in DkgCommitments"))?
|
||||
@@ -428,7 +483,7 @@ impl ReadWrite for Transaction {
|
||||
}
|
||||
|
||||
Transaction::DkgShares { attempt, shares, confirmation_nonces, signed } => {
|
||||
writer.write_all(&[1])?;
|
||||
writer.write_all(&[2])?;
|
||||
writer.write_all(&attempt.to_le_bytes())?;
|
||||
|
||||
// `shares` is a Vec which is supposed to map to a HashMap<Pariticpant, Vec<u8>>. Since we
|
||||
@@ -456,43 +511,59 @@ impl ReadWrite for Transaction {
|
||||
signed.write(writer)
|
||||
}
|
||||
|
||||
Transaction::InvalidDkgShare { attempt, accuser, faulty, blame, signed } => {
|
||||
writer.write_all(&[3])?;
|
||||
writer.write_all(&attempt.to_le_bytes())?;
|
||||
writer.write_all(&u16::from(*accuser).to_le_bytes())?;
|
||||
writer.write_all(&u16::from(*faulty).to_le_bytes())?;
|
||||
|
||||
// Flattens Some(vec![]) to None on the expectation no actual blame will be 0-length
|
||||
assert!(blame.as_ref().map(|blame| blame.len()).unwrap_or(1) != 0);
|
||||
let blame_len =
|
||||
u16::try_from(blame.as_ref().unwrap_or(&vec![]).len()).expect("blame exceeded 64 KB");
|
||||
writer.write_all(&blame_len.to_le_bytes())?;
|
||||
writer.write_all(blame.as_ref().unwrap_or(&vec![]))?;
|
||||
|
||||
signed.write(writer)
|
||||
}
|
||||
|
||||
Transaction::DkgConfirmed(attempt, share, signed) => {
|
||||
writer.write_all(&[2])?;
|
||||
writer.write_all(&[4])?;
|
||||
writer.write_all(&attempt.to_le_bytes())?;
|
||||
writer.write_all(share)?;
|
||||
signed.write(writer)
|
||||
}
|
||||
|
||||
Transaction::Batch(block, batch) => {
|
||||
writer.write_all(&[3])?;
|
||||
writer.write_all(&[5])?;
|
||||
writer.write_all(block)?;
|
||||
writer.write_all(batch)
|
||||
}
|
||||
|
||||
Transaction::SubstrateBlock(block) => {
|
||||
writer.write_all(&[4])?;
|
||||
writer.write_all(&[6])?;
|
||||
writer.write_all(&block.to_le_bytes())
|
||||
}
|
||||
|
||||
Transaction::BatchPreprocess(data) => {
|
||||
writer.write_all(&[5])?;
|
||||
writer.write_all(&[7])?;
|
||||
data.write(writer)
|
||||
}
|
||||
Transaction::BatchShare(data) => {
|
||||
writer.write_all(&[6])?;
|
||||
writer.write_all(&[8])?;
|
||||
data.write(writer)
|
||||
}
|
||||
|
||||
Transaction::SignPreprocess(data) => {
|
||||
writer.write_all(&[7])?;
|
||||
writer.write_all(&[9])?;
|
||||
data.write(writer)
|
||||
}
|
||||
Transaction::SignShare(data) => {
|
||||
writer.write_all(&[8])?;
|
||||
writer.write_all(&[10])?;
|
||||
data.write(writer)
|
||||
}
|
||||
Transaction::SignCompleted { plan, tx_hash, first_signer, signature } => {
|
||||
writer.write_all(&[9])?;
|
||||
writer.write_all(&[11])?;
|
||||
writer.write_all(plan)?;
|
||||
writer
|
||||
.write_all(&[u8::try_from(tx_hash.len()).expect("tx hash length exceed 255 bytes")])?;
|
||||
@@ -507,8 +578,11 @@ impl ReadWrite for Transaction {
|
||||
impl TransactionTrait for Transaction {
|
||||
fn kind(&self) -> TransactionKind<'_> {
|
||||
match self {
|
||||
Transaction::RemoveParticipant(_) => TransactionKind::Provided("remove"),
|
||||
|
||||
Transaction::DkgCommitments(_, _, signed) => TransactionKind::Signed(signed),
|
||||
Transaction::DkgShares { signed, .. } => TransactionKind::Signed(signed),
|
||||
Transaction::InvalidDkgShare { signed, .. } => TransactionKind::Signed(signed),
|
||||
Transaction::DkgConfirmed(_, _, signed) => TransactionKind::Signed(signed),
|
||||
|
||||
Transaction::Batch(_, _) => TransactionKind::Provided("batch"),
|
||||
@@ -574,8 +648,11 @@ impl Transaction {
|
||||
) {
|
||||
fn signed(tx: &mut Transaction) -> &mut Signed {
|
||||
match tx {
|
||||
Transaction::RemoveParticipant(_) => panic!("signing RemoveParticipant"),
|
||||
|
||||
Transaction::DkgCommitments(_, _, ref mut signed) => signed,
|
||||
Transaction::DkgShares { ref mut signed, .. } => signed,
|
||||
Transaction::InvalidDkgShare { ref mut signed, .. } => signed,
|
||||
Transaction::DkgConfirmed(_, _, ref mut signed) => signed,
|
||||
|
||||
Transaction::Batch(_, _) => panic!("signing Batch"),
|
||||
|
||||
@@ -85,6 +85,8 @@ impl<D: Db> NonceDecider<D> {
|
||||
|
||||
pub fn nonce<G: Get>(getter: &G, genesis: [u8; 32], tx: &Transaction) -> Option<Option<u32>> {
|
||||
match tx {
|
||||
Transaction::RemoveParticipant(_) => None,
|
||||
|
||||
Transaction::DkgCommitments(attempt, _, _) => {
|
||||
assert_eq!(*attempt, 0);
|
||||
Some(Some(0))
|
||||
@@ -93,6 +95,12 @@ impl<D: Db> NonceDecider<D> {
|
||||
assert_eq!(*attempt, 0);
|
||||
Some(Some(1))
|
||||
}
|
||||
// InvalidDkgShare and DkgConfirmed share a nonce due to the expected existence of only one
|
||||
// on-chain
|
||||
Transaction::InvalidDkgShare { attempt, .. } => {
|
||||
assert_eq!(*attempt, 0);
|
||||
Some(Some(2))
|
||||
}
|
||||
Transaction::DkgConfirmed(attempt, _, _) => {
|
||||
assert_eq!(*attempt, 0);
|
||||
Some(Some(2))
|
||||
|
||||
Reference in New Issue
Block a user