diff --git a/Cargo.lock b/Cargo.lock index c8d00271..4d6bf218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6906,6 +6906,7 @@ dependencies = [ "log", "parity-scale-codec", "serai-client", + "serai-cosign", "serai-db", "serai-env", "serai-message-queue", @@ -7097,6 +7098,7 @@ dependencies = [ "modular-frost", "parity-scale-codec", "rand_core", + "serai-cosign", "serai-db", "serai-in-instructions-primitives", "serai-primitives", diff --git a/coordinator/cosign/src/lib.rs b/coordinator/cosign/src/lib.rs index 517dd7f3..3d476c3d 100644 --- a/coordinator/cosign/src/lib.rs +++ b/coordinator/cosign/src/lib.rs @@ -104,6 +104,24 @@ pub struct Cosign { pub cosigner: NetworkId, } +impl CosignIntent { + /// Convert this into a `Cosign`. + pub fn into_cosign(self, cosigner: NetworkId) -> Cosign { + let CosignIntent { global_session, block_number, block_hash, notable: _ } = self; + Cosign { global_session, block_number, block_hash, cosigner } + } +} + +impl Cosign { + /// The message to sign to sign this cosign. + /// + /// This must be signed with schnorrkel, the context set to `COSIGN_CONTEXT`. + pub fn signature_message(&self) -> Vec<u8> { + // We use a schnorrkel context to domain-separate this + self.encode() + } +} + /// A signed cosign. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct SignedCosign { @@ -118,7 +136,7 @@ impl SignedCosign { let Ok(signer) = schnorrkel::PublicKey::from_bytes(&signer.0) else { return false }; let Ok(signature) = schnorrkel::Signature::from_bytes(&self.signature) else { return false }; - signer.verify_simple(COSIGN_CONTEXT, &self.cosign.encode(), &signature).is_ok() + signer.verify_simple(COSIGN_CONTEXT, &self.cosign.signature_message(), &signature).is_ok() } } diff --git a/coordinator/tributary/src/transaction.rs b/coordinator/tributary/src/transaction.rs index b302f8d7..d05bf3c2 100644 --- a/coordinator/tributary/src/transaction.rs +++ b/coordinator/tributary/src/transaction.rs @@ -365,10 +365,12 @@ impl Transaction { Transaction::DkgConfirmationPreprocess { ref mut signed, .. } => signed, Transaction::DkgConfirmationShare { ref mut signed, .. } => signed, - Transaction::Cosign { .. } => panic!("signing CosignSubstrateBlock"), - Transaction::Cosigned { .. } => panic!("signing Cosigned"), - Transaction::SubstrateBlock { .. } => panic!("signing SubstrateBlock"), - Transaction::Batch { .. } => panic!("signing Batch"), + Transaction::Cosign { .. } => panic!("signing Cosign transaction (provided)"), + Transaction::Cosigned { .. } => panic!("signing Cosigned transaction (provided)"), + Transaction::SubstrateBlock { .. } => { + panic!("signing SubstrateBlock transaction (provided)") + } + Transaction::Batch { .. } => panic!("signing Batch transaction (provided)"), Transaction::Sign { ref mut signed, .. } => signed, diff --git a/processor/bin/Cargo.toml b/processor/bin/Cargo.toml index 52ebaeb9..164036a0 100644 --- a/processor/bin/Cargo.toml +++ b/processor/bin/Cargo.toml @@ -28,6 +28,7 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] } serai-client = { path = "../../substrate/client", default-features = false } +serai-cosign = { path = "../../coordinator/cosign" } log = { version = "0.4", default-features = false, features = ["std"] } env_logger = { version = "0.10", default-features = false, features = ["humantime"] } diff --git a/processor/bin/src/coordinator.rs b/processor/bin/src/coordinator.rs index 591826bd..62eb1097 100644 --- a/processor/bin/src/coordinator.rs +++ b/processor/bin/src/coordinator.rs @@ -3,12 +3,14 @@ use std::sync::{LazyLock, Arc, Mutex}; use tokio::sync::mpsc; -use scale::Encode; use serai_client::{ - primitives::Signature, validator_sets::primitives::Session, + primitives::Signature, + validator_sets::primitives::{Session, SlashReport}, in_instructions::primitives::SignedBatch, }; +use serai_cosign::SignedCosign; + use serai_db::{Get, DbTxn, Db, create_db, db_channel}; use scanner::ScannerFeed; @@ -181,17 +183,11 @@ impl signers::Coordinator for CoordinatorSend { fn publish_cosign( &mut self, - block_number: u64, - block: [u8; 32], - signature: Signature, + cosign: SignedCosign, ) -> impl Send + Future<Output = Result<(), Self::EphemeralError>> { async move { self.send(&messages::ProcessorMessage::Coordinator( - messages::coordinator::ProcessorMessage::CosignedBlock { - block_number, - block, - signature: signature.encode(), - }, + messages::coordinator::ProcessorMessage::CosignedBlock { cosign }, )); Ok(()) } @@ -212,13 +208,15 @@ impl signers::Coordinator for CoordinatorSend { fn publish_slash_report_signature( &mut self, session: Session, + slash_report: SlashReport, signature: Signature, ) -> impl Send + Future<Output = Result<(), Self::EphemeralError>> { async move { self.send(&messages::ProcessorMessage::Coordinator( messages::coordinator::ProcessorMessage::SignedSlashReport { session, - signature: signature.encode(), + slash_report, + signature: signature.0, }, )); Ok(()) diff --git a/processor/bin/src/lib.rs b/processor/bin/src/lib.rs index 119c4f40..5109dcbc 100644 --- a/processor/bin/src/lib.rs +++ b/processor/bin/src/lib.rs @@ -221,20 +221,16 @@ pub async fn main_loop< signers.queue_message(txn, &msg) } messages::CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::CosignSubstrateBlock { - session, - block_number, - block, - }, + messages::coordinator::CoordinatorMessage::CosignSubstrateBlock { session, cosign }, ) => { let txn = txn.take().unwrap(); - signers.cosign_block(txn, session, block_number, block) + signers.cosign_block(txn, session, &cosign) } messages::CoordinatorMessage::Coordinator( - messages::coordinator::CoordinatorMessage::SignSlashReport { session, report }, + messages::coordinator::CoordinatorMessage::SignSlashReport { session, slash_report }, ) => { let txn = txn.take().unwrap(); - signers.sign_slash_report(txn, session, &report) + signers.sign_slash_report(txn, session, &slash_report) } messages::CoordinatorMessage::Substrate(msg) => match msg { diff --git a/processor/messages/src/lib.rs b/processor/messages/src/lib.rs index b8f496ab..7101fdc2 100644 --- a/processor/messages/src/lib.rs +++ b/processor/messages/src/lib.rs @@ -11,7 +11,7 @@ use validator_sets_primitives::{Session, KeyPair, SlashReport}; use coins_primitives::OutInstructionWithBalance; use in_instructions_primitives::SignedBatch; -use serai_cosign::{CosignIntent, SignedCosign}; +use serai_cosign::{Cosign, SignedCosign}; #[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)] pub struct SubstrateContext { @@ -166,7 +166,7 @@ pub mod coordinator { /// Cosign the specified Substrate block. /// /// This is sent by the Coordinator's Tributary scanner. - CosignSubstrateBlock { session: Session, intent: CosignIntent }, + CosignSubstrateBlock { session: Session, cosign: Cosign }, /// Sign the slash report for this session. /// /// This is sent by the Coordinator's Tributary scanner. @@ -322,8 +322,8 @@ impl CoordinatorMessage { CoordinatorMessage::Coordinator(msg) => { let (sub, id) = match msg { // We only cosign a block once, and Reattempt is a separate message - coordinator::CoordinatorMessage::CosignSubstrateBlock { intent, .. } => { - (0, intent.block_number.encode()) + coordinator::CoordinatorMessage::CosignSubstrateBlock { cosign, .. } => { + (0, cosign.block_number.encode()) } // We only sign one slash report, and Reattempt is a separate message coordinator::CoordinatorMessage::SignSlashReport { session, .. } => (1, session.encode()), diff --git a/processor/signers/Cargo.toml b/processor/signers/Cargo.toml index ddd295d3..ecf588d4 100644 --- a/processor/signers/Cargo.toml +++ b/processor/signers/Cargo.toml @@ -40,6 +40,7 @@ serai-db = { path = "../../common/db" } log = { version = "0.4", default-features = false, features = ["std"] } tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "sync", "time", "macros"] } +serai-cosign = { path = "../../coordinator/cosign" } messages = { package = "serai-processor-messages", path = "../messages" } primitives = { package = "serai-processor-primitives", path = "../primitives" } scanner = { package = "serai-processor-scanner", path = "../scanner" } diff --git a/processor/signers/src/batch/mod.rs b/processor/signers/src/batch/mod.rs index 2c4fd1f5..f38666a8 100644 --- a/processor/signers/src/batch/mod.rs +++ b/processor/signers/src/batch/mod.rs @@ -69,7 +69,12 @@ impl<D: Db, E: GroupEncoding> BatchSignerTask<D, E> { let mut machines = Vec::with_capacity(keys.len()); for keys in &keys { - machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch))); + // TODO: Fetch the context for this from a constant instead of re-defining it + machines.push(WrappedSchnorrkelMachine::new( + keys.clone(), + b"substrate", + batch_message(&batch), + )); } attempt_manager.register(VariantSignId::Batch(id), machines); } @@ -106,7 +111,12 @@ impl<D: Db, E: Send + GroupEncoding> ContinuallyRan for BatchSignerTask<D, E> { let mut machines = Vec::with_capacity(self.keys.len()); for keys in &self.keys { - machines.push(WrappedSchnorrkelMachine::new(keys.clone(), batch_message(&batch))); + // TODO: Also fetch the constant here + machines.push(WrappedSchnorrkelMachine::new( + keys.clone(), + b"substrate", + batch_message(&batch), + )); } for msg in self.attempt_manager.register(VariantSignId::Batch(batch_hash), machines) { BatchSignerToCoordinatorMessages::send(&mut txn, self.session, &msg); diff --git a/processor/signers/src/coordinator/mod.rs b/processor/signers/src/coordinator/mod.rs index 003c14cd..0fd10822 100644 --- a/processor/signers/src/coordinator/mod.rs +++ b/processor/signers/src/coordinator/mod.rs @@ -1,6 +1,7 @@ use core::future::Future; -use scale::Decode; +use serai_primitives::Signature; + use serai_db::{DbTxn, Db}; use primitives::task::ContinuallyRan; @@ -99,17 +100,11 @@ impl<D: Db, C: Coordinator> ContinuallyRan for CoordinatorTask<D, C> { // Publish the cosigns from this session { let mut txn = self.db.txn(); - while let Some(((block_number, block_id), signature)) = - Cosign::try_recv(&mut txn, session) - { + while let Some(signed_cosign) = Cosign::try_recv(&mut txn, session) { iterated = true; self .coordinator - .publish_cosign( - block_number, - block_id, - <_>::decode(&mut signature.as_slice()).unwrap(), - ) + .publish_cosign(signed_cosign) .await .map_err(|e| format!("couldn't publish Cosign: {e:?}"))?; } @@ -119,15 +114,12 @@ impl<D: Db, C: Coordinator> ContinuallyRan for CoordinatorTask<D, C> { // If this session signed its slash report, publish its signature { let mut txn = self.db.txn(); - if let Some(slash_report_signature) = SlashReportSignature::try_recv(&mut txn, session) { + if let Some((slash_report, signature)) = SignedSlashReport::try_recv(&mut txn, session) { iterated = true; self .coordinator - .publish_slash_report_signature( - session, - <_>::decode(&mut slash_report_signature.as_slice()).unwrap(), - ) + .publish_slash_report_signature(session, slash_report, Signature(signature)) .await .map_err(|e| { format!("couldn't send slash report signature to the coordinator: {e:?}") diff --git a/processor/signers/src/cosign/mod.rs b/processor/signers/src/cosign/mod.rs index dc5de6cd..ddf6c490 100644 --- a/processor/signers/src/cosign/mod.rs +++ b/processor/signers/src/cosign/mod.rs @@ -9,7 +9,8 @@ use serai_validator_sets_primitives::Session; use serai_db::{DbTxn, Db}; -use messages::{sign::VariantSignId, coordinator::cosign_block_msg}; +use serai_cosign::{COSIGN_CONTEXT, Cosign as CosignStruct, SignedCosign}; +use messages::sign::VariantSignId; use primitives::task::{DoesNotError, ContinuallyRan}; @@ -34,7 +35,7 @@ pub(crate) struct CosignerTask<D: Db> { session: Session, keys: Vec<ThresholdKeys<Ristretto>>, - current_cosign: Option<(u64, [u8; 32])>, + current_cosign: Option<CosignStruct>, attempt_manager: AttemptManager<D, WrappedSchnorrkelMachine>, } @@ -62,26 +63,34 @@ impl<D: Db> ContinuallyRan for CosignerTask<D> { let mut txn = self.db.txn(); if let Some(cosign) = ToCosign::get(&txn, self.session) { // If this wasn't already signed for... - if LatestCosigned::get(&txn, self.session) < Some(cosign.0) { + if LatestCosigned::get(&txn, self.session) < Some(cosign.block_number) { // If this isn't the cosign we're currently working on, meaning it's fresh - if self.current_cosign != Some(cosign) { + if self.current_cosign.as_ref() != Some(&cosign) { // Retire the current cosign - if let Some(current_cosign) = self.current_cosign { - assert!(current_cosign.0 < cosign.0); - self.attempt_manager.retire(&mut txn, VariantSignId::Cosign(current_cosign.0)); + if let Some(current_cosign) = &self.current_cosign { + assert!(current_cosign.block_number < cosign.block_number); + self + .attempt_manager + .retire(&mut txn, VariantSignId::Cosign(current_cosign.block_number)); } // Set the cosign being worked on - self.current_cosign = Some(cosign); + self.current_cosign = Some(cosign.clone()); let mut machines = Vec::with_capacity(self.keys.len()); { - let message = cosign_block_msg(cosign.0, cosign.1); + let message = cosign.signature_message(); for keys in &self.keys { - machines.push(WrappedSchnorrkelMachine::new(keys.clone(), message.clone())); + machines.push(WrappedSchnorrkelMachine::new( + keys.clone(), + COSIGN_CONTEXT, + message.clone(), + )); } } - for msg in self.attempt_manager.register(VariantSignId::Cosign(cosign.0), machines) { + for msg in + self.attempt_manager.register(VariantSignId::Cosign(cosign.block_number), machines) + { CosignerToCoordinatorMessages::send(&mut txn, self.session, &msg); } @@ -109,12 +118,19 @@ impl<D: Db> ContinuallyRan for CosignerTask<D> { let VariantSignId::Cosign(block_number) = id else { panic!("CosignerTask signed a non-Cosign") }; - assert_eq!(Some(block_number), self.current_cosign.map(|cosign| cosign.0)); + assert_eq!( + Some(block_number), + self.current_cosign.as_ref().map(|cosign| cosign.block_number) + ); let cosign = self.current_cosign.take().unwrap(); - LatestCosigned::set(&mut txn, self.session, &cosign.0); + LatestCosigned::set(&mut txn, self.session, &cosign.block_number); + let cosign = SignedCosign { + cosign, + signature: Signature::from(signature).encode().try_into().unwrap(), + }; // Send the cosign - Cosign::send(&mut txn, self.session, &(cosign, Signature::from(signature).encode())); + Cosign::send(&mut txn, self.session, &cosign); } } diff --git a/processor/signers/src/db.rs b/processor/signers/src/db.rs index 2c13ddba..23862236 100644 --- a/processor/signers/src/db.rs +++ b/processor/signers/src/db.rs @@ -1,7 +1,9 @@ -use serai_validator_sets_primitives::{Session, Slash}; +use serai_validator_sets_primitives::{Session, SlashReport as SlashReportStruct}; use serai_db::{Get, DbTxn, create_db, db_channel}; +use serai_cosign::{Cosign as CosignStruct, SignedCosign}; + use messages::sign::{ProcessorMessage, CoordinatorMessage}; create_db! { @@ -11,16 +13,16 @@ create_db! { LatestRetiredSession: () -> Session, ToCleanup: () -> Vec<(Session, Vec<u8>)>, - ToCosign: (session: Session) -> (u64, [u8; 32]), + ToCosign: (session: Session) -> CosignStruct, } } db_channel! { SignersGlobal { - Cosign: (session: Session) -> ((u64, [u8; 32]), Vec<u8>), + Cosign: (session: Session) -> SignedCosign, - SlashReport: (session: Session) -> Vec<Slash>, - SlashReportSignature: (session: Session) -> Vec<u8>, + SlashReport: (session: Session) -> SlashReportStruct, + SignedSlashReport: (session: Session) -> (SlashReportStruct, [u8; 64]), /* TODO: Most of these are pointless? We drop all active signing sessions on reboot. It's diff --git a/processor/signers/src/lib.rs b/processor/signers/src/lib.rs index 116f7b9e..2f5a4a04 100644 --- a/processor/signers/src/lib.rs +++ b/processor/signers/src/lib.rs @@ -11,11 +11,13 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto}; use frost::dkg::{ThresholdCore, ThresholdKeys}; use serai_primitives::Signature; -use serai_validator_sets_primitives::{Session, Slash}; +use serai_validator_sets_primitives::{Session, SlashReport}; use serai_in_instructions_primitives::SignedBatch; use serai_db::{DbTxn, Db}; +use serai_cosign::{Cosign, SignedCosign}; + use messages::sign::{VariantSignId, ProcessorMessage, CoordinatorMessage}; use primitives::task::{Task, TaskHandle, ContinuallyRan}; @@ -59,9 +61,7 @@ pub trait Coordinator: 'static + Send + Sync { /// Publish a cosign. fn publish_cosign( &mut self, - block_number: u64, - block_id: [u8; 32], - signature: Signature, + signed_cosign: SignedCosign, ) -> impl Send + Future<Output = Result<(), Self::EphemeralError>>; /// Publish a `SignedBatch`. @@ -74,6 +74,7 @@ pub trait Coordinator: 'static + Send + Sync { fn publish_slash_report_signature( &mut self, session: Session, + slash_report: SlashReport, signature: Signature, ) -> impl Send + Future<Output = Result<(), Self::EphemeralError>>; } @@ -408,19 +409,13 @@ impl< /// Cosign a block. /// /// This is a cheap call and able to be done inline from a higher-level loop. - pub fn cosign_block( - &mut self, - mut txn: impl DbTxn, - session: Session, - block_number: u64, - block: [u8; 32], - ) { + pub fn cosign_block(&mut self, mut txn: impl DbTxn, session: Session, cosign: &Cosign) { // Don't cosign blocks with already retired keys if Some(session.0) <= db::LatestRetiredSession::get(&txn).map(|session| session.0) { return; } - db::ToCosign::set(&mut txn, session, &(block_number, block)); + db::ToCosign::set(&mut txn, session, cosign); txn.commit(); if let Some(tasks) = self.tasks.get(&session) { @@ -435,7 +430,7 @@ impl< &mut self, mut txn: impl DbTxn, session: Session, - slash_report: &Vec<Slash>, + slash_report: &SlashReport, ) { // Don't sign slash reports with already retired keys if Some(session.0) <= db::LatestRetiredSession::get(&txn).map(|session| session.0) { diff --git a/processor/signers/src/slash_report.rs b/processor/signers/src/slash_report.rs index a5d155ef..14437a74 100644 --- a/processor/signers/src/slash_report.rs +++ b/processor/signers/src/slash_report.rs @@ -3,11 +3,8 @@ use core::{marker::PhantomData, future::Future}; use ciphersuite::Ristretto; use frost::dkg::ThresholdKeys; -use scale::Encode; use serai_primitives::Signature; -use serai_validator_sets_primitives::{ - Session, ValidatorSet, SlashReport as SlashReportStruct, report_slashes_message, -}; +use serai_validator_sets_primitives::Session; use serai_db::{DbTxn, Db}; @@ -20,7 +17,7 @@ use frost_attempt_manager::*; use crate::{ db::{ - SlashReport, SlashReportSignature, CoordinatorToSlashReportSignerMessages, + SlashReport, SignedSlashReport, CoordinatorToSlashReportSignerMessages, SlashReportSignerToCoordinatorMessages, }, WrappedSchnorrkelMachine, @@ -72,12 +69,14 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SlashReportSignerTask<D, S> { let mut machines = Vec::with_capacity(self.keys.len()); { - let message = report_slashes_message( - &ValidatorSet { network: S::NETWORK, session: self.session }, - &SlashReportStruct(slash_report.try_into().unwrap()), - ); + let message = slash_report.report_slashes_message(); for keys in &self.keys { - machines.push(WrappedSchnorrkelMachine::new(keys.clone(), message.clone())); + // TODO: Fetch this constant from somewhere instead of inlining it + machines.push(WrappedSchnorrkelMachine::new( + keys.clone(), + b"substrate", + message.clone(), + )); } } let mut txn = self.db.txn(); @@ -105,12 +104,12 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SlashReportSignerTask<D, S> { Response::Signature { id, signature } => { assert_eq!(id, VariantSignId::SlashReport); // Drain the channel - SlashReport::try_recv(&mut txn, self.session).unwrap(); + let slash_report = SlashReport::try_recv(&mut txn, self.session).unwrap(); // Send the signature - SlashReportSignature::send( + SignedSlashReport::send( &mut txn, self.session, - &Signature::from(signature).encode(), + &(slash_report, Signature::from(signature).0), ); } } diff --git a/processor/signers/src/wrapped_schnorrkel.rs b/processor/signers/src/wrapped_schnorrkel.rs index d81eaa70..a84b8d43 100644 --- a/processor/signers/src/wrapped_schnorrkel.rs +++ b/processor/signers/src/wrapped_schnorrkel.rs @@ -16,10 +16,10 @@ use frost_schnorrkel::Schnorrkel; // This wraps a Schnorrkel sign machine into one with a preset message. #[derive(Clone)] -pub(crate) struct WrappedSchnorrkelMachine(ThresholdKeys<Ristretto>, Vec<u8>); +pub(crate) struct WrappedSchnorrkelMachine(ThresholdKeys<Ristretto>, &'static [u8], Vec<u8>); impl WrappedSchnorrkelMachine { - pub(crate) fn new(keys: ThresholdKeys<Ristretto>, msg: Vec<u8>) -> Self { - Self(keys, msg) + pub(crate) fn new(keys: ThresholdKeys<Ristretto>, context: &'static [u8], msg: Vec<u8>) -> Self { + Self(keys, context, msg) } } @@ -39,10 +39,10 @@ impl PreprocessMachine for WrappedSchnorrkelMachine { rng: &mut R, ) -> (Self::SignMachine, Preprocess<Ristretto, <Schnorrkel as Algorithm<Ristretto>>::Addendum>) { - let WrappedSchnorrkelMachine(keys, batch) = self; + let WrappedSchnorrkelMachine(keys, context, msg) = self; let (machine, preprocess) = - AlgorithmMachine::new(Schnorrkel::new(b"substrate"), keys).preprocess(rng); - (WrappedSchnorrkelSignMachine(machine, batch), preprocess) + AlgorithmMachine::new(Schnorrkel::new(context), keys).preprocess(rng); + (WrappedSchnorrkelSignMachine(machine, msg), preprocess) } } diff --git a/substrate/validator-sets/primitives/src/slash_points.rs b/substrate/validator-sets/primitives/src/slash_points.rs index d420157e..0cc72b2f 100644 --- a/substrate/validator-sets/primitives/src/slash_points.rs +++ b/substrate/validator-sets/primitives/src/slash_points.rs @@ -234,9 +234,12 @@ impl TryFrom<Vec<Slash>> for SlashReport { } } -// This is assumed binding to the ValidatorSet via the key signed with -pub fn report_slashes_message(slashes: &SlashReport) -> Vec<u8> { - (b"ValidatorSets-report_slashes", slashes).encode() +impl SlashReport { + /// The message to sign when publishing this SlashReport. + // This is assumed binding to the ValidatorSet via the key signed with + pub fn report_slashes_message(&self) -> Vec<u8> { + (b"ValidatorSets-report_slashes", &self.0).encode() + } } #[test]