2022-12-14 14:27:49 +00:00
|
|
|
use core::ops::Deref;
|
2023-01-06 09:33:17 +00:00
|
|
|
use std::collections::HashSet;
|
2022-12-05 22:25:09 +00:00
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
2022-12-14 14:27:49 +00:00
|
|
|
|
|
|
|
use zeroize::Zeroizing;
|
2022-12-05 22:25:09 +00:00
|
|
|
use rand_core::OsRng;
|
|
|
|
|
|
|
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
|
|
|
|
2023-01-01 09:18:23 +00:00
|
|
|
use tokio::sync::Mutex;
|
|
|
|
|
2022-12-05 22:25:09 +00:00
|
|
|
use monero_serai::{
|
2023-03-13 12:03:13 +00:00
|
|
|
random_scalar,
|
2022-12-05 22:25:09 +00:00
|
|
|
wallet::{
|
2023-01-06 09:33:17 +00:00
|
|
|
ViewPair, Scanner,
|
|
|
|
address::{Network, AddressType, AddressSpec, AddressMeta, MoneroAddress},
|
|
|
|
SpendableOutput,
|
2022-12-05 22:25:09 +00:00
|
|
|
},
|
2022-12-07 15:08:04 +00:00
|
|
|
rpc::Rpc,
|
2022-12-05 22:25:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
|
|
|
|
let spend = random_scalar(&mut OsRng);
|
|
|
|
let spend_pub = &spend * &ED25519_BASEPOINT_TABLE;
|
2022-12-14 14:27:49 +00:00
|
|
|
let view = Zeroizing::new(random_scalar(&mut OsRng));
|
2022-12-05 22:25:09 +00:00
|
|
|
(
|
|
|
|
spend,
|
2022-12-14 14:27:49 +00:00
|
|
|
ViewPair::new(spend_pub, view.clone()),
|
2022-12-05 22:25:09 +00:00
|
|
|
MoneroAddress {
|
|
|
|
meta: AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
|
|
|
spend: spend_pub,
|
2022-12-14 14:27:49 +00:00
|
|
|
view: view.deref() * &ED25519_BASEPOINT_TABLE,
|
2022-12-05 22:25:09 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-07 15:08:04 +00:00
|
|
|
// TODO: Support transactions already on-chain
|
|
|
|
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions
|
|
|
|
// TODO: mine as much as needed instead of default 10 blocks
|
|
|
|
pub async fn mine_until_unlocked(rpc: &Rpc, addr: &str, tx_hash: [u8; 32]) {
|
|
|
|
// mine until tx is in a block
|
|
|
|
let mut height = rpc.get_height().await.unwrap();
|
|
|
|
let mut found = false;
|
|
|
|
while !found {
|
2023-01-07 09:00:12 +00:00
|
|
|
let block = rpc.get_block_by_number(height - 1).await.unwrap();
|
2022-12-07 15:08:04 +00:00
|
|
|
found = match block.txs.iter().find(|&&x| x == tx_hash) {
|
|
|
|
Some(_) => true,
|
|
|
|
None => {
|
|
|
|
rpc.generate_blocks(addr, 1).await.unwrap();
|
|
|
|
height += 1;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// mine 9 more blocks to unlock the tx
|
|
|
|
rpc.generate_blocks(addr, 9).await.unwrap();
|
2022-12-05 22:25:09 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 09:33:17 +00:00
|
|
|
// Mines 60 blocks and returns an unlocked miner TX output.
|
2023-01-20 16:00:18 +00:00
|
|
|
#[allow(dead_code)]
|
2023-01-06 09:33:17 +00:00
|
|
|
pub async fn get_miner_tx_output(rpc: &Rpc, view: &ViewPair) -> SpendableOutput {
|
|
|
|
let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
|
|
|
|
|
|
|
|
// Mine 60 blocks to unlock a miner TX
|
|
|
|
let start = rpc.get_height().await.unwrap();
|
|
|
|
rpc
|
|
|
|
.generate_blocks(&view.address(Network::Mainnet, AddressSpec::Standard).to_string(), 60)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2023-01-07 09:00:12 +00:00
|
|
|
let block = rpc.get_block_by_number(start).await.unwrap();
|
2023-01-06 09:33:17 +00:00
|
|
|
scanner.scan(rpc, &block).await.unwrap().swap_remove(0).ignore_timelock().swap_remove(0)
|
|
|
|
}
|
|
|
|
|
2022-12-05 22:25:09 +00:00
|
|
|
pub async fn rpc() -> Rpc {
|
|
|
|
let rpc = Rpc::new("http://127.0.0.1:18081".to_string()).unwrap();
|
|
|
|
|
|
|
|
// Only run once
|
|
|
|
if rpc.get_height().await.unwrap() != 1 {
|
|
|
|
return rpc;
|
|
|
|
}
|
|
|
|
|
|
|
|
let addr = MoneroAddress {
|
|
|
|
meta: AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
|
|
|
spend: &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE,
|
|
|
|
view: &random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE,
|
|
|
|
}
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
// Mine 40 blocks to ensure decoy availability
|
2022-12-07 15:08:04 +00:00
|
|
|
rpc.generate_blocks(&addr, 40).await.unwrap();
|
2022-12-05 22:25:09 +00:00
|
|
|
|
2023-03-14 05:54:08 +00:00
|
|
|
// Make sure we recognize the protocol
|
|
|
|
rpc.get_protocol().await.unwrap();
|
2022-12-05 22:25:09 +00:00
|
|
|
|
|
|
|
rpc
|
|
|
|
}
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
pub static ref SEQUENTIAL: Mutex<()> = Mutex::new(());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! async_sequential {
|
|
|
|
($(async fn $name: ident() $body: block)*) => {
|
|
|
|
$(
|
|
|
|
#[tokio::test]
|
|
|
|
async fn $name() {
|
2023-01-01 09:18:23 +00:00
|
|
|
let guard = runner::SEQUENTIAL.lock().await;
|
2022-12-05 22:25:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! test {
|
|
|
|
(
|
|
|
|
$name: ident,
|
|
|
|
(
|
|
|
|
$first_tx: expr,
|
|
|
|
$first_checks: expr,
|
|
|
|
),
|
|
|
|
$((
|
|
|
|
$tx: expr,
|
|
|
|
$checks: expr,
|
|
|
|
)$(,)?),*
|
|
|
|
) => {
|
|
|
|
async_sequential! {
|
|
|
|
async fn $name() {
|
|
|
|
use core::{ops::Deref, any::Any};
|
|
|
|
use std::collections::HashSet;
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
use zeroize::Zeroizing;
|
|
|
|
use rand_core::OsRng;
|
|
|
|
|
|
|
|
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
|
|
|
|
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
use transcript::{Transcript, RecommendedTranscript};
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
use frost::{
|
|
|
|
curve::Ed25519,
|
2023-02-23 11:50:45 +00:00
|
|
|
Participant,
|
2022-12-05 22:25:09 +00:00
|
|
|
tests::{THRESHOLD, key_gen},
|
|
|
|
};
|
|
|
|
|
|
|
|
use monero_serai::{
|
|
|
|
random_scalar,
|
|
|
|
wallet::{
|
2023-03-11 15:31:58 +00:00
|
|
|
address::{Network, AddressSpec}, ViewPair, Scanner, Change, SignableTransaction,
|
2022-12-05 22:25:09 +00:00
|
|
|
SignableTransactionBuilder,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-01-06 09:33:17 +00:00
|
|
|
use runner::{random_address, rpc, mine_until_unlocked, get_miner_tx_output};
|
2022-12-05 22:25:09 +00:00
|
|
|
|
|
|
|
type Builder = SignableTransactionBuilder;
|
|
|
|
|
|
|
|
// Run each function as both a single signer and as a multisig
|
2023-01-01 09:18:23 +00:00
|
|
|
#[allow(clippy::redundant_closure_call)]
|
2022-12-05 22:25:09 +00:00
|
|
|
for multisig in [false, true] {
|
|
|
|
// Only run the multisig variant if multisig is enabled
|
|
|
|
if multisig {
|
|
|
|
#[cfg(not(feature = "multisig"))]
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let spend = Zeroizing::new(random_scalar(&mut OsRng));
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
let keys = key_gen::<_, Ed25519>(&mut OsRng);
|
|
|
|
|
|
|
|
let spend_pub = if !multisig {
|
|
|
|
spend.deref() * &ED25519_BASEPOINT_TABLE
|
|
|
|
} else {
|
|
|
|
#[cfg(not(feature = "multisig"))]
|
|
|
|
panic!("Multisig branch called without the multisig feature");
|
|
|
|
#[cfg(feature = "multisig")]
|
2023-02-23 11:50:45 +00:00
|
|
|
keys[&Participant::new(1).unwrap()].group_key().0
|
2022-12-05 22:25:09 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let rpc = rpc().await;
|
|
|
|
|
2023-01-06 09:33:17 +00:00
|
|
|
let view = ViewPair::new(spend_pub, Zeroizing::new(random_scalar(&mut OsRng)));
|
|
|
|
let addr = view.address(Network::Mainnet, AddressSpec::Standard);
|
|
|
|
|
|
|
|
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
2022-12-05 22:25:09 +00:00
|
|
|
|
|
|
|
let builder = SignableTransactionBuilder::new(
|
|
|
|
rpc.get_protocol().await.unwrap(),
|
|
|
|
rpc.get_fee().await.unwrap(),
|
2023-03-11 15:31:58 +00:00
|
|
|
Some(Change::new(
|
|
|
|
&ViewPair::new(
|
|
|
|
&random_scalar(&mut OsRng) * &ED25519_BASEPOINT_TABLE,
|
|
|
|
Zeroizing::new(random_scalar(&mut OsRng))
|
|
|
|
),
|
|
|
|
false
|
|
|
|
)),
|
2022-12-05 22:25:09 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let sign = |tx: SignableTransaction| {
|
|
|
|
let rpc = rpc.clone();
|
|
|
|
let spend = spend.clone();
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
let keys = keys.clone();
|
|
|
|
async move {
|
|
|
|
if !multisig {
|
|
|
|
tx.sign(&mut OsRng, &rpc, &spend).await.unwrap()
|
|
|
|
} else {
|
|
|
|
#[cfg(not(feature = "multisig"))]
|
|
|
|
panic!("Multisig branch called without the multisig feature");
|
|
|
|
#[cfg(feature = "multisig")]
|
|
|
|
{
|
|
|
|
let mut machines = HashMap::new();
|
2023-02-23 11:50:45 +00:00
|
|
|
for i in (1 ..= THRESHOLD).map(|i| Participant::new(i).unwrap()) {
|
2022-12-05 22:25:09 +00:00
|
|
|
machines.insert(
|
|
|
|
i,
|
|
|
|
tx
|
|
|
|
.clone()
|
|
|
|
.multisig(
|
|
|
|
&rpc,
|
|
|
|
keys[&i].clone(),
|
|
|
|
RecommendedTranscript::new(b"Monero Serai Test Transaction"),
|
|
|
|
rpc.get_height().await.unwrap() - 10,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-01 09:18:23 +00:00
|
|
|
frost::tests::sign_without_caching(&mut OsRng, machines, &[])
|
2022-12-05 22:25:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: Generate a distinct wallet for each transaction to prevent overlap
|
|
|
|
let next_addr = addr;
|
|
|
|
|
|
|
|
let temp = Box::new({
|
|
|
|
let mut builder = builder.clone();
|
|
|
|
builder.add_input(miner_tx);
|
|
|
|
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
|
|
|
|
|
|
|
let signed = sign(tx).await;
|
|
|
|
rpc.publish_transaction(&signed).await.unwrap();
|
2022-12-07 15:08:04 +00:00
|
|
|
mine_until_unlocked(&rpc, &random_address().2.to_string(), signed.hash()).await;
|
|
|
|
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
|
|
|
let scanner =
|
2023-01-06 09:33:17 +00:00
|
|
|
Scanner::from_view(view.clone(), Some(HashSet::new()));
|
2022-12-07 15:08:04 +00:00
|
|
|
($first_checks)(rpc.clone(), tx, scanner, state).await
|
2022-12-05 22:25:09 +00:00
|
|
|
});
|
|
|
|
#[allow(unused_variables, unused_mut, unused_assignments)]
|
|
|
|
let mut carried_state: Box<dyn Any> = temp;
|
|
|
|
|
|
|
|
$(
|
|
|
|
let (tx, state) = ($tx)(
|
|
|
|
rpc.clone(),
|
|
|
|
builder.clone(),
|
|
|
|
next_addr,
|
|
|
|
*carried_state.downcast().unwrap()
|
|
|
|
).await;
|
|
|
|
|
|
|
|
let signed = sign(tx).await;
|
|
|
|
rpc.publish_transaction(&signed).await.unwrap();
|
2022-12-07 15:08:04 +00:00
|
|
|
mine_until_unlocked(&rpc, &random_address().2.to_string(), signed.hash()).await;
|
|
|
|
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
2022-12-05 22:25:09 +00:00
|
|
|
#[allow(unused_assignments)]
|
|
|
|
{
|
2022-12-07 15:08:04 +00:00
|
|
|
let scanner =
|
2023-01-06 09:33:17 +00:00
|
|
|
Scanner::from_view(view.clone(), Some(HashSet::new()));
|
2022-12-05 22:25:09 +00:00
|
|
|
carried_state =
|
2022-12-07 15:08:04 +00:00
|
|
|
Box::new(($checks)(rpc.clone(), tx, scanner, state).await);
|
2022-12-05 22:25:09 +00:00
|
|
|
}
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|