mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-27 04:55:57 +00:00
ba032cca4a
Saves roughly 0.8s when running the tests, which took 16.6s and now take 15.8 (5%). Removes the larger sample size, which replaced the closest selected decoy with the real spend, per advice of Rucknium.
189 lines
5.1 KiB
Rust
189 lines
5.1 KiB
Rust
use std::{sync::Mutex, collections::HashMap};
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
#[cfg(feature = "multisig")]
|
|
use blake2::{digest::Update, Digest, Blake2b512};
|
|
|
|
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
|
|
#[cfg(feature = "multisig")]
|
|
use dalek_ff_group::Scalar;
|
|
|
|
use monero::{
|
|
network::Network,
|
|
util::{key::PublicKey, address::Address}
|
|
};
|
|
|
|
use monero_serai::{random_scalar, wallet::SignableTransaction};
|
|
|
|
mod rpc;
|
|
use crate::rpc::{rpc, mine_block};
|
|
|
|
#[cfg(feature = "multisig")]
|
|
mod frost;
|
|
#[cfg(feature = "multisig")]
|
|
use crate::frost::{THRESHOLD, generate_keys, sign};
|
|
|
|
lazy_static! {
|
|
static ref SEQUENTIAL: Mutex<()> = Mutex::new(());
|
|
}
|
|
|
|
macro_rules! async_sequential {
|
|
($(async fn $name: ident() $body: block)*) => {
|
|
$(
|
|
#[tokio::test]
|
|
async fn $name() {
|
|
let guard = SEQUENTIAL.lock().unwrap();
|
|
let local = tokio::task::LocalSet::new();
|
|
local.run_until(async move {
|
|
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
|
drop(guard);
|
|
Err(err).unwrap()
|
|
}
|
|
}).await;
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
async fn send_core(test: usize, multisig: bool) {
|
|
let rpc = rpc().await;
|
|
|
|
// Generate an address
|
|
let spend = random_scalar(&mut OsRng);
|
|
#[allow(unused_mut)]
|
|
let mut view = random_scalar(&mut OsRng);
|
|
#[allow(unused_mut)]
|
|
let mut spend_pub = &spend * &ED25519_BASEPOINT_TABLE;
|
|
|
|
#[cfg(feature = "multisig")]
|
|
let (keys, _) = generate_keys();
|
|
|
|
if multisig {
|
|
#[cfg(not(feature = "multisig"))]
|
|
panic!("Running a multisig test without the multisig feature");
|
|
#[cfg(feature = "multisig")]
|
|
{
|
|
view = Scalar::from_hash(Blake2b512::new().chain("Monero Serai Transaction Test")).0;
|
|
spend_pub = keys[&1].group_key().0;
|
|
}
|
|
}
|
|
|
|
let addr = Address::standard(
|
|
Network::Mainnet,
|
|
PublicKey { point: spend_pub.compress() },
|
|
PublicKey { point: (&view * &ED25519_BASEPOINT_TABLE).compress() }
|
|
);
|
|
|
|
// TODO
|
|
let fee_per_byte = 50000000;
|
|
let fee = fee_per_byte * 2000;
|
|
|
|
let start = rpc.get_height().await.unwrap();
|
|
for _ in 0 .. 7 {
|
|
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
|
}
|
|
|
|
let mut tx = None;
|
|
// Allow tests to test variable transactions
|
|
for i in 0 .. [2, 1][test] {
|
|
let mut outputs = vec![];
|
|
let mut amount = 0;
|
|
// Test spending both a miner output and a normal output
|
|
if test == 0 {
|
|
if i == 0 {
|
|
tx = Some(rpc.get_block_transactions(start).await.unwrap().swap_remove(0));
|
|
}
|
|
|
|
// Grab the largest output available
|
|
let output = {
|
|
let mut outputs = tx.as_ref().unwrap().scan(view, spend_pub);
|
|
outputs.sort_by(|x, y| x.commitment.amount.cmp(&y.commitment.amount).reverse());
|
|
outputs.swap_remove(0)
|
|
};
|
|
// Test creating a zero change output and a non-zero change output
|
|
amount = output.commitment.amount - u64::try_from(i).unwrap();
|
|
outputs.push(output);
|
|
|
|
// Test spending multiple inputs
|
|
} else if test == 1 {
|
|
if i != 0 {
|
|
continue;
|
|
}
|
|
|
|
// We actually need 80 decoys for this transaction, so mine until then
|
|
// 80 + 60 (miner TX maturity) + 10 (lock blocks)
|
|
// It is possible for this to be lower, by noting maturity is sufficient regardless of lock
|
|
// blocks, yet that's not currently implemented
|
|
// TODO, if we care
|
|
while rpc.get_height().await.unwrap() < 160 {
|
|
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
|
}
|
|
|
|
for i in (start + 1) .. (start + 9) {
|
|
let tx = rpc.get_block_transactions(i).await.unwrap().swap_remove(0);
|
|
let output = tx.scan(view, spend_pub).swap_remove(0);
|
|
amount += output.commitment.amount;
|
|
outputs.push(output);
|
|
}
|
|
}
|
|
|
|
let mut signable = SignableTransaction::new(
|
|
outputs, vec![(addr, amount - fee)], addr, fee_per_byte
|
|
).unwrap();
|
|
|
|
if !multisig {
|
|
tx = Some(signable.sign(&mut OsRng, &rpc, &spend).await.unwrap());
|
|
} else {
|
|
#[cfg(feature = "multisig")]
|
|
{
|
|
let mut machines = HashMap::new();
|
|
for i in 1 ..= THRESHOLD {
|
|
machines.insert(
|
|
i,
|
|
signable.clone().multisig(
|
|
b"Monero Serai Test Transaction".to_vec(),
|
|
&mut OsRng,
|
|
&rpc,
|
|
rpc.get_height().await.unwrap() - 10,
|
|
keys[&i].clone(),
|
|
(1 ..= THRESHOLD).collect::<Vec<_>>()
|
|
).await.unwrap()
|
|
);
|
|
}
|
|
|
|
let mut txs = sign(&mut machines, &vec![]);
|
|
for s in 1 .. THRESHOLD {
|
|
assert_eq!(txs[usize::from(s)].hash(), txs[0].hash());
|
|
}
|
|
tx = Some(txs.swap_remove(0));
|
|
}
|
|
}
|
|
|
|
rpc.publish_transaction(tx.as_ref().unwrap()).await.unwrap();
|
|
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
|
}
|
|
}
|
|
|
|
async_sequential! {
|
|
async fn send_single_input() {
|
|
send_core(0, false).await;
|
|
}
|
|
|
|
async fn send_multiple_inputs() {
|
|
send_core(1, false).await;
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "multisig")]
|
|
async_sequential! {
|
|
async fn multisig_send_single_input() {
|
|
send_core(0, true).await;
|
|
}
|
|
|
|
async fn multisig_send_multiple_inputs() {
|
|
send_core(1, true).await;
|
|
}
|
|
}
|