Smash out the signer

Abstract, to be done for the transactions, the batches, the cosigns, the slash
reports, everything. It has a minimal API itself, intending to be as clear as
possible.
This commit is contained in:
Luke Parker 2024-08-18 22:43:13 -04:00
parent 2f29c91d30
commit 1e8a9ec5bd
12 changed files with 442 additions and 39 deletions

View file

@ -40,6 +40,7 @@ jobs:
-p serai-message-queue \
-p serai-processor-messages \
-p serai-processor-key-gen \
-p serai-processor-frost-attempt-manager \
-p serai-processor \
-p tendermint-machine \
-p tributary-chain \

13
Cargo.lock generated
View file

@ -8599,6 +8599,19 @@ dependencies = [
"zeroize",
]
[[package]]
name = "serai-processor-frost-attempt-manager"
version = "0.1.0"
dependencies = [
"hex",
"log",
"modular-frost",
"rand_core",
"serai-db",
"serai-processor-messages",
"serai-validator-sets-primitives",
]
[[package]]
name = "serai-processor-key-gen"
version = "0.1.0"

View file

@ -71,6 +71,7 @@ members = [
"processor/messages",
"processor/key-gen",
"processor/frost-attempt-manager",
"processor",
"coordinator/tributary/tendermint",

View file

@ -47,6 +47,7 @@ exceptions = [
{ allow = ["AGPL-3.0"], name = "serai-processor-messages" },
{ allow = ["AGPL-3.0"], name = "serai-processor-key-gen" },
{ allow = ["AGPL-3.0"], name = "serai-processor-frost-attempt-manager" },
{ allow = ["AGPL-3.0"], name = "serai-processor" },
{ allow = ["AGPL-3.0"], name = "tributary-chain" },

View file

@ -1,6 +1,6 @@
AGPL-3.0-only license
Copyright (c) 2022-2023 Luke Parker
Copyright (c) 2022-2024 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as

View file

@ -0,0 +1,29 @@
[package]
name = "serai-processor-frost-attempt-manager"
version = "0.1.0"
description = "A manager of multiple attempts of FROST signing protocols"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/processor/frost-attempt-manager"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["frost", "multisig", "threshold"]
edition = "2021"
rust-version = "1.79"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
[dependencies]
rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] }
frost = { package = "modular-frost", path = "../../crypto/frost", version = "^0.8.1", default-features = false }
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
hex = { version = "0.4", default-features = false, features = ["std"] }
log = { version = "0.4", default-features = false, features = ["std"] }
serai-db = { path = "../../common/db" }
messages = { package = "serai-processor-messages", path = "../messages" }

View file

@ -0,0 +1,15 @@
AGPL-3.0-only license
Copyright (c) 2022-2024 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View file

@ -0,0 +1,6 @@
# FROST Attempt Manager
A library for helper structures to manage various attempts of a FROST signing
protocol.
This library is interacted with via the `serai-processor-messages::sign` API.

View file

@ -0,0 +1,251 @@
use std::collections::HashMap;
use rand_core::OsRng;
use frost::{
Participant, FrostError,
sign::{Writable, PreprocessMachine, SignMachine, SignatureMachine},
};
use serai_validator_sets_primitives::Session;
use messages::sign::{SignId, ProcessorMessage};
/// An instance of a signing protocol with re-attempts handled internally.
#[allow(clippy::type_complexity)]
pub(crate) struct SigningProtocol<M: Clone + PreprocessMachine> {
// The session this signing protocol is being conducted by.
session: Session,
// The `i` of our first, or starting, set of key shares we will be signing with.
// The key shares we sign with are expected to be continguous from this position.
start_i: Participant,
// The ID of this signing protocol.
id: [u8; 32],
// This accepts a vector of `root` machines in order to support signing with multiple key shares.
root: Vec<M>,
preprocessed: HashMap<u32, (Vec<M::SignMachine>, HashMap<Participant, Vec<u8>>)>,
// Here, we drop to a single machine as we only need one to complete the signature.
shared: HashMap<
u32,
(
<M::SignMachine as SignMachine<M::Signature>>::SignatureMachine,
HashMap<Participant, Vec<u8>>,
),
>,
}
impl<M: Clone + PreprocessMachine> SigningProtocol<M> {
/// Create a new signing protocol.
pub(crate) fn new(session: Session, start_i: Participant, id: [u8; 32], root: Vec<M>) -> Self {
log::info!("starting signing protocol {}", hex::encode(id));
Self {
session,
start_i,
id,
root,
preprocessed: HashMap::with_capacity(1),
shared: HashMap::with_capacity(1),
}
}
/// Start a new attempt of the signing protocol.
///
/// Returns the (serialized) preprocesses for the attempt.
pub(crate) fn attempt(&mut self, attempt: u32) -> Vec<ProcessorMessage> {
/*
We'd get slashed as malicious if we:
1) Preprocessed
2) Rebooted
3) On reboot, preprocessed again, sending new preprocesses which would be deduplicated by
the message-queue
4) Got sent preprocesses
5) Sent a share based on our new preprocesses, yet with everyone else expecting it to be
based on our old preprocesses
We avoid this by saving to the DB we preprocessed before sending our preprocessed, and only
keeping our preprocesses for this instance of the processor. Accordingly, on reboot, we will
flag the prior preprocess and not send new preprocesses.
We also won't send the share we were supposed to, unfortunately, yet caching/reloading the
preprocess has enough safety issues it isn't worth the headache.
*/
// TODO
log::debug!("attemting a new instance of signing protocol {}", hex::encode(self.id));
let mut our_preprocesses = HashMap::with_capacity(self.root.len());
let mut preprocessed = Vec::with_capacity(self.root.len());
let mut preprocesses = Vec::with_capacity(self.root.len());
for (i, machine) in self.root.iter().enumerate() {
let (machine, preprocess) = machine.clone().preprocess(&mut OsRng);
preprocessed.push(machine);
let mut this_preprocess = Vec::with_capacity(64);
preprocess.write(&mut this_preprocess).unwrap();
our_preprocesses.insert(
Participant::new(
u16::from(self.start_i) + u16::try_from(i).expect("signing with 2**16 machines"),
)
.expect("start_i + i exceeded the valid indexes for a Participant"),
this_preprocess.clone(),
);
preprocesses.push(this_preprocess);
}
assert!(self.preprocessed.insert(attempt, (preprocessed, our_preprocesses)).is_none());
vec![ProcessorMessage::Preprocesses {
id: SignId { session: self.session, id: self.id, attempt },
preprocesses,
}]
}
/// Handle preprocesses for the signing protocol.
///
/// Returns the (serialized) shares for the attempt.
pub(crate) fn preprocesses(
&mut self,
attempt: u32,
serialized_preprocesses: HashMap<Participant, Vec<u8>>,
) -> Vec<ProcessorMessage> {
log::debug!("handling preprocesses for signing protocol {}", hex::encode(self.id));
let Some((machines, our_serialized_preprocesses)) = self.preprocessed.remove(&attempt) else {
return vec![];
};
let mut msgs = Vec::with_capacity(1);
let mut preprocesses =
HashMap::with_capacity(serialized_preprocesses.len() + our_serialized_preprocesses.len());
for (i, serialized_preprocess) in
serialized_preprocesses.into_iter().chain(our_serialized_preprocesses)
{
let mut serialized_preprocess = serialized_preprocess.as_slice();
let Ok(preprocess) = machines[0].read_preprocess(&mut serialized_preprocess) else {
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
continue;
};
if !serialized_preprocess.is_empty() {
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
continue;
}
preprocesses.insert(i, preprocess);
}
// We throw out our preprocessed machines here, despite the fact they haven't been invalidated
// We could reuse them with a new set of valid preprocesses
// https://github.com/serai-dex/serai/issues/588
if !msgs.is_empty() {
return msgs;
}
let mut our_shares = HashMap::with_capacity(self.root.len());
let mut shared = Vec::with_capacity(machines.len());
let mut shares = Vec::with_capacity(machines.len());
for (i, machine) in machines.into_iter().enumerate() {
let i = Participant::new(
u16::from(self.start_i) + u16::try_from(i).expect("signing with 2**16 machines"),
)
.expect("start_i + i exceeded the valid indexes for a Participant");
let mut preprocesses = preprocesses.clone();
assert!(preprocesses.remove(&i).is_some());
// TODO: Replace this with `()`, which requires making the message type part of the trait
let (machine, share) = match machine.sign(preprocesses, &[]) {
Ok((machine, share)) => (machine, share),
Err(e) => match e {
FrostError::InternalError(_) |
FrostError::InvalidParticipant(_, _) |
FrostError::InvalidSigningSet(_) |
FrostError::InvalidParticipantQuantity(_, _) |
FrostError::DuplicatedParticipant(_) |
FrostError::MissingParticipant(_) |
FrostError::InvalidShare(_) => {
panic!("FROST had an error which shouldn't be reachable: {e:?}");
}
FrostError::InvalidPreprocess(i) => {
msgs
.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
return msgs;
}
},
};
shared.push(machine);
let mut this_share = Vec::with_capacity(32);
share.write(&mut this_share).unwrap();
our_shares.insert(i, this_share.clone());
shares.push(this_share);
}
assert!(self.shared.insert(attempt, (shared.swap_remove(0), our_shares)).is_none());
log::debug!(
"successfully handled preprocesses for signing protocol {}, sending shares",
hex::encode(self.id)
);
msgs.push(ProcessorMessage::Shares {
id: SignId { session: self.session, id: self.id, attempt },
shares,
});
msgs
}
/// Process shares for the signing protocol.
///
/// Returns the signature produced by the protocol.
pub(crate) fn shares(
&mut self,
attempt: u32,
serialized_shares: HashMap<Participant, Vec<u8>>,
) -> Result<M::Signature, Vec<ProcessorMessage>> {
log::debug!("handling shares for signing protocol {}", hex::encode(self.id));
let Some((machine, our_serialized_shares)) = self.shared.remove(&attempt) else { Err(vec![])? };
let mut msgs = Vec::with_capacity(1);
let mut shares = HashMap::with_capacity(serialized_shares.len() + our_serialized_shares.len());
for (i, serialized_share) in our_serialized_shares.into_iter().chain(serialized_shares) {
let mut serialized_share = serialized_share.as_slice();
let Ok(share) = machine.read_share(&mut serialized_share) else {
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
continue;
};
if !serialized_share.is_empty() {
msgs.push(ProcessorMessage::InvalidParticipant { session: self.session, participant: i });
continue;
}
shares.insert(i, share);
}
if !msgs.is_empty() {
Err(msgs)?;
}
assert!(shares.remove(&self.start_i).is_some());
let signature = match machine.complete(shares) {
Ok(signature) => signature,
Err(e) => match e {
FrostError::InternalError(_) |
FrostError::InvalidParticipant(_, _) |
FrostError::InvalidSigningSet(_) |
FrostError::InvalidParticipantQuantity(_, _) |
FrostError::DuplicatedParticipant(_) |
FrostError::MissingParticipant(_) |
FrostError::InvalidPreprocess(_) => {
panic!("FROST had an error which shouldn't be reachable: {e:?}");
}
FrostError::InvalidShare(i) => {
Err(vec![ProcessorMessage::InvalidParticipant { session: self.session, participant: i }])?
}
},
};
log::info!("finished signing for protocol {}", hex::encode(self.id));
Ok(signature)
}
}

View file

@ -0,0 +1,92 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
use std::collections::HashMap;
use frost::{Participant, sign::PreprocessMachine};
use serai_validator_sets_primitives::Session;
use messages::sign::{ProcessorMessage, CoordinatorMessage};
mod individual;
use individual::SigningProtocol;
/// A response to handling a message from the coordinator.
pub enum Response<M: PreprocessMachine> {
/// Messages to send to the coordinator.
Messages(Vec<ProcessorMessage>),
/// A produced signature.
Signature(M::Signature),
}
/// A manager of attempts for a variety of signing protocols.
pub struct AttemptManager<M: Clone + PreprocessMachine> {
session: Session,
start_i: Participant,
active: HashMap<[u8; 32], SigningProtocol<M>>,
}
impl<M: Clone + PreprocessMachine> AttemptManager<M> {
/// Create a new attempt manager.
pub fn new(session: Session, start_i: Participant) -> Self {
AttemptManager { session, start_i, active: HashMap::new() }
}
/// Register a signing protocol to attempt.
pub fn register(&mut self, id: [u8; 32], machines: Vec<M>) {
self.active.insert(id, SigningProtocol::new(self.session, self.start_i, id, machines));
}
/// Retire a signing protocol.
///
/// This frees all memory used for it and means no further messages will be handled for it.
/// This does not stop the protocol from being re-registered and further worked on (with
/// undefined behavior) then. The higher-level context must never call `register` again with this
/// ID.
// TODO: Also have the DB for this SigningProtocol cleaned up here.
pub fn retire(&mut self, id: [u8; 32]) {
log::info!("retiring signing protocol {}", hex::encode(id));
self.active.remove(&id);
}
/// Handle a message for a signing protocol.
pub fn handle(&mut self, msg: CoordinatorMessage) -> Response<M> {
match msg {
CoordinatorMessage::Preprocesses { id, preprocesses } => {
let Some(protocol) = self.active.get_mut(&id.id) else {
log::trace!(
"handling preprocesses for signing protocol {}, which we're not actively running",
hex::encode(id.id)
);
return Response::Messages(vec![]);
};
Response::Messages(protocol.preprocesses(id.attempt, preprocesses))
}
CoordinatorMessage::Shares { id, shares } => {
let Some(protocol) = self.active.get_mut(&id.id) else {
log::trace!(
"handling shares for signing protocol {}, which we're not actively running",
hex::encode(id.id)
);
return Response::Messages(vec![]);
};
match protocol.shares(id.attempt, shares) {
Ok(signature) => Response::Signature(signature),
Err(messages) => Response::Messages(messages),
}
}
CoordinatorMessage::Reattempt { id } => {
let Some(protocol) = self.active.get_mut(&id.id) else {
log::trace!(
"reattempting signing protocol {}, which we're not actively running",
hex::encode(id.id)
);
return Response::Messages(vec![]);
};
Response::Messages(protocol.attempt(id.attempt))
}
}
}
}

View file

@ -17,8 +17,6 @@ use ciphersuite::{
};
use dkg::{Participant, ThresholdKeys, evrf::*};
use log::info;
use serai_validator_sets_primitives::Session;
use messages::key_gen::*;
@ -184,7 +182,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
match msg {
CoordinatorMessage::GenerateKey { session, threshold, evrf_public_keys } => {
info!("Generating new key. Session: {session:?}");
log::info!("Generating new key. Session: {session:?}");
// Unzip the vector of eVRF keys
let substrate_evrf_public_keys =
@ -260,7 +258,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
}
CoordinatorMessage::Participation { session, participant, participation } => {
info!("received participation from {:?} for {:?}", participant, session);
log::info!("received participation from {:?} for {:?}", participant, session);
let Params { t: threshold, n, substrate_evrf_public_keys, network_evrf_public_keys } =
KeyGenDb::<P>::params(txn, session).unwrap();
@ -295,7 +293,7 @@ impl<P: KeyGenParams, D: Db> KeyGen<P, D> {
// participations and continue. We solely have to verify them, as to identify malicious
// participants and prevent DoSs, before returning
if self.key_shares(session).is_some() {
info!("already finished generating a key for {:?}", session);
log::info!("already finished generating a key for {:?}", session);
match EvrfDkg::<Ristretto>::verify(
&mut OsRng,

View file

@ -22,7 +22,6 @@ pub mod key_gen {
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum CoordinatorMessage {
// Instructs the Processor to begin the key generation process.
// TODO: Should this be moved under Substrate?
GenerateKey { session: Session, threshold: u16, evrf_public_keys: Vec<([u8; 32], Vec<u8>)> },
// Received participations for the specified key generation protocol.
Participation { session: Session, participant: Participant, participation: Vec<u8> },
@ -93,6 +92,8 @@ pub mod sign {
pub attempt: u32,
}
// TODO: Make this generic to the ID once we introduce topics into the message-queue and remove
// the global ProcessorMessage/CoordinatorMessage
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum CoordinatorMessage {
// Received preprocesses for the specified signing protocol.
@ -101,8 +102,10 @@ pub mod sign {
Shares { id: SignId, shares: HashMap<Participant, Vec<u8>> },
// Re-attempt a signing protocol.
Reattempt { id: SignId },
/* TODO
// Completed a signing protocol already.
Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
*/
}
impl CoordinatorMessage {
@ -114,8 +117,8 @@ pub mod sign {
match self {
CoordinatorMessage::Preprocesses { id, .. } |
CoordinatorMessage::Shares { id, .. } |
CoordinatorMessage::Reattempt { id } => id.session,
CoordinatorMessage::Completed { session, .. } => *session,
CoordinatorMessage::Reattempt { id, .. } => id.session,
// TODO CoordinatorMessage::Completed { session, .. } => *session,
}
}
}
@ -123,13 +126,13 @@ pub mod sign {
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum ProcessorMessage {
// Participant sent an invalid message during the sign protocol.
InvalidParticipant { id: SignId, participant: Participant },
// Created preprocess for the specified signing protocol.
Preprocess { id: SignId, preprocesses: Vec<Vec<u8>> },
// Signed share for the specified signing protocol.
Share { id: SignId, shares: Vec<Vec<u8>> },
InvalidParticipant { session: Session, participant: Participant },
// Created preprocesses for the specified signing protocol.
Preprocesses { id: SignId, preprocesses: Vec<Vec<u8>> },
// Signed shares for the specified signing protocol.
Shares { id: SignId, shares: Vec<Vec<u8>> },
// Completed a signing protocol already.
Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
// TODO Completed { session: Session, id: [u8; 32], tx: Vec<u8> },
}
}
@ -165,10 +168,6 @@ pub mod coordinator {
pub enum CoordinatorMessage {
CosignSubstrateBlock { id: SubstrateSignId, block_number: u64 },
SignSlashReport { id: SubstrateSignId, report: Vec<([u8; 32], u32)> },
SubstratePreprocesses { id: SubstrateSignId, preprocesses: HashMap<Participant, [u8; 64]> },
SubstrateShares { id: SubstrateSignId, shares: HashMap<Participant, [u8; 32]> },
// Re-attempt a batch signing protocol.
BatchReattempt { id: SubstrateSignId },
}
impl CoordinatorMessage {
@ -192,9 +191,9 @@ pub mod coordinator {
SubstrateBlockAck { block: u64, plans: Vec<PlanMeta> },
InvalidParticipant { id: SubstrateSignId, participant: Participant },
CosignPreprocess { id: SubstrateSignId, preprocesses: Vec<[u8; 64]> },
// TODO: Remove BatchPreprocess? Why does this take a BlockHash here and not in its
// SubstrateSignId?
BatchPreprocess { id: SubstrateSignId, block: BlockHash, preprocesses: Vec<[u8; 64]> },
SlashReportPreprocess { id: SubstrateSignId, preprocesses: Vec<[u8; 64]> },
SubstrateShare { id: SubstrateSignId, shares: Vec<[u8; 32]> },
// TODO: Make these signatures [u8; 64]?
CosignedBlock { block_number: u64, block: [u8; 32], signature: Vec<u8> },
SignedSlashReport { session: Session, signature: Vec<u8> },
@ -327,19 +326,19 @@ impl CoordinatorMessage {
}
CoordinatorMessage::Sign(msg) => {
let (sub, id) = match msg {
// Unique since SignId includes a hash of the network, and specific transaction info
sign::CoordinatorMessage::Preprocesses { id, .. } => (0, id.encode()),
sign::CoordinatorMessage::Shares { id, .. } => (1, id.encode()),
sign::CoordinatorMessage::Reattempt { id } => (2, id.encode()),
// Unique since SignId
sign::CoordinatorMessage::Preprocesses { id, .. } => (0, id),
sign::CoordinatorMessage::Shares { id, .. } => (1, id),
sign::CoordinatorMessage::Reattempt { id, .. } => (2, id),
// The coordinator should report all reported completions to the processor
// Accordingly, the intent is a combination of plan ID and actual TX
// While transaction alone may suffice, that doesn't cover cross-chain TX ID conflicts,
// which are possible
sign::CoordinatorMessage::Completed { id, tx, .. } => (3, (id, tx).encode()),
// TODO sign::CoordinatorMessage::Completed { id, tx, .. } => (3, (id, tx).encode()),
};
let mut res = vec![COORDINATOR_UID, TYPE_SIGN_UID, sub];
res.extend(&id);
res.extend(id.encode());
res
}
CoordinatorMessage::Coordinator(msg) => {
@ -349,10 +348,6 @@ impl CoordinatorMessage {
// Unique since there's only one of these per session/attempt, and ID is inclusive to
// both
coordinator::CoordinatorMessage::SignSlashReport { id, .. } => (1, id.encode()),
// Unique since this embeds the batch ID (including its network) and attempt
coordinator::CoordinatorMessage::SubstratePreprocesses { id, .. } => (2, id.encode()),
coordinator::CoordinatorMessage::SubstrateShares { id, .. } => (3, id.encode()),
coordinator::CoordinatorMessage::BatchReattempt { id, .. } => (4, id.encode()),
};
let mut res = vec![COORDINATOR_UID, TYPE_COORDINATOR_UID, sub];
@ -404,12 +399,15 @@ impl ProcessorMessage {
}
ProcessorMessage::Sign(msg) => {
let (sub, id) = match msg {
// Unique since we'll only fatally slash a a participant once
sign::ProcessorMessage::InvalidParticipant { session, participant } => {
(0, (session, u16::from(*participant)).encode())
}
// Unique since SignId
sign::ProcessorMessage::InvalidParticipant { id, .. } => (0, id.encode()),
sign::ProcessorMessage::Preprocess { id, .. } => (1, id.encode()),
sign::ProcessorMessage::Share { id, .. } => (2, id.encode()),
sign::ProcessorMessage::Preprocesses { id, .. } => (1, id.encode()),
sign::ProcessorMessage::Shares { id, .. } => (2, id.encode()),
// Unique since a processor will only sign a TX once
sign::ProcessorMessage::Completed { id, .. } => (3, id.to_vec()),
// TODO sign::ProcessorMessage::Completed { id, .. } => (3, id.to_vec()),
};
let mut res = vec![PROCESSOR_UID, TYPE_SIGN_UID, sub];
@ -423,11 +421,9 @@ impl ProcessorMessage {
coordinator::ProcessorMessage::InvalidParticipant { id, .. } => (1, id.encode()),
coordinator::ProcessorMessage::CosignPreprocess { id, .. } => (2, id.encode()),
coordinator::ProcessorMessage::BatchPreprocess { id, .. } => (3, id.encode()),
coordinator::ProcessorMessage::SlashReportPreprocess { id, .. } => (4, id.encode()),
coordinator::ProcessorMessage::SubstrateShare { id, .. } => (5, id.encode()),
// Unique since only one instance of a signature matters
coordinator::ProcessorMessage::CosignedBlock { block, .. } => (6, block.encode()),
coordinator::ProcessorMessage::SignedSlashReport { .. } => (7, vec![]),
coordinator::ProcessorMessage::CosignedBlock { block, .. } => (4, block.encode()),
coordinator::ProcessorMessage::SignedSlashReport { .. } => (5, vec![]),
};
let mut res = vec![PROCESSOR_UID, TYPE_COORDINATOR_UID, sub];