From 9e78c8fc9ebe5aa3089b57e7e6e526d930e47815 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 10 Apr 2023 12:48:48 -0400 Subject: [PATCH] Test the processor's Substrate signer --- processor/src/tests/mod.rs | 2 + processor/src/tests/substrate_signer.rs | 139 ++++++++++++++++++ substrate/in-instructions/pallet/src/lib.rs | 3 + .../in-instructions/primitives/src/lib.rs | 4 +- 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 processor/src/tests/substrate_signer.rs diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 8a41fd52..100238d1 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -9,6 +9,8 @@ pub(crate) use scanner::test_scanner; mod signer; pub(crate) use signer::{sign, test_signer}; +mod substrate_signer; + mod wallet; pub(crate) use wallet::test_wallet; diff --git a/processor/src/tests/substrate_signer.rs b/processor/src/tests/substrate_signer.rs new file mode 100644 index 00000000..913594a5 --- /dev/null +++ b/processor/src/tests/substrate_signer.rs @@ -0,0 +1,139 @@ +use std::{ + time::{Duration, SystemTime}, + collections::HashMap, +}; + +use rand_core::OsRng; + +use group::GroupEncoding; +use frost::{ + curve::Ristretto, + Participant, + dkg::tests::{key_gen, clone_without}, +}; + +use tokio::time::timeout; + +use scale::Encode; +use sp_application_crypto::{RuntimePublic, sr25519::Public}; + +use serai_client::{primitives::*, in_instructions::primitives::*}; + +use messages::{sign::SignId, coordinator::*}; +use crate::{ + substrate_signer::{SubstrateSignerEvent, SubstrateSigner}, + tests::util::db::MemDb, +}; + +#[tokio::test] +async fn test_substrate_signer() { + let mut keys = key_gen::<_, Ristretto>(&mut OsRng); + + let participant_one = Participant::new(1).unwrap(); + + let block = BlockHash([0xaa; 32]); + let actual_id = + SignId { key: keys[&participant_one].group_key().to_bytes().to_vec(), id: block.0, attempt: 0 }; + + let batch = Batch { + network: MONERO_NET_ID, + id: 5, + block, + instructions: vec![ + InInstructionWithBalance { + instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])), + balance: Balance { coin: BITCOIN, amount: Amount(1000) }, + }, + InInstructionWithBalance { + instruction: InInstruction::Call(ApplicationCall { + application: Application::DEX, + data: Data::new(vec![0xcc; 128]).unwrap(), + }), + balance: Balance { coin: MONERO, amount: Amount(9999999999999999) }, + }, + ], + }; + + let signing_set = actual_id.signing_set(&keys[&participant_one].params()); + for these_keys in keys.values() { + assert_eq!(actual_id.signing_set(&these_keys.params()), signing_set); + } + + let start = SystemTime::now(); + let mut signers = HashMap::new(); + for i in 1 ..= keys.len() { + let i = Participant::new(u16::try_from(i).unwrap()).unwrap(); + let signer = SubstrateSigner::new(MemDb::new(), keys.remove(&i).unwrap()); + signer.sign(start, batch.clone()).await; + signers.insert(i, signer); + } + + let mut preprocesses = HashMap::new(); + for i in &signing_set { + if let Some(SubstrateSignerEvent::ProcessorMessage(ProcessorMessage::BatchPreprocess { + id, + preprocess, + })) = signers.get_mut(i).unwrap().events.recv().await + { + assert_eq!(id, actual_id); + preprocesses.insert(*i, preprocess); + } else { + panic!("didn't get preprocess back"); + } + } + + let mut shares = HashMap::new(); + for i in &signing_set { + signers[i] + .handle(CoordinatorMessage::BatchPreprocesses { + id: actual_id.clone(), + preprocesses: clone_without(&preprocesses, i), + }) + .await; + if let Some(SubstrateSignerEvent::ProcessorMessage(ProcessorMessage::BatchShare { + id, + share, + })) = signers.get_mut(i).unwrap().events.recv().await + { + assert_eq!(id, actual_id); + shares.insert(*i, share); + } else { + panic!("didn't get share back"); + } + } + + for i in &signing_set { + signers[i] + .handle(CoordinatorMessage::BatchShares { + id: actual_id.clone(), + shares: clone_without(&shares, i), + }) + .await; + + if let Some(SubstrateSignerEvent::SignedBatch(signed_batch)) = + signers.get_mut(i).unwrap().events.recv().await + { + assert_eq!(signed_batch.batch, batch); + assert!(Public::from_raw(actual_id.key.clone().try_into().unwrap()) + .verify(&batch.encode(), &signed_batch.signature)); + } else { + panic!("didn't get signed batch back"); + } + } + + // Make sure the signers not included didn't do anything + let mut excluded = (1 ..= signers.len()) + .map(|i| Participant::new(u16::try_from(i).unwrap()).unwrap()) + .collect::>(); + for i in signing_set { + excluded.remove(excluded.binary_search(&i).unwrap()); + } + for i in excluded { + assert!(timeout( + Duration::from_secs(5), + signers.get_mut(&Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap().events.recv() + ) + .await + .is_err()); + } +} diff --git a/substrate/in-instructions/pallet/src/lib.rs b/substrate/in-instructions/pallet/src/lib.rs index 3e5395c8..17ea1abe 100644 --- a/substrate/in-instructions/pallet/src/lib.rs +++ b/substrate/in-instructions/pallet/src/lib.rs @@ -83,6 +83,9 @@ pub mod pallet { block: batch.block, }); for (i, instruction) in batch.instructions.drain(..).enumerate() { + // TODO: Check this balance's coin belongs to this network + // If they don't, the validator set should be completely slashed, without question + if Self::execute(instruction).is_err() { Self::deposit_event(Event::InstructionFailure { network: batch.network, diff --git a/substrate/in-instructions/primitives/src/lib.rs b/substrate/in-instructions/primitives/src/lib.rs index 049158bd..ab05490e 100644 --- a/substrate/in-instructions/primitives/src/lib.rs +++ b/substrate/in-instructions/primitives/src/lib.rs @@ -31,8 +31,8 @@ pub enum Application { #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] pub struct ApplicationCall { - application: Application, - data: Data, + pub application: Application, + pub data: Data, } #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]