Test Batches with Instructions

This commit is contained in:
Luke Parker 2023-07-26 14:02:17 -04:00
parent e00aa3031c
commit 64c309f8db
No known key found for this signature in database
6 changed files with 74 additions and 14 deletions

1
Cargo.lock generated
View file

@ -8849,6 +8849,7 @@ dependencies = [
"dockertest",
"hex",
"monero-serai",
"parity-scale-codec",
"rand_core 0.6.4",
"serai-docker-tests",
"serai-in-instructions-primitives",

View file

@ -699,6 +699,8 @@ async fn run<C: Coin, D: Db, Co: Coordinator>(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

View file

@ -162,7 +162,7 @@ impl<D: Db> SubstrateSigner<D> {
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<D: Db> SubstrateSigner<D> {
// Only run if this hasn't already been attempted
if SubstrateSignerDb::<D>::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
);

View file

@ -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" }

View file

@ -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<u8> {
pub async fn send_to_address(
&mut self,
ops: &DockerOperations,
to: &ExternalKey,
instruction: Option<InInstruction>,
) -> (Vec<u8>, 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))
}
}
}

View file

@ -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());
}
}
});
}