Add Batch messages from processor, verify Batchs published on-chain

Renames Update to SignedBatch.

Checks Batch equality via a hash of the InInstructions. That prevents needing
to keep the Batch in node state or TX introspect.
This commit is contained in:
Luke Parker
2023-09-29 03:51:01 -04:00
parent 0be567ff69
commit 0eff3d9453
18 changed files with 281 additions and 80 deletions

View File

@@ -10,7 +10,7 @@ use messages::{sign::SignId, SubstrateContext};
use serai_client::{
primitives::{BlockHash, crypto::RuntimePublic, PublicKey, SeraiAddress, NetworkId},
in_instructions::primitives::{
InInstruction, InInstructionWithBalance, SignedBatch, batch_message,
InInstruction, InInstructionWithBalance, Batch, SignedBatch, batch_message,
},
};
@@ -19,6 +19,7 @@ use crate::{*, tests::*};
pub(crate) async fn recv_batch_preprocesses(
coordinators: &mut [Coordinator],
substrate_key: &[u8; 32],
batch: &Batch,
attempt: u32,
) -> (SignId, HashMap<Participant, Vec<u8>>) {
let mut id = None;
@@ -27,8 +28,18 @@ pub(crate) async fn recv_batch_preprocesses(
for (i, coordinator) in coordinators.iter_mut().enumerate() {
let i = Participant::new(u16::try_from(i).unwrap() + 1).unwrap();
let msg = coordinator.recv_message().await;
match msg {
if attempt == 0 {
match coordinator.recv_message().await {
messages::ProcessorMessage::Substrate(messages::substrate::ProcessorMessage::Batch {
batch: sent_batch,
}) => {
assert_eq!(&sent_batch, batch);
}
_ => panic!("processor didn't send batch"),
}
}
match coordinator.recv_message().await {
messages::ProcessorMessage::Coordinator(
messages::coordinator::ProcessorMessage::BatchPreprocess {
id: this_id,
@@ -122,9 +133,9 @@ pub(crate) async fn sign_batch(
if preprocesses.contains_key(&i) {
match coordinator.recv_message().await {
messages::ProcessorMessage::Substrate(messages::substrate::ProcessorMessage::Update {
batch: this_batch,
}) => {
messages::ProcessorMessage::Substrate(
messages::substrate::ProcessorMessage::SignedBatch { batch: this_batch },
) => {
if batch.is_none() {
assert!(PublicKey::from_raw(key)
.verify(&batch_message(&this_batch.batch), &this_batch.signature));
@@ -231,9 +242,23 @@ fn batch_test() {
// The scanner works on a 5s interval, so this leaves a few s for any processing/latency
tokio::time::sleep(Duration::from_secs(10)).await;
let expected_batch = Batch {
network,
id: i,
block: BlockHash(block_with_tx.unwrap()),
instructions: if let Some(instruction) = instruction {
vec![InInstructionWithBalance { instruction, balance: balance_sent }]
} else {
// This shouldn't have an instruction as we didn't add any data into the TX we sent
// Empty batches remain valuable as they let us achieve consensus on the block and spend
// contained outputs
vec![]
},
};
// Make sure the proceessors picked it up by checking they're trying to sign a batch for it
let (mut id, mut preprocesses) =
recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, 0).await;
recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, &expected_batch, 0).await;
// Trigger a random amount of re-attempts
for attempt in 1 ..= u32::try_from(OsRng.next_u64() % 4).unwrap() {
// TODO: Double check how the processor handles this ID field
@@ -247,27 +272,15 @@ fn batch_test() {
.await;
}
(id, preprocesses) =
recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, attempt).await;
recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, &expected_batch, attempt)
.await;
}
// Continue with signing the batch
let batch = sign_batch(&mut coordinators, key_pair.0 .0, id, preprocesses).await;
// Check it
assert_eq!(batch.batch.network, network);
assert_eq!(batch.batch.id, i);
assert_eq!(batch.batch.block, BlockHash(block_with_tx.unwrap()));
if let Some(instruction) = instruction {
assert_eq!(
batch.batch.instructions,
vec![InInstructionWithBalance { instruction, balance: balance_sent }]
);
} else {
// This shouldn't have an instruction as we didn't add any data into the TX we sent
// Empty batches remain valuable as they let us achieve consensus on the block and spend
// contained outputs
assert!(batch.batch.instructions.is_empty());
}
assert_eq!(batch.batch, expected_batch);
// Fire a SubstrateBlock
let serai_time =

View File

@@ -9,6 +9,7 @@ use messages::{sign::SignId, SubstrateContext};
use serai_client::{
primitives::{BlockHash, NetworkId},
in_instructions::primitives::Batch,
tokens::primitives::{OutInstruction, OutInstructionWithBalance},
};
@@ -186,17 +187,18 @@ fn send_test() {
// The scanner works on a 5s interval, so this leaves a few s for any processing/latency
tokio::time::sleep(Duration::from_secs(10)).await;
let expected_batch =
Batch { network, id: 0, block: BlockHash(block_with_tx.unwrap()), instructions: vec![] };
// Make sure the proceessors picked it up by checking they're trying to sign a batch for it
let (id, preprocesses) = recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, 0).await;
let (id, preprocesses) =
recv_batch_preprocesses(&mut coordinators, &key_pair.0 .0, &expected_batch, 0).await;
// Continue with signing the batch
let batch = sign_batch(&mut coordinators, key_pair.0 .0, id, preprocesses).await;
// Check it
assert_eq!(batch.batch.network, network);
assert_eq!(batch.batch.id, 0);
assert_eq!(batch.batch.block, BlockHash(block_with_tx.unwrap()));
assert!(batch.batch.instructions.is_empty());
assert_eq!(batch.batch, expected_batch);
// Fire a SubstrateBlock with a burn
let substrate_block_num = (OsRng.next_u64() % 4_000_000_000u64) + 1;