mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39:24 +00:00
While it was fine as-is, as it only had one variable length property, this is a bit more robust. Also binds the Curve ID, which should declare differently even for just different basepoints, and therefore adds two variable length properties (justifying the transcript).
187 lines
5.0 KiB
Rust
187 lines
5.0 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;
|
|
#[cfg(feature = "multisig")]
|
|
use frost::tests::{THRESHOLD, key_gen, sign};
|
|
|
|
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")]
|
|
use monero_serai::frost::{Transcript, Ed25519};
|
|
|
|
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 = key_gen::<_, Ed25519>(&mut OsRng);
|
|
|
|
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).0;
|
|
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).0.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(
|
|
Transcript::new(b"Monero Serai Test Transaction"),
|
|
&mut OsRng,
|
|
&rpc,
|
|
rpc.get_height().await.unwrap() - 10,
|
|
(*keys[&i]).clone(),
|
|
(1 ..= THRESHOLD).collect::<Vec<_>>()
|
|
).await.unwrap()
|
|
);
|
|
}
|
|
|
|
tx = Some(sign(&mut OsRng, machines, &vec![]));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|