mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 09:27:36 +00:00
Write a test runner for Monero transactions
Also includes a few fixes for the library itself. Supersedes #172.
This commit is contained in:
parent
7a16ce78b0
commit
8f352353ba
6 changed files with 321 additions and 279 deletions
|
@ -27,7 +27,7 @@ pub struct JsonRpcResponse<T> {
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct TransactionResponse {
|
struct TransactionResponse {
|
||||||
tx_hash: String,
|
tx_hash: String,
|
||||||
block_height: usize,
|
block_height: Option<usize>,
|
||||||
as_hex: String,
|
as_hex: String,
|
||||||
pruned_as_hex: String,
|
pruned_as_hex: String,
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ impl Rpc {
|
||||||
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
|
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_transaction_block_number(&self, tx: &[u8]) -> Result<usize, RpcError> {
|
pub async fn get_transaction_block_number(&self, tx: &[u8]) -> Result<Option<usize>, RpcError> {
|
||||||
let txs: TransactionsResponse =
|
let txs: TransactionsResponse =
|
||||||
self.rpc_call("get_transactions", Some(json!({ "txs_hashes": [hex::encode(tx)] }))).await?;
|
self.rpc_call("get_transactions", Some(json!({ "txs_hashes": [hex::encode(tx)] }))).await?;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub mod address;
|
||||||
use address::{Network, AddressType, AddressMeta, MoneroAddress};
|
use address::{Network, AddressType, AddressMeta, MoneroAddress};
|
||||||
|
|
||||||
mod scan;
|
mod scan;
|
||||||
pub use scan::SpendableOutput;
|
pub use scan::{ReceivedOutput, SpendableOutput};
|
||||||
|
|
||||||
pub(crate) mod decoys;
|
pub(crate) mod decoys;
|
||||||
pub(crate) use decoys::Decoys;
|
pub(crate) use decoys::Decoys;
|
||||||
|
|
|
@ -362,7 +362,7 @@ impl SignableTransaction {
|
||||||
|
|
||||||
/// Sign this transaction.
|
/// Sign this transaction.
|
||||||
pub async fn sign<R: RngCore + CryptoRng>(
|
pub async fn sign<R: RngCore + CryptoRng>(
|
||||||
&mut self,
|
mut self,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
rpc: &Rpc,
|
rpc: &Rpc,
|
||||||
spend: &Zeroizing<Scalar>,
|
spend: &Zeroizing<Scalar>,
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
use rand_core::OsRng;
|
|
||||||
|
|
||||||
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
|
|
||||||
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use monero_serai::{
|
|
||||||
Protocol, random_scalar,
|
|
||||||
wallet::address::{Network, AddressType, AddressMeta, MoneroAddress},
|
|
||||||
rpc::{EmptyResponse, RpcError, Rpc},
|
|
||||||
};
|
|
||||||
|
|
||||||
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 20 blocks to ensure decoy availability
|
|
||||||
mine_block(&rpc, &addr).await.unwrap();
|
|
||||||
mine_block(&rpc, &addr).await.unwrap();
|
|
||||||
assert!(!matches!(rpc.get_protocol().await.unwrap(), Protocol::Unsupported(_)));
|
|
||||||
|
|
||||||
rpc
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn mine_block(rpc: &Rpc, address: &str) -> Result<EmptyResponse, RpcError> {
|
|
||||||
rpc
|
|
||||||
.rpc_call(
|
|
||||||
"json_rpc",
|
|
||||||
Some(json!({
|
|
||||||
"method": "generateblocks",
|
|
||||||
"params": {
|
|
||||||
"wallet_address": address,
|
|
||||||
"amount_of_blocks": 10
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
266
coins/monero/tests/runner.rs
Normal file
266
coins/monero/tests/runner.rs
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||||
|
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use monero_serai::{
|
||||||
|
Protocol, random_scalar,
|
||||||
|
wallet::{
|
||||||
|
ViewPair,
|
||||||
|
address::{Network, AddressType, AddressMeta, MoneroAddress},
|
||||||
|
},
|
||||||
|
rpc::{EmptyResponse, Rpc},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
|
||||||
|
let spend = random_scalar(&mut OsRng);
|
||||||
|
let spend_pub = &spend * &ED25519_BASEPOINT_TABLE;
|
||||||
|
let view = random_scalar(&mut OsRng);
|
||||||
|
(
|
||||||
|
spend,
|
||||||
|
ViewPair::new(spend_pub, view),
|
||||||
|
MoneroAddress {
|
||||||
|
meta: AddressMeta::new(Network::Mainnet, AddressType::Standard),
|
||||||
|
spend: spend_pub,
|
||||||
|
view: &view * &ED25519_BASEPOINT_TABLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mine_blocks(rpc: &Rpc, address: &str) {
|
||||||
|
rpc
|
||||||
|
.rpc_call::<_, EmptyResponse>(
|
||||||
|
"json_rpc",
|
||||||
|
Some(json!({
|
||||||
|
"method": "generateblocks",
|
||||||
|
"params": {
|
||||||
|
"wallet_address": address,
|
||||||
|
"amount_of_blocks": 10
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
for _ in 0 .. 4 {
|
||||||
|
mine_blocks(&rpc, &addr).await;
|
||||||
|
}
|
||||||
|
assert!(!matches!(rpc.get_protocol().await.unwrap(), Protocol::Unsupported(_)));
|
||||||
|
|
||||||
|
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() {
|
||||||
|
let guard = runner::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;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
tests::{THRESHOLD, key_gen},
|
||||||
|
};
|
||||||
|
|
||||||
|
use monero_serai::{
|
||||||
|
random_scalar,
|
||||||
|
wallet::{
|
||||||
|
address::Network, ViewPair, Scanner, SignableTransaction,
|
||||||
|
SignableTransactionBuilder,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use runner::{random_address, rpc, mine_blocks};
|
||||||
|
|
||||||
|
type Builder = SignableTransactionBuilder;
|
||||||
|
|
||||||
|
// Run each function as both a single signer and as a multisig
|
||||||
|
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")]
|
||||||
|
keys[&1].group_key().0
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = ViewPair::new(spend_pub, random_scalar(&mut OsRng));
|
||||||
|
|
||||||
|
let rpc = rpc().await;
|
||||||
|
|
||||||
|
let (addr, miner_tx) = {
|
||||||
|
let mut scanner =
|
||||||
|
Scanner::from_view(view.clone(), Network::Mainnet, Some(HashSet::new()));
|
||||||
|
let addr = scanner.address();
|
||||||
|
|
||||||
|
let start = rpc.get_height().await.unwrap();
|
||||||
|
for _ in 0 .. 7 {
|
||||||
|
mine_blocks(&rpc, &addr.to_string()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = rpc.get_block(start).await.unwrap();
|
||||||
|
(
|
||||||
|
addr,
|
||||||
|
scanner.scan(
|
||||||
|
&rpc,
|
||||||
|
&block
|
||||||
|
).await.unwrap().swap_remove(0).ignore_timelock().swap_remove(0)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = SignableTransactionBuilder::new(
|
||||||
|
rpc.get_protocol().await.unwrap(),
|
||||||
|
rpc.get_fee().await.unwrap(),
|
||||||
|
Some(random_address().2),
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
for i in 1 ..= THRESHOLD {
|
||||||
|
machines.insert(
|
||||||
|
i,
|
||||||
|
tx
|
||||||
|
.clone()
|
||||||
|
.multisig(
|
||||||
|
&rpc,
|
||||||
|
keys[&i].clone(),
|
||||||
|
RecommendedTranscript::new(b"Monero Serai Test Transaction"),
|
||||||
|
rpc.get_height().await.unwrap() - 10,
|
||||||
|
(1 ..= THRESHOLD).collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
frost::tests::sign(&mut OsRng, machines, &vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
mine_blocks(&rpc, &random_address().2.to_string()).await;
|
||||||
|
($first_checks)(rpc.clone(), signed.hash(), view.clone(), state).await
|
||||||
|
});
|
||||||
|
#[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();
|
||||||
|
#[allow(unused_assignments)]
|
||||||
|
{
|
||||||
|
carried_state =
|
||||||
|
Box::new(($checks)(rpc.clone(), signed.hash(), view.clone(), state).await);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,232 +1,57 @@
|
||||||
use core::ops::Deref;
|
|
||||||
use std::{sync::Mutex, collections::HashSet};
|
|
||||||
#[cfg(feature = "multisig")]
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use zeroize::Zeroizing;
|
|
||||||
use rand_core::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 transcript::{Transcript, RecommendedTranscript};
|
|
||||||
#[cfg(feature = "multisig")]
|
|
||||||
use frost::{
|
|
||||||
curve::Ed25519,
|
|
||||||
tests::{THRESHOLD, key_gen, sign},
|
|
||||||
};
|
|
||||||
|
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
random_scalar,
|
rpc::Rpc,
|
||||||
wallet::{
|
wallet::{ReceivedOutput, SpendableOutput},
|
||||||
address::Network, ViewPair, Scanner, SpendableOutput, SignableTransaction,
|
|
||||||
SignableTransactionBuilder,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod rpc;
|
mod runner;
|
||||||
use crate::rpc::{rpc, mine_block};
|
|
||||||
|
|
||||||
lazy_static! {
|
test!(
|
||||||
static ref SEQUENTIAL: Mutex<()> = Mutex::new(());
|
spend_miner_output,
|
||||||
}
|
(
|
||||||
|
|_, mut builder: Builder, addr| async move {
|
||||||
|
builder.add_payment(addr, 5);
|
||||||
|
(builder.build().unwrap(), ())
|
||||||
|
},
|
||||||
|
|rpc: Rpc, hash, view, _| async move {
|
||||||
|
let mut scanner = Scanner::from_view(view, Network::Mainnet, Some(HashSet::new()));
|
||||||
|
let tx = rpc.get_transaction(hash).await.unwrap();
|
||||||
|
let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0);
|
||||||
|
assert_eq!(output.commitment().amount, 5);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
macro_rules! async_sequential {
|
test!(
|
||||||
($(async fn $name: ident() $body: block)*) => {
|
spend_multiple_outputs,
|
||||||
$(
|
(
|
||||||
#[tokio::test]
|
|_, mut builder: Builder, addr| async move {
|
||||||
async fn $name() {
|
builder.add_payment(addr, 1000000000000);
|
||||||
let guard = SEQUENTIAL.lock().unwrap();
|
builder.add_payment(addr, 2000000000000);
|
||||||
let local = tokio::task::LocalSet::new();
|
(builder.build().unwrap(), ())
|
||||||
local.run_until(async move {
|
},
|
||||||
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
|rpc: Rpc, hash, view, _| async move {
|
||||||
drop(guard);
|
let mut scanner = Scanner::from_view(view, Network::Mainnet, Some(HashSet::new()));
|
||||||
Err(err).unwrap()
|
let tx = rpc.get_transaction(hash).await.unwrap();
|
||||||
}
|
let mut outputs = scanner.scan_transaction(&tx).not_locked();
|
||||||
}).await;
|
outputs.sort_by(|x, y| x.commitment().amount.cmp(&y.commitment().amount));
|
||||||
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
|
assert_eq!(outputs[1].commitment().amount, 2000000000000);
|
||||||
|
outputs
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
|rpc, mut builder: Builder, addr, mut outputs: Vec<ReceivedOutput>| async move {
|
||||||
|
for output in outputs.drain(..) {
|
||||||
|
builder.add_input(SpendableOutput::from(&rpc, output).await.unwrap());
|
||||||
}
|
}
|
||||||
)*
|
builder.add_payment(addr, 6);
|
||||||
};
|
(builder.build().unwrap(), ())
|
||||||
}
|
},
|
||||||
|
|rpc: Rpc, hash, view, _| async move {
|
||||||
async fn send_core(test: usize, multisig: bool) {
|
let mut scanner = Scanner::from_view(view, Network::Mainnet, Some(HashSet::new()));
|
||||||
let rpc = rpc().await;
|
let tx = rpc.get_transaction(hash).await.unwrap();
|
||||||
|
let output = scanner.scan_transaction(&tx).not_locked().swap_remove(0);
|
||||||
// Generate an address
|
assert_eq!(output.commitment().amount, 6);
|
||||||
let spend = Zeroizing::new(random_scalar(&mut OsRng));
|
},
|
||||||
#[allow(unused_mut)]
|
),
|
||||||
let mut view = random_scalar(&mut OsRng);
|
);
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut spend_pub = spend.deref() * &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 view_pair = ViewPair::new(spend_pub, view);
|
|
||||||
let mut scanner = Scanner::from_view(view_pair, Network::Mainnet, Some(HashSet::new()));
|
|
||||||
let addr = scanner.address();
|
|
||||||
|
|
||||||
let fee = rpc.get_fee().await.unwrap();
|
|
||||||
|
|
||||||
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 = scanner.scan_transaction(tx.as_ref().unwrap()).ignore_timelock();
|
|
||||||
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(SpendableOutput::from(&rpc, output).await.unwrap());
|
|
||||||
|
|
||||||
// Test spending multiple inputs
|
|
||||||
} else if test == 1 {
|
|
||||||
if i != 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We actually need 120 decoys for this transaction, so mine until then
|
|
||||||
// 120 + 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() < 200 {
|
|
||||||
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in (start + 1) .. (start + 9) {
|
|
||||||
let mut txs = scanner.scan(&rpc, &rpc.get_block(i).await.unwrap()).await.unwrap();
|
|
||||||
let output = txs.swap_remove(0).ignore_timelock().swap_remove(0);
|
|
||||||
amount += output.commitment().amount;
|
|
||||||
outputs.push(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut signable = SignableTransaction::new(
|
|
||||||
rpc.get_protocol().await.unwrap(),
|
|
||||||
outputs,
|
|
||||||
vec![(addr, amount - 10000000000)],
|
|
||||||
Some(addr),
|
|
||||||
None,
|
|
||||||
fee,
|
|
||||||
)
|
|
||||||
.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(
|
|
||||||
&rpc,
|
|
||||||
keys[&i].clone(),
|
|
||||||
RecommendedTranscript::new(b"Monero Serai Test Transaction"),
|
|
||||||
rpc.get_height().await.unwrap() - 10,
|
|
||||||
(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async_sequential! {
|
|
||||||
async fn builder() {
|
|
||||||
let rpc = rpc().await;
|
|
||||||
|
|
||||||
// Generate an address
|
|
||||||
let spend = Zeroizing::new(random_scalar(&mut OsRng));
|
|
||||||
let view = random_scalar(&mut OsRng);
|
|
||||||
let spend_pub = spend.deref() * &ED25519_BASEPOINT_TABLE;
|
|
||||||
|
|
||||||
let view_pair = ViewPair::new(spend_pub, view);
|
|
||||||
let mut scanner = Scanner::from_view(view_pair, Network::Mainnet, Some(HashSet::new()));
|
|
||||||
let addr = scanner.address();
|
|
||||||
|
|
||||||
let fee = rpc.get_fee().await.unwrap();
|
|
||||||
|
|
||||||
let start = rpc.get_height().await.unwrap();
|
|
||||||
for _ in 0 .. 7 {
|
|
||||||
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let coinbase = rpc.get_block_transactions(start).await.unwrap().swap_remove(0);
|
|
||||||
let output = scanner.scan_transaction(&coinbase).ignore_timelock().swap_remove(0);
|
|
||||||
rpc.publish_transaction(
|
|
||||||
&SignableTransactionBuilder::new(rpc.get_protocol().await.unwrap(), fee, Some(addr))
|
|
||||||
.add_input(SpendableOutput::from(&rpc, output).await.unwrap())
|
|
||||||
.add_payment(addr, 0)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.sign(&mut OsRng, &rpc, &spend)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue