serai/coins/monero/tests/send.rs
Luke Parker d10c6e16dc
Move FROST to HashMaps
Honestly, the borrowed keys are frustrating, and this probably reduces 
performance while no longer offering an order when iterating. That said, 
they enable full u16 indexing and should mildly improve the API.

Cleans the Proof of Knowledge handling present in key gen.
2022-05-24 21:41:14 -04:00

180 lines
4.7 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;
}
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;
}
}