diff --git a/Cargo.lock b/Cargo.lock index 18342c05..c6af896a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8849,6 +8849,7 @@ dependencies = [ "dockertest", "hex", "monero-serai", + "parity-scale-codec", "rand_core 0.6.4", "serai-docker-tests", "serai-in-instructions-primitives", diff --git a/processor/src/main.rs b/processor/src/main.rs index a156b1d5..45a55dae 100644 --- a/processor/src/main.rs +++ b/processor/src/main.rs @@ -699,6 +699,8 @@ async fn run(mut raw_db: D, coin: C, mut coordi }).collect() }; + info!("created batch {} ({} instructions)", batch.id, batch.instructions.len()); + // Start signing this batch tributary_mutable .substrate_signers diff --git a/processor/src/substrate_signer.rs b/processor/src/substrate_signer.rs index 7c23a07a..2a81e0c7 100644 --- a/processor/src/substrate_signer.rs +++ b/processor/src/substrate_signer.rs @@ -162,7 +162,7 @@ impl SubstrateSigner { self.attempt.insert(id, attempt); let id = SignId { key: self.keys.group_key().to_bytes().to_vec(), id, attempt }; - info!("signing batch {} with attempt #{}", hex::encode(id.id), id.attempt); + info!("signing batch {}, attempt #{}", hex::encode(id.id), id.attempt); // If we reboot mid-sign, the current design has us abort all signs and wait for latter // attempts/new signing protocols @@ -178,7 +178,7 @@ impl SubstrateSigner { // Only run if this hasn't already been attempted if SubstrateSignerDb::::has_attempt(txn, &id) { warn!( - "already attempted {} #{}. this is an error if we didn't reboot", + "already attempted batch {}, attempt #{}. this is an error if we didn't reboot", hex::encode(id.id), id.attempt ); diff --git a/tests/processor/Cargo.toml b/tests/processor/Cargo.toml index 04914049..2563de23 100644 --- a/tests/processor/Cargo.toml +++ b/tests/processor/Cargo.toml @@ -28,6 +28,7 @@ monero-serai = { path = "../../coins/monero" } messages = { package = "serai-processor-messages", path = "../../processor/messages" } +scale = { package = "parity-scale-codec", version = "3" } serai-primitives = { path = "../../substrate/primitives" } serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives" } serai-in-instructions-primitives = { path = "../../substrate/in-instructions/primitives" } diff --git a/tests/processor/src/networks.rs b/tests/processor/src/networks.rs index 4e4b5d75..57ccdec2 100644 --- a/tests/processor/src/networks.rs +++ b/tests/processor/src/networks.rs @@ -3,8 +3,11 @@ use std::collections::HashSet; use zeroize::Zeroizing; use rand_core::{RngCore, OsRng}; -use serai_primitives::NetworkId; +use scale::Encode; + +use serai_primitives::{NetworkId, Amount}; use serai_validator_sets_primitives::ExternalKey; +use serai_in_instructions_primitives::{InInstruction, RefundableInInstruction, Shorthand}; use dockertest::{PullPolicy, Image, StartPolicy, Composition, DockerOperations}; @@ -213,7 +216,12 @@ impl Wallet { } } - pub async fn send_to_address(&mut self, ops: &DockerOperations, to: &ExternalKey) -> Vec { + pub async fn send_to_address( + &mut self, + ops: &DockerOperations, + to: &ExternalKey, + instruction: Option, + ) -> (Vec, Amount) { match self { Wallet::Bitcoin { private_key, public_key, ref mut input_tx } => { use bitcoin_serai::bitcoin::{ @@ -221,7 +229,7 @@ impl Wallet { key::{XOnlyPublicKey, TweakedPublicKey}, consensus::Encodable, sighash::{EcdsaSighashType, SighashCache}, - script::{PushBytesBuf, Script, Builder}, + script::{PushBytesBuf, Script, ScriptBuf, Builder}, address::Payload, OutPoint, Sequence, Witness, TxIn, TxOut, absolute::LockTime, @@ -253,6 +261,18 @@ impl Wallet { ], }; + if let Some(instruction) = instruction { + tx.output.push(TxOut { + value: 0, + script_pubkey: ScriptBuf::new_op_return( + &PushBytesBuf::try_from( + Shorthand::Raw(RefundableInInstruction { origin: None, instruction }).encode(), + ) + .unwrap(), + ), + }); + } + let mut der = SECP256K1 .sign_ecdsa_low_r( &Message::from( @@ -278,7 +298,7 @@ impl Wallet { let mut buf = vec![]; tx.consensus_encode(&mut buf).unwrap(); *input_tx = tx; - buf + (buf, Amount(AMOUNT)) } Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => { @@ -329,13 +349,18 @@ impl Wallet { ); // Create and sign the TX + const AMOUNT: u64 = 1_000_000_000_000; + let mut data = vec![]; + if let Some(instruction) = instruction { + data.push(Shorthand::Raw(RefundableInInstruction { origin: None, instruction }).encode()); + } let tx = SignableTransaction::new( Protocol::v16, None, these_inputs.drain(..).zip(decoys.drain(..)).collect(), - vec![(to_addr, 1_000_000_000_000)], + vec![(to_addr, AMOUNT)], Some(Change::new(view_pair, false)), - vec![], + data, rpc.get_fee(Protocol::v16, FeePriority::Low).await.unwrap(), ) .unwrap() @@ -351,7 +376,7 @@ impl Wallet { .remove(0), ); - tx.serialize() + (tx.serialize(), Amount(AMOUNT)) } } } diff --git a/tests/processor/src/tests/batch.rs b/tests/processor/src/tests/batch.rs index 23006a53..77b798f4 100644 --- a/tests/processor/src/tests/batch.rs +++ b/tests/processor/src/tests/batch.rs @@ -4,8 +4,12 @@ use dkg::{Participant, tests::clone_without}; use messages::sign::SignId; -use serai_primitives::{NetworkId, BlockHash, crypto::RuntimePublic, PublicKey}; -use serai_in_instructions_primitives::{SignedBatch, batch_message}; +use serai_primitives::{ + BlockHash, crypto::RuntimePublic, PublicKey, SeraiAddress, NetworkId, Coin, Balance, +}; +use serai_in_instructions_primitives::{ + InInstruction, InInstructionWithBalance, SignedBatch, batch_message, +}; use dockertest::DockerTest; @@ -168,9 +172,16 @@ fn batch_test() { } coordinators[0].sync(&ops, &coordinators[1 ..]).await; + // Run twice, once with an instruction and once without for i in 0 .. 2 { + let mut serai_address = [0; 32]; + OsRng.fill_bytes(&mut serai_address); + let instruction = + if i == 1 { Some(InInstruction::Transfer(SeraiAddress(serai_address))) } else { None }; + // Send into the processor's wallet - let tx = wallet.send_to_address(&ops, &key_pair.1).await; + let (tx, amount_sent) = + wallet.send_to_address(&ops, &key_pair.1, instruction.clone()).await; for coordinator in &mut coordinators { coordinator.publish_transacton(&ops, &tx).await; } @@ -215,8 +226,28 @@ fn batch_test() { assert_eq!(batch.batch.network, network); assert_eq!(batch.batch.id, i); assert_eq!(batch.batch.block, BlockHash(block_with_tx.unwrap())); - // This shouldn't have an instruction as we didn't add any data into the TX we sent - assert!(batch.batch.instructions.is_empty()); + if let Some(instruction) = instruction { + assert_eq!( + batch.batch.instructions, + vec![InInstructionWithBalance { + instruction, + balance: Balance { + coin: match network { + NetworkId::Bitcoin => Coin::Bitcoin, + NetworkId::Ethereum => todo!(), + NetworkId::Monero => Coin::Monero, + NetworkId::Serai => panic!("running processor tests on Serai"), + }, + amount: amount_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()); + } } }); }