mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39:24 +00:00
Rename the coins folder to networks (#583)
* Rename the coins folder to networks Ethereum isn't a coin. It's a network. Resolves #357. * More renames of coins -> networks in orchestration * Correct paths in tests/ * cargo fmt
This commit is contained in:
81
networks/monero/wallet/tests/add_data.rs
Normal file
81
networks/monero/wallet/tests/add_data.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use monero_serai::transaction::Transaction;
|
||||
use monero_wallet::{rpc::Rpc, extra::MAX_ARBITRARY_DATA_SIZE, send::SendError};
|
||||
|
||||
mod runner;
|
||||
|
||||
test!(
|
||||
add_single_data_less_than_max,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
let arbitrary_data = vec![b'\0'; MAX_ARBITRARY_DATA_SIZE - 1];
|
||||
|
||||
// make sure we can add to tx
|
||||
builder.add_data(arbitrary_data.clone()).unwrap();
|
||||
|
||||
builder.add_payment(addr, 5);
|
||||
(builder.build().unwrap(), (arbitrary_data,))
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: (Vec<u8>,)| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.arbitrary_data()[0], data.0);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
add_multiple_data_less_than_max,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
let mut data = vec![];
|
||||
for b in 1 ..= 3 {
|
||||
data.push(vec![b; MAX_ARBITRARY_DATA_SIZE - 1]);
|
||||
}
|
||||
|
||||
// Add data multiple times
|
||||
for data in &data {
|
||||
builder.add_data(data.clone()).unwrap();
|
||||
}
|
||||
|
||||
builder.add_payment(addr, 5);
|
||||
(builder.build().unwrap(), data)
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: Vec<Vec<u8>>| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.arbitrary_data(), data);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
add_single_data_more_than_max,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
// Make a data that is bigger than the maximum
|
||||
let mut data = vec![b'a'; MAX_ARBITRARY_DATA_SIZE + 1];
|
||||
|
||||
// Make sure we get an error if we try to add it to the TX
|
||||
assert_eq!(builder.add_data(data.clone()), Err(SendError::TooMuchArbitraryData));
|
||||
|
||||
// Reduce data size and retry. The data will now be 255 bytes long (including the added
|
||||
// marker), exactly
|
||||
data.pop();
|
||||
builder.add_data(data.clone()).unwrap();
|
||||
|
||||
builder.add_payment(addr, 5);
|
||||
(builder.build().unwrap(), data)
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: Vec<u8>| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.arbitrary_data(), vec![data]);
|
||||
},
|
||||
),
|
||||
);
|
||||
165
networks/monero/wallet/tests/decoys.rs
Normal file
165
networks/monero/wallet/tests/decoys.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
DEFAULT_LOCK_WINDOW,
|
||||
transaction::Transaction,
|
||||
rpc::{Rpc, DecoyRpc},
|
||||
WalletOutput,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
|
||||
test!(
|
||||
select_latest_output_as_decoy_canonical,
|
||||
(
|
||||
// First make an initial tx0
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 2000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 2000000000000);
|
||||
output
|
||||
},
|
||||
),
|
||||
(
|
||||
// Then make a second tx1
|
||||
|rct_type: RctType, rpc: SimpleRequestRpc, mut builder: Builder, addr, state: _| async move {
|
||||
let output_tx0: WalletOutput = state;
|
||||
|
||||
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
builder.add_input(input);
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
},
|
||||
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|
||||
|rpc, _, tx: Transaction, _: Scanner, state: (_, _)| async move {
|
||||
use rand_core::OsRng;
|
||||
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let height = rpc.get_height().await.unwrap();
|
||||
|
||||
let most_recent_o_index = rpc.get_o_indexes(tx.hash()).await.unwrap().pop().unwrap();
|
||||
|
||||
// Make sure output from tx1 is in the block in which it unlocks
|
||||
let out_tx1 = rpc.get_outs(&[most_recent_o_index]).await.unwrap().swap_remove(0);
|
||||
assert_eq!(out_tx1.height, height - DEFAULT_LOCK_WINDOW);
|
||||
assert!(out_tx1.unlocked);
|
||||
|
||||
// Select decoys using spendable output from tx0 as the real, and make sure DSA selects
|
||||
// the freshly unlocked output from tx1 as a decoy
|
||||
let (rct_type, output_tx0): (RctType, WalletOutput) = state;
|
||||
let mut selected_fresh_decoy = false;
|
||||
let mut attempts = 1000;
|
||||
while !selected_fresh_decoy && attempts > 0 {
|
||||
let decoys = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
height,
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.decoys()
|
||||
.clone();
|
||||
|
||||
selected_fresh_decoy = decoys.positions().contains(&most_recent_o_index);
|
||||
attempts -= 1;
|
||||
}
|
||||
|
||||
assert!(selected_fresh_decoy);
|
||||
assert_eq!(height, rpc.get_height().await.unwrap());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
select_latest_output_as_decoy,
|
||||
(
|
||||
// First make an initial tx0
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 2000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 2000000000000);
|
||||
output
|
||||
},
|
||||
),
|
||||
(
|
||||
// Then make a second tx1
|
||||
|rct_type: RctType, rpc, mut builder: Builder, addr, output_tx0: WalletOutput| async move {
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let input = OutputWithDecoys::new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
builder.add_input(input);
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
|
||||
(builder.build().unwrap(), (rct_type, output_tx0))
|
||||
},
|
||||
// Then make sure DSA selects freshly unlocked output from tx1 as a decoy
|
||||
|rpc, _, tx: Transaction, _: Scanner, state: (_, _)| async move {
|
||||
use rand_core::OsRng;
|
||||
|
||||
let rpc: SimpleRequestRpc = rpc;
|
||||
|
||||
let height = rpc.get_height().await.unwrap();
|
||||
|
||||
let most_recent_o_index = rpc.get_o_indexes(tx.hash()).await.unwrap().pop().unwrap();
|
||||
|
||||
// Make sure output from tx1 is in the block in which it unlocks
|
||||
let out_tx1 = rpc.get_outs(&[most_recent_o_index]).await.unwrap().swap_remove(0);
|
||||
assert_eq!(out_tx1.height, height - DEFAULT_LOCK_WINDOW);
|
||||
assert!(out_tx1.unlocked);
|
||||
|
||||
// Select decoys using spendable output from tx0 as the real, and make sure DSA selects
|
||||
// the freshly unlocked output from tx1 as a decoy
|
||||
let (rct_type, output_tx0): (RctType, WalletOutput) = state;
|
||||
let mut selected_fresh_decoy = false;
|
||||
let mut attempts = 1000;
|
||||
while !selected_fresh_decoy && attempts > 0 {
|
||||
let decoys = OutputWithDecoys::new(
|
||||
&mut OsRng, // TODO: use a seeded RNG to consistently select the latest output
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
height,
|
||||
output_tx0.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.decoys()
|
||||
.clone();
|
||||
|
||||
selected_fresh_decoy = decoys.positions().contains(&most_recent_o_index);
|
||||
attempts -= 1;
|
||||
}
|
||||
|
||||
assert!(selected_fresh_decoy);
|
||||
assert_eq!(height, rpc.get_height().await.unwrap());
|
||||
},
|
||||
),
|
||||
);
|
||||
80
networks/monero/wallet/tests/eventuality.rs
Normal file
80
networks/monero/wallet/tests/eventuality.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use curve25519_dalek::constants::ED25519_BASEPOINT_POINT;
|
||||
|
||||
use monero_serai::transaction::Transaction;
|
||||
use monero_wallet::{
|
||||
rpc::Rpc,
|
||||
address::{AddressType, MoneroAddress},
|
||||
};
|
||||
|
||||
mod runner;
|
||||
|
||||
test!(
|
||||
eventuality,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// Add a standard address, a payment ID address, a subaddress, and a guaranteed address
|
||||
// Each have their own slight implications to eventualities
|
||||
builder.add_payment(
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Legacy,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
),
|
||||
1,
|
||||
);
|
||||
builder.add_payment(
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::LegacyIntegrated([0xaa; 8]),
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
),
|
||||
2,
|
||||
);
|
||||
builder.add_payment(
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Subaddress,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
),
|
||||
3,
|
||||
);
|
||||
builder.add_payment(
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Featured { subaddress: false, payment_id: None, guaranteed: true },
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
),
|
||||
4,
|
||||
);
|
||||
let tx = builder.build().unwrap();
|
||||
let eventuality = Eventuality::from(tx.clone());
|
||||
assert_eq!(
|
||||
eventuality,
|
||||
Eventuality::read::<&[u8]>(&mut eventuality.serialize().as_ref()).unwrap()
|
||||
);
|
||||
(tx, eventuality)
|
||||
},
|
||||
|_, _, mut tx: Transaction, _, eventuality: Eventuality| async move {
|
||||
// 4 explicitly outputs added and one change output
|
||||
assert_eq!(tx.prefix().outputs.len(), 5);
|
||||
|
||||
// The eventuality's available extra should be the actual TX's
|
||||
assert_eq!(tx.prefix().extra, eventuality.extra());
|
||||
|
||||
// The TX should match
|
||||
assert!(eventuality.matches(&tx.clone().into()));
|
||||
|
||||
// Mutate the TX
|
||||
let Transaction::V2 { proofs: Some(ref mut proofs), .. } = tx else {
|
||||
panic!("TX wasn't RingCT")
|
||||
};
|
||||
proofs.base.commitments[0] += ED25519_BASEPOINT_POINT;
|
||||
// Verify it no longer matches
|
||||
assert!(!eventuality.matches(&tx.clone().into()));
|
||||
},
|
||||
),
|
||||
);
|
||||
82
networks/monero/wallet/tests/runner/builder.rs
Normal file
82
networks/monero/wallet/tests/runner/builder.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use monero_wallet::{
|
||||
ringct::RctType,
|
||||
rpc::FeeRate,
|
||||
address::MoneroAddress,
|
||||
OutputWithDecoys,
|
||||
send::{Change, SendError, SignableTransaction},
|
||||
extra::MAX_ARBITRARY_DATA_SIZE,
|
||||
};
|
||||
|
||||
/// A builder for Monero transactions.
|
||||
#[derive(Clone, PartialEq, Eq, Zeroize, Debug)]
|
||||
pub struct SignableTransactionBuilder {
|
||||
rct_type: RctType,
|
||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||
inputs: Vec<OutputWithDecoys>,
|
||||
payments: Vec<(MoneroAddress, u64)>,
|
||||
change: Change,
|
||||
data: Vec<Vec<u8>>,
|
||||
fee_rate: FeeRate,
|
||||
}
|
||||
|
||||
impl SignableTransactionBuilder {
|
||||
pub fn new(
|
||||
rct_type: RctType,
|
||||
outgoing_view_key: Zeroizing<[u8; 32]>,
|
||||
change: Change,
|
||||
fee_rate: FeeRate,
|
||||
) -> Self {
|
||||
Self {
|
||||
rct_type,
|
||||
outgoing_view_key,
|
||||
inputs: vec![],
|
||||
payments: vec![],
|
||||
change,
|
||||
data: vec![],
|
||||
fee_rate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_input(&mut self, input: OutputWithDecoys) -> &mut Self {
|
||||
self.inputs.push(input);
|
||||
self
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_inputs(&mut self, inputs: &[OutputWithDecoys]) -> &mut Self {
|
||||
self.inputs.extend(inputs.iter().cloned());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_payment(&mut self, dest: MoneroAddress, amount: u64) -> &mut Self {
|
||||
self.payments.push((dest, amount));
|
||||
self
|
||||
}
|
||||
#[allow(unused)]
|
||||
pub fn add_payments(&mut self, payments: &[(MoneroAddress, u64)]) -> &mut Self {
|
||||
self.payments.extend(payments);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn add_data(&mut self, data: Vec<u8>) -> Result<&mut Self, SendError> {
|
||||
if data.len() > MAX_ARBITRARY_DATA_SIZE {
|
||||
Err(SendError::TooMuchArbitraryData)?;
|
||||
}
|
||||
self.data.push(data);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<SignableTransaction, SendError> {
|
||||
SignableTransaction::new(
|
||||
self.rct_type,
|
||||
self.outgoing_view_key,
|
||||
self.inputs,
|
||||
self.payments,
|
||||
self.change,
|
||||
self.data,
|
||||
self.fee_rate,
|
||||
)
|
||||
}
|
||||
}
|
||||
355
networks/monero/wallet/tests/runner/mod.rs
Normal file
355
networks/monero/wallet/tests/runner/mod.rs
Normal file
@@ -0,0 +1,355 @@
|
||||
use core::ops::Deref;
|
||||
use std_shims::sync::OnceLock;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::OsRng;
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
ringct::RctType,
|
||||
transaction::Transaction,
|
||||
block::Block,
|
||||
rpc::{Rpc, FeeRate},
|
||||
address::{Network, AddressType, MoneroAddress},
|
||||
DEFAULT_LOCK_WINDOW, ViewPair, GuaranteedViewPair, WalletOutput, Scanner,
|
||||
};
|
||||
|
||||
mod builder;
|
||||
pub use builder::SignableTransactionBuilder;
|
||||
|
||||
pub fn ring_len(rct_type: RctType) -> usize {
|
||||
match rct_type {
|
||||
RctType::ClsagBulletproof => 11,
|
||||
RctType::ClsagBulletproofPlus => 16,
|
||||
_ => panic!("ring size unknown for RctType"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_address() -> (Scalar, ViewPair, MoneroAddress) {
|
||||
let spend = Scalar::random(&mut OsRng);
|
||||
let spend_pub = &spend * ED25519_BASEPOINT_TABLE;
|
||||
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
(
|
||||
spend,
|
||||
ViewPair::new(spend_pub, view.clone()).unwrap(),
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Legacy,
|
||||
spend_pub,
|
||||
view.deref() * ED25519_BASEPOINT_TABLE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn random_guaranteed_address() -> (Scalar, GuaranteedViewPair, MoneroAddress) {
|
||||
let spend = Scalar::random(&mut OsRng);
|
||||
let spend_pub = &spend * ED25519_BASEPOINT_TABLE;
|
||||
let view = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
(
|
||||
spend,
|
||||
GuaranteedViewPair::new(spend_pub, view.clone()).unwrap(),
|
||||
MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Legacy,
|
||||
spend_pub,
|
||||
view.deref() * ED25519_BASEPOINT_TABLE,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Support transactions already on-chain
|
||||
// TODO: Don't have a side effect of mining blocks more blocks than needed under race conditions
|
||||
pub async fn mine_until_unlocked(
|
||||
rpc: &SimpleRequestRpc,
|
||||
addr: &MoneroAddress,
|
||||
tx_hash: [u8; 32],
|
||||
) -> Block {
|
||||
// mine until tx is in a block
|
||||
let mut height = rpc.get_height().await.unwrap();
|
||||
let mut found = false;
|
||||
let mut block = None;
|
||||
while !found {
|
||||
let inner_block = rpc.get_block_by_number(height - 1).await.unwrap();
|
||||
found = match inner_block.transactions.iter().find(|&&x| x == tx_hash) {
|
||||
Some(_) => {
|
||||
block = Some(inner_block);
|
||||
true
|
||||
}
|
||||
None => {
|
||||
height = rpc.generate_blocks(addr, 1).await.unwrap().1 + 1;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mine until tx's outputs are unlocked
|
||||
for _ in 0 .. (DEFAULT_LOCK_WINDOW - 1) {
|
||||
rpc.generate_blocks(addr, 1).await.unwrap();
|
||||
}
|
||||
|
||||
block.unwrap()
|
||||
}
|
||||
|
||||
// Mines 60 blocks and returns an unlocked miner TX output.
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> WalletOutput {
|
||||
let mut scanner = Scanner::new(view.clone());
|
||||
|
||||
// Mine 60 blocks to unlock a miner TX
|
||||
let start = rpc.get_height().await.unwrap();
|
||||
rpc.generate_blocks(&view.legacy_address(Network::Mainnet), 60).await.unwrap();
|
||||
|
||||
let block = rpc.get_block_by_number(start).await.unwrap();
|
||||
scanner.scan(rpc, &block).await.unwrap().ignore_additional_timelock().swap_remove(0)
|
||||
}
|
||||
|
||||
/// Make sure the weight and fee match the expected calculation.
|
||||
pub fn check_weight_and_fee(tx: &Transaction, fee_rate: FeeRate) {
|
||||
let Transaction::V2 { proofs: Some(ref proofs), .. } = tx else { panic!("TX wasn't RingCT") };
|
||||
let fee = proofs.base.fee;
|
||||
|
||||
let weight = tx.weight();
|
||||
let expected_weight = fee_rate.calculate_weight_from_fee(fee);
|
||||
assert_eq!(weight, expected_weight);
|
||||
|
||||
let expected_fee = fee_rate.calculate_fee_from_weight(weight);
|
||||
assert_eq!(fee, expected_fee);
|
||||
}
|
||||
|
||||
pub async fn rpc() -> SimpleRequestRpc {
|
||||
let rpc =
|
||||
SimpleRequestRpc::new("http://serai:seraidex@127.0.0.1:18081".to_string()).await.unwrap();
|
||||
|
||||
const BLOCKS_TO_MINE: usize = 110;
|
||||
|
||||
// Only run once
|
||||
if rpc.get_height().await.unwrap() > BLOCKS_TO_MINE {
|
||||
return rpc;
|
||||
}
|
||||
|
||||
let addr = MoneroAddress::new(
|
||||
Network::Mainnet,
|
||||
AddressType::Legacy,
|
||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||
);
|
||||
|
||||
// Mine enough blocks to ensure decoy availability
|
||||
rpc.generate_blocks(&addr, BLOCKS_TO_MINE).await.unwrap();
|
||||
|
||||
rpc
|
||||
}
|
||||
|
||||
pub static SEQUENTIAL: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! async_sequential {
|
||||
($(async fn $name: ident() $body: block)*) => {
|
||||
$(
|
||||
#[tokio::test]
|
||||
async fn $name() {
|
||||
let guard = runner::SEQUENTIAL.get_or_init(|| tokio::sync::Mutex::new(())).lock().await;
|
||||
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};
|
||||
#[cfg(feature = "multisig")]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, scalar::Scalar};
|
||||
|
||||
#[cfg(feature = "multisig")]
|
||||
use frost::{
|
||||
curve::Ed25519,
|
||||
Participant,
|
||||
tests::{THRESHOLD, key_gen},
|
||||
};
|
||||
|
||||
use monero_wallet::{
|
||||
ringct::RctType,
|
||||
rpc::FeePriority,
|
||||
address::Network,
|
||||
ViewPair, Scanner, OutputWithDecoys,
|
||||
send::{Change, SignableTransaction, Eventuality},
|
||||
};
|
||||
|
||||
use runner::{
|
||||
SignableTransactionBuilder, ring_len, random_address, rpc, mine_until_unlocked,
|
||||
get_miner_tx_output, check_weight_and_fee,
|
||||
};
|
||||
|
||||
type Builder = SignableTransactionBuilder;
|
||||
|
||||
// Run each function as both a single signer and as a multisig
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
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(Scalar::random(&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[&Participant::new(1).unwrap()].group_key().0
|
||||
};
|
||||
|
||||
let rpc = rpc().await;
|
||||
|
||||
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||
let view = ViewPair::new(spend_pub, view_priv.clone()).unwrap();
|
||||
let addr = view.legacy_address(Network::Mainnet);
|
||||
|
||||
let miner_tx = get_miner_tx_output(&rpc, &view).await;
|
||||
|
||||
let rct_type = match rpc.get_hardfork_version().await.unwrap() {
|
||||
14 => RctType::ClsagBulletproof,
|
||||
15 | 16 => RctType::ClsagBulletproofPlus,
|
||||
_ => panic!("unrecognized hardfork version"),
|
||||
};
|
||||
|
||||
let builder = SignableTransactionBuilder::new(
|
||||
rct_type,
|
||||
outgoing_view,
|
||||
Change::new(
|
||||
ViewPair::new(
|
||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||
Zeroizing::new(Scalar::random(&mut OsRng))
|
||||
).unwrap(),
|
||||
None,
|
||||
),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
);
|
||||
|
||||
let sign = |tx: SignableTransaction| {
|
||||
let spend = spend.clone();
|
||||
#[cfg(feature = "multisig")]
|
||||
let keys = keys.clone();
|
||||
|
||||
assert_eq!(&SignableTransaction::read(&mut tx.serialize().as_slice()).unwrap(), &tx);
|
||||
|
||||
let eventuality = Eventuality::from(tx.clone());
|
||||
|
||||
let tx = if !multisig {
|
||||
tx.sign(&mut OsRng, &spend).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).map(|i| Participant::new(i).unwrap()) {
|
||||
machines.insert(i, tx.clone().multisig(&keys[&i]).unwrap());
|
||||
}
|
||||
|
||||
frost::tests::sign_without_caching(&mut OsRng, machines, &[])
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(&eventuality.extra(), &tx.prefix().extra, "eventuality extra was distinct");
|
||||
assert!(eventuality.matches(&tx.clone().into()), "eventuality didn't match");
|
||||
|
||||
tx
|
||||
};
|
||||
|
||||
// TODO: Generate a distinct wallet for each transaction to prevent overlap
|
||||
let next_addr = addr;
|
||||
|
||||
let temp = Box::new({
|
||||
let mut builder = builder.clone();
|
||||
|
||||
let input = OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
&rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
miner_tx,
|
||||
).await.unwrap();
|
||||
builder.add_input(input);
|
||||
|
||||
let (tx, state) = ($first_tx)(rpc.clone(), builder, next_addr).await;
|
||||
let fee_rate = tx.fee_rate().clone();
|
||||
let signed = sign(tx);
|
||||
rpc.publish_transaction(&signed).await.unwrap();
|
||||
let block =
|
||||
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
||||
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
||||
check_weight_and_fee(&tx, fee_rate);
|
||||
let scanner = Scanner::new(view.clone());
|
||||
($first_checks)(rpc.clone(), block, tx, scanner, state).await
|
||||
});
|
||||
#[allow(unused_variables, unused_mut, unused_assignments)]
|
||||
let mut carried_state: Box<dyn Any> = temp;
|
||||
|
||||
$(
|
||||
let (tx, state) = ($tx)(
|
||||
rct_type,
|
||||
rpc.clone(),
|
||||
builder.clone(),
|
||||
next_addr,
|
||||
*carried_state.downcast().unwrap()
|
||||
).await;
|
||||
let fee_rate = tx.fee_rate().clone();
|
||||
let signed = sign(tx);
|
||||
rpc.publish_transaction(&signed).await.unwrap();
|
||||
let block =
|
||||
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
||||
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
||||
if stringify!($name) != "spend_one_input_to_two_outputs_no_change" {
|
||||
// Skip weight and fee check for the above test because when there is no change,
|
||||
// the change is added to the fee
|
||||
check_weight_and_fee(&tx, fee_rate);
|
||||
}
|
||||
#[allow(unused_assignments)]
|
||||
{
|
||||
let scanner = Scanner::new(view.clone());
|
||||
carried_state = Box::new(($checks)(rpc.clone(), block, tx, scanner, state).await);
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
networks/monero/wallet/tests/scan.rs
Normal file
166
networks/monero/wallet/tests/scan.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use monero_serai::transaction::Transaction;
|
||||
use monero_wallet::{rpc::Rpc, address::SubaddressIndex, extra::PaymentId, GuaranteedScanner};
|
||||
|
||||
mod runner;
|
||||
|
||||
test!(
|
||||
scan_standard_address,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let view = runner::random_address().1;
|
||||
let scanner = Scanner::new(view.clone());
|
||||
builder.add_payment(view.legacy_address(Network::Mainnet), 5);
|
||||
(builder.build().unwrap(), scanner)
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: Scanner| async move {
|
||||
let output = state.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
let dummy_payment_id = PaymentId::Encrypted([0u8; 8]);
|
||||
assert_eq!(output.payment_id(), Some(dummy_payment_id));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
scan_subaddress,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let subaddress = SubaddressIndex::new(0, 1).unwrap();
|
||||
|
||||
let view = runner::random_address().1;
|
||||
let mut scanner = Scanner::new(view.clone());
|
||||
scanner.register_subaddress(subaddress);
|
||||
|
||||
builder.add_payment(view.subaddress(Network::Mainnet, subaddress), 5);
|
||||
(builder.build().unwrap(), (scanner, subaddress))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
|
||||
let output =
|
||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.subaddress(), Some(state.1));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
scan_integrated_address,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let view = runner::random_address().1;
|
||||
let scanner = Scanner::new(view.clone());
|
||||
|
||||
let mut payment_id = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut payment_id);
|
||||
|
||||
builder.add_payment(view.legacy_integrated_address(Network::Mainnet, payment_id), 5);
|
||||
(builder.build().unwrap(), (scanner, payment_id))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
|
||||
let output =
|
||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
scan_guaranteed,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let view = runner::random_guaranteed_address().1;
|
||||
let scanner = GuaranteedScanner::new(view.clone());
|
||||
builder.add_payment(view.address(Network::Mainnet, None, None), 5);
|
||||
(builder.build().unwrap(), scanner)
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut scanner: GuaranteedScanner| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.subaddress(), None);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
scan_guaranteed_subaddress,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let subaddress = SubaddressIndex::new(0, 2).unwrap();
|
||||
|
||||
let view = runner::random_guaranteed_address().1;
|
||||
let mut scanner = GuaranteedScanner::new(view.clone());
|
||||
scanner.register_subaddress(subaddress);
|
||||
|
||||
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), None), 5);
|
||||
(builder.build().unwrap(), (scanner, subaddress))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (GuaranteedScanner, SubaddressIndex)| async move {
|
||||
let output =
|
||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.subaddress(), Some(state.1));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
scan_guaranteed_integrated,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let view = runner::random_guaranteed_address().1;
|
||||
let scanner = GuaranteedScanner::new(view.clone());
|
||||
let mut payment_id = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut payment_id);
|
||||
|
||||
builder.add_payment(view.address(Network::Mainnet, None, Some(payment_id)), 5);
|
||||
(builder.build().unwrap(), (scanner, payment_id))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (GuaranteedScanner, [u8; 8])| async move {
|
||||
let output =
|
||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
#[rustfmt::skip]
|
||||
test!(
|
||||
scan_guaranteed_integrated_subaddress,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
let subaddress = SubaddressIndex::new(0, 3).unwrap();
|
||||
|
||||
let view = runner::random_guaranteed_address().1;
|
||||
let mut scanner = GuaranteedScanner::new(view.clone());
|
||||
scanner.register_subaddress(subaddress);
|
||||
|
||||
let mut payment_id = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut payment_id);
|
||||
|
||||
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), Some(payment_id)), 5);
|
||||
(builder.build().unwrap(), (scanner, payment_id, subaddress))
|
||||
},
|
||||
|
|
||||
rpc,
|
||||
block,
|
||||
tx: Transaction,
|
||||
_,
|
||||
mut state: (GuaranteedScanner, [u8; 8], SubaddressIndex),
|
||||
| async move {
|
||||
let output = state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||
assert_eq!(output.subaddress(), Some(state.2));
|
||||
},
|
||||
),
|
||||
);
|
||||
394
networks/monero/wallet/tests/send.rs
Normal file
394
networks/monero/wallet/tests/send.rs
Normal file
@@ -0,0 +1,394 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
ringct::RctType, transaction::Transaction, rpc::Rpc, address::SubaddressIndex, extra::Extra,
|
||||
WalletOutput, OutputWithDecoys,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
use runner::{SignableTransactionBuilder, ring_len};
|
||||
|
||||
// Set up inputs, select decoys, then add them to the TX builder
|
||||
async fn add_inputs(
|
||||
rct_type: RctType,
|
||||
rpc: &SimpleRequestRpc,
|
||||
outputs: Vec<WalletOutput>,
|
||||
builder: &mut SignableTransactionBuilder,
|
||||
) {
|
||||
for output in outputs {
|
||||
builder.add_input(
|
||||
OutputWithDecoys::fingerprintable_deterministic_new(
|
||||
&mut OsRng,
|
||||
rpc,
|
||||
ring_len(rct_type),
|
||||
rpc.get_height().await.unwrap(),
|
||||
output,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test!(
|
||||
spend_miner_output,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 5);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 5);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
spend_multiple_outputs,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
builder.add_payment(addr, 2000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let mut outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 2);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
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
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type: RctType, rpc, mut builder: Builder, addr, outputs: Vec<WalletOutput>| async move {
|
||||
add_inputs(rct_type, &rpc, outputs, &mut builder).await;
|
||||
builder.add_payment(addr, 6);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 6);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
// Ideally, this would be single_R, yet it isn't feasible to apply allow(non_snake_case) here
|
||||
single_r_subaddress_send,
|
||||
(
|
||||
// Consume this builder for an output we can use in the future
|
||||
// This is needed because we can't get the input from the passed in builder
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type, rpc: SimpleRequestRpc, _, _, outputs: Vec<WalletOutput>| async move {
|
||||
use monero_wallet::rpc::FeePriority;
|
||||
|
||||
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||
let change_view =
|
||||
ViewPair::new(&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, view_priv.clone())
|
||||
.unwrap();
|
||||
|
||||
let mut builder = SignableTransactionBuilder::new(
|
||||
rct_type,
|
||||
outgoing_view,
|
||||
Change::new(change_view.clone(), None),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
);
|
||||
add_inputs(rct_type, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await;
|
||||
|
||||
// Send to a subaddress
|
||||
let sub_view = ViewPair::new(
|
||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||
Zeroizing::new(Scalar::random(&mut OsRng)),
|
||||
)
|
||||
.unwrap();
|
||||
builder
|
||||
.add_payment(sub_view.subaddress(Network::Mainnet, SubaddressIndex::new(0, 1).unwrap()), 1);
|
||||
(builder.build().unwrap(), (change_view, sub_view))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, views: (ViewPair, ViewPair)| async move {
|
||||
// Make sure the change can pick up its output
|
||||
let mut change_scanner = Scanner::new(views.0);
|
||||
assert!(
|
||||
change_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().len() == 1
|
||||
);
|
||||
|
||||
// Make sure the subaddress can pick up its output
|
||||
let mut sub_scanner = Scanner::new(views.1);
|
||||
sub_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
||||
let sub_outputs = sub_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert!(sub_outputs.len() == 1);
|
||||
assert_eq!(sub_outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(sub_outputs[0].commitment().amount, 1);
|
||||
assert!(sub_outputs[0].subaddress().unwrap().account() == 0);
|
||||
assert!(sub_outputs[0].subaddress().unwrap().address() == 1);
|
||||
|
||||
// Make sure only one R was included in TX extra
|
||||
assert!(Extra::read::<&[u8]>(&mut tx.prefix().extra.as_ref())
|
||||
.unwrap()
|
||||
.keys()
|
||||
.unwrap()
|
||||
.1
|
||||
.is_none());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
spend_one_input_to_one_output_plus_change,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 2000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 2000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type: RctType, rpc, mut builder: Builder, addr, outputs: Vec<WalletOutput>| async move {
|
||||
add_inputs(rct_type, &rpc, outputs, &mut builder).await;
|
||||
builder.add_payment(addr, 2);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let output =
|
||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
assert_eq!(output.commitment().amount, 2);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
spend_max_outputs,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type: RctType, rpc, mut builder: Builder, addr, outputs: Vec<WalletOutput>| async move {
|
||||
add_inputs(rct_type, &rpc, outputs, &mut builder).await;
|
||||
|
||||
for i in 0 .. 15 {
|
||||
builder.add_payment(addr, i + 1);
|
||||
}
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let mut scanned_tx = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
|
||||
let mut output_amounts = HashSet::new();
|
||||
for i in 0 .. 15 {
|
||||
output_amounts.insert(i + 1);
|
||||
}
|
||||
for _ in 0 .. 15 {
|
||||
let output = scanned_tx.swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
let amount = output.commitment().amount;
|
||||
assert!(output_amounts.remove(&amount));
|
||||
}
|
||||
assert_eq!(output_amounts.len(), 0);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
spend_max_outputs_to_subaddresses,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type: RctType, rpc, mut builder: Builder, _, outputs: Vec<WalletOutput>| async move {
|
||||
add_inputs(rct_type, &rpc, outputs, &mut builder).await;
|
||||
|
||||
let view = runner::random_address().1;
|
||||
let mut scanner = Scanner::new(view.clone());
|
||||
|
||||
let mut subaddresses = vec![];
|
||||
for i in 0 .. 15 {
|
||||
let subaddress = SubaddressIndex::new(0, i + 1).unwrap();
|
||||
scanner.register_subaddress(subaddress);
|
||||
|
||||
builder.add_payment(view.subaddress(Network::Mainnet, subaddress), u64::from(i + 1));
|
||||
subaddresses.push(subaddress);
|
||||
}
|
||||
|
||||
(builder.build().unwrap(), (scanner, subaddresses))
|
||||
},
|
||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, Vec<SubaddressIndex>)| async move {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scanned_tx = state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
|
||||
let mut output_amounts_by_subaddress = HashMap::new();
|
||||
for i in 0 .. 15 {
|
||||
output_amounts_by_subaddress.insert(u64::try_from(i + 1).unwrap(), state.1[i]);
|
||||
}
|
||||
for _ in 0 .. 15 {
|
||||
let output = scanned_tx.swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx.hash());
|
||||
let amount = output.commitment().amount;
|
||||
|
||||
assert_eq!(
|
||||
output.subaddress().unwrap(),
|
||||
output_amounts_by_subaddress.remove(&amount).unwrap()
|
||||
);
|
||||
}
|
||||
assert_eq!(output_amounts_by_subaddress.len(), 0);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
spend_one_input_to_two_outputs_no_change,
|
||||
(
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type, rpc: SimpleRequestRpc, _, addr, outputs: Vec<WalletOutput>| async move {
|
||||
use monero_wallet::rpc::FeePriority;
|
||||
|
||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||
let mut builder = SignableTransactionBuilder::new(
|
||||
rct_type,
|
||||
outgoing_view,
|
||||
Change::fingerprintable(None),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
);
|
||||
add_inputs(rct_type, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await;
|
||||
builder.add_payment(addr, 10000);
|
||||
builder.add_payment(addr, 50000);
|
||||
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let mut outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 2);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[1].transaction(), tx.hash());
|
||||
outputs.sort_by(|x, y| x.commitment().amount.cmp(&y.commitment().amount));
|
||||
assert_eq!(outputs[0].commitment().amount, 10000);
|
||||
assert_eq!(outputs[1].commitment().amount, 50000);
|
||||
|
||||
// The remainder should get shunted to fee, which is fingerprintable
|
||||
let Transaction::V2 { proofs: Some(ref proofs), .. } = tx else { panic!("TX wasn't RingCT") };
|
||||
assert_eq!(proofs.base.fee, 1000000000000 - 10000 - 50000);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
subaddress_change,
|
||||
(
|
||||
// Consume this builder for an output we can use in the future
|
||||
// This is needed because we can't get the input from the passed in builder
|
||||
|_, mut builder: Builder, addr| async move {
|
||||
builder.add_payment(addr, 1000000000000);
|
||||
(builder.build().unwrap(), ())
|
||||
},
|
||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||
outputs
|
||||
},
|
||||
),
|
||||
(
|
||||
|rct_type, rpc: SimpleRequestRpc, _, _, outputs: Vec<WalletOutput>| async move {
|
||||
use monero_wallet::rpc::FeePriority;
|
||||
|
||||
let view_priv = Zeroizing::new(Scalar::random(&mut OsRng));
|
||||
let mut outgoing_view = Zeroizing::new([0; 32]);
|
||||
OsRng.fill_bytes(outgoing_view.as_mut());
|
||||
let change_view =
|
||||
ViewPair::new(&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE, view_priv.clone())
|
||||
.unwrap();
|
||||
|
||||
let mut builder = SignableTransactionBuilder::new(
|
||||
rct_type,
|
||||
outgoing_view,
|
||||
Change::new(change_view.clone(), Some(SubaddressIndex::new(0, 1).unwrap())),
|
||||
rpc.get_fee_rate(FeePriority::Unimportant).await.unwrap(),
|
||||
);
|
||||
add_inputs(rct_type, &rpc, vec![outputs.first().unwrap().clone()], &mut builder).await;
|
||||
|
||||
// Send to a random address
|
||||
let view = ViewPair::new(
|
||||
&Scalar::random(&mut OsRng) * ED25519_BASEPOINT_TABLE,
|
||||
Zeroizing::new(Scalar::random(&mut OsRng)),
|
||||
)
|
||||
.unwrap();
|
||||
builder.add_payment(view.legacy_address(Network::Mainnet), 1);
|
||||
(builder.build().unwrap(), change_view)
|
||||
},
|
||||
|rpc, block, _, _, change_view: ViewPair| async move {
|
||||
// Make sure the change can pick up its output
|
||||
let mut change_scanner = Scanner::new(change_view);
|
||||
change_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
||||
let outputs = change_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
||||
assert!(outputs.len() == 1);
|
||||
assert!(outputs[0].subaddress().unwrap().account() == 0);
|
||||
assert!(outputs[0].subaddress().unwrap().address() == 1);
|
||||
},
|
||||
),
|
||||
);
|
||||
366
networks/monero/wallet/tests/wallet2_compatibility.rs
Normal file
366
networks/monero/wallet/tests/wallet2_compatibility.rs
Normal file
@@ -0,0 +1,366 @@
|
||||
use rand_core::{OsRng, RngCore};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
use monero_wallet::{
|
||||
transaction::Transaction,
|
||||
rpc::Rpc,
|
||||
address::{Network, SubaddressIndex, MoneroAddress},
|
||||
extra::{MAX_ARBITRARY_DATA_SIZE, Extra, PaymentId},
|
||||
Scanner,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum AddressSpec {
|
||||
Legacy,
|
||||
LegacyIntegrated([u8; 8]),
|
||||
Subaddress(SubaddressIndex),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct EmptyResponse {}
|
||||
|
||||
async fn make_integrated_address(rpc: &SimpleRequestRpc, payment_id: [u8; 8]) -> String {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct IntegratedAddressResponse {
|
||||
integrated_address: String,
|
||||
}
|
||||
|
||||
let res = rpc
|
||||
.json_rpc_call::<IntegratedAddressResponse>(
|
||||
"make_integrated_address",
|
||||
Some(json!({ "payment_id": hex::encode(payment_id) })),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
res.integrated_address
|
||||
}
|
||||
|
||||
async fn initialize_rpcs() -> (SimpleRequestRpc, SimpleRequestRpc, MoneroAddress) {
|
||||
let wallet_rpc = SimpleRequestRpc::new("http://127.0.0.1:18082".to_string()).await.unwrap();
|
||||
let daemon_rpc = runner::rpc().await;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddressResponse {
|
||||
address: String,
|
||||
}
|
||||
|
||||
let mut wallet_id = [0; 8];
|
||||
OsRng.fill_bytes(&mut wallet_id);
|
||||
let _: EmptyResponse = wallet_rpc
|
||||
.json_rpc_call(
|
||||
"create_wallet",
|
||||
Some(json!({ "filename": hex::encode(wallet_id), "language": "English" })),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let address: AddressResponse =
|
||||
wallet_rpc.json_rpc_call("get_address", Some(json!({ "account_index": 0 }))).await.unwrap();
|
||||
|
||||
// Fund the new wallet
|
||||
let address = MoneroAddress::from_str(Network::Mainnet, &address.address).unwrap();
|
||||
daemon_rpc.generate_blocks(&address, 70).await.unwrap();
|
||||
|
||||
(wallet_rpc, daemon_rpc, address)
|
||||
}
|
||||
|
||||
async fn from_wallet_rpc_to_self(spec: AddressSpec) {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, daemon_rpc, wallet_rpc_addr) = initialize_rpcs().await;
|
||||
|
||||
// make an addr
|
||||
let (_, view_pair, _) = runner::random_address();
|
||||
let addr = match spec {
|
||||
AddressSpec::Legacy => view_pair.legacy_address(Network::Mainnet),
|
||||
AddressSpec::LegacyIntegrated(payment_id) => {
|
||||
view_pair.legacy_integrated_address(Network::Mainnet, payment_id)
|
||||
}
|
||||
AddressSpec::Subaddress(index) => view_pair.subaddress(Network::Mainnet, index),
|
||||
};
|
||||
|
||||
// refresh & make a tx
|
||||
let _: EmptyResponse = wallet_rpc.json_rpc_call("refresh", None).await.unwrap();
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TransferResponse {
|
||||
tx_hash: String,
|
||||
}
|
||||
let tx: TransferResponse = wallet_rpc
|
||||
.json_rpc_call(
|
||||
"transfer",
|
||||
Some(json!({
|
||||
"destinations": [{"address": addr.to_string(), "amount": 1_000_000_000_000u64 }],
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let tx_hash = hex::decode(tx.tx_hash).unwrap().try_into().unwrap();
|
||||
|
||||
// TODO: Needs https://github.com/monero-project/monero/pull/9260
|
||||
// let fee_rate = daemon_rpc
|
||||
// .get_fee_rate(FeePriority::Unimportant)
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// unlock it
|
||||
let block = runner::mine_until_unlocked(&daemon_rpc, &wallet_rpc_addr, tx_hash).await;
|
||||
|
||||
// Create the scanner
|
||||
let mut scanner = Scanner::new(view_pair);
|
||||
if let AddressSpec::Subaddress(index) = spec {
|
||||
scanner.register_subaddress(index);
|
||||
}
|
||||
|
||||
// Retrieve it and scan it
|
||||
let output =
|
||||
scanner.scan(&daemon_rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
||||
assert_eq!(output.transaction(), tx_hash);
|
||||
|
||||
// TODO: Needs https://github.com/monero-project/monero/pull/9260
|
||||
// runner::check_weight_and_fee(&tx, fee_rate);
|
||||
|
||||
match spec {
|
||||
AddressSpec::Subaddress(index) => {
|
||||
assert_eq!(output.subaddress(), Some(index));
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted([0u8; 8])));
|
||||
}
|
||||
AddressSpec::LegacyIntegrated(payment_id) => {
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(payment_id)));
|
||||
assert_eq!(output.subaddress(), None);
|
||||
}
|
||||
AddressSpec::Legacy => {
|
||||
assert_eq!(output.subaddress(), None);
|
||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted([0u8; 8])));
|
||||
}
|
||||
}
|
||||
assert_eq!(output.commitment().amount, 1000000000000);
|
||||
}
|
||||
|
||||
async_sequential!(
|
||||
async fn receipt_of_wallet_rpc_tx_standard() {
|
||||
from_wallet_rpc_to_self(AddressSpec::Legacy).await;
|
||||
}
|
||||
|
||||
async fn receipt_of_wallet_rpc_tx_subaddress() {
|
||||
from_wallet_rpc_to_self(AddressSpec::Subaddress(SubaddressIndex::new(0, 1).unwrap())).await;
|
||||
}
|
||||
|
||||
async fn receipt_of_wallet_rpc_tx_integrated() {
|
||||
let mut payment_id = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut payment_id);
|
||||
from_wallet_rpc_to_self(AddressSpec::LegacyIntegrated(payment_id)).await;
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Deserialize)]
|
||||
struct Index {
|
||||
major: u32,
|
||||
minor: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Transfer {
|
||||
payment_id: String,
|
||||
subaddr_index: Index,
|
||||
amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TransfersResponse {
|
||||
transfer: Transfer,
|
||||
transfers: Vec<Transfer>,
|
||||
}
|
||||
|
||||
test!(
|
||||
send_to_wallet_rpc_standard,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await;
|
||||
|
||||
// add destination
|
||||
builder.add_payment(wallet_rpc_addr, 1000000);
|
||||
(builder.build().unwrap(), wallet_rpc)
|
||||
},
|
||||
|_, _, tx: Transaction, _, data: SimpleRequestRpc| async move {
|
||||
// confirm receipt
|
||||
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
|
||||
let transfer: TransfersResponse = data
|
||||
.json_rpc_call("get_transfer_by_txid", Some(json!({ "txid": hex::encode(tx.hash()) })))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(transfer.transfer.subaddr_index, Index { major: 0, minor: 0 });
|
||||
assert_eq!(transfer.transfer.amount, 1000000);
|
||||
assert_eq!(transfer.transfer.payment_id, hex::encode([0u8; 8]));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
send_to_wallet_rpc_subaddress,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, _, _) = initialize_rpcs().await;
|
||||
|
||||
// make the subaddress
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AccountResponse {
|
||||
address: String,
|
||||
account_index: u32,
|
||||
}
|
||||
let addr: AccountResponse = wallet_rpc.json_rpc_call("create_account", None).await.unwrap();
|
||||
assert!(addr.account_index != 0);
|
||||
|
||||
builder
|
||||
.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr.address).unwrap(), 1000000);
|
||||
(builder.build().unwrap(), (wallet_rpc, addr.account_index))
|
||||
},
|
||||
|_, _, tx: Transaction, _, data: (SimpleRequestRpc, u32)| async move {
|
||||
// confirm receipt
|
||||
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
|
||||
let transfer: TransfersResponse = data
|
||||
.0
|
||||
.json_rpc_call(
|
||||
"get_transfer_by_txid",
|
||||
Some(json!({ "txid": hex::encode(tx.hash()), "account_index": data.1 })),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(transfer.transfer.subaddr_index, Index { major: data.1, minor: 0 });
|
||||
assert_eq!(transfer.transfer.amount, 1000000);
|
||||
assert_eq!(transfer.transfer.payment_id, hex::encode([0u8; 8]));
|
||||
|
||||
// Make sure only one R was included in TX extra
|
||||
assert!(Extra::read::<&[u8]>(&mut tx.prefix().extra.as_ref())
|
||||
.unwrap()
|
||||
.keys()
|
||||
.unwrap()
|
||||
.1
|
||||
.is_none());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
send_to_wallet_rpc_subaddresses,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, daemon_rpc, _) = initialize_rpcs().await;
|
||||
|
||||
// make the subaddress
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AddressesResponse {
|
||||
addresses: Vec<String>,
|
||||
address_index: u32,
|
||||
}
|
||||
let addrs: AddressesResponse = wallet_rpc
|
||||
.json_rpc_call("create_address", Some(json!({ "account_index": 0, "count": 2 })))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(addrs.address_index != 0);
|
||||
assert!(addrs.addresses.len() == 2);
|
||||
|
||||
builder.add_payments(&[
|
||||
(MoneroAddress::from_str(Network::Mainnet, &addrs.addresses[0]).unwrap(), 1000000),
|
||||
(MoneroAddress::from_str(Network::Mainnet, &addrs.addresses[1]).unwrap(), 2000000),
|
||||
]);
|
||||
(builder.build().unwrap(), (wallet_rpc, daemon_rpc, addrs.address_index))
|
||||
},
|
||||
|_, _, tx: Transaction, _, data: (SimpleRequestRpc, SimpleRequestRpc, u32)| async move {
|
||||
// confirm receipt
|
||||
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
|
||||
let transfer: TransfersResponse = data
|
||||
.0
|
||||
.json_rpc_call(
|
||||
"get_transfer_by_txid",
|
||||
Some(json!({ "txid": hex::encode(tx.hash()), "account_index": 0 })),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(transfer.transfers.len(), 2);
|
||||
for t in transfer.transfers {
|
||||
match t.amount {
|
||||
1000000 => assert_eq!(t.subaddr_index, Index { major: 0, minor: data.2 }),
|
||||
2000000 => assert_eq!(t.subaddr_index, Index { major: 0, minor: data.2 + 1 }),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure 3 additional pub keys are included in TX extra
|
||||
let keys =
|
||||
Extra::read::<&[u8]>(&mut tx.prefix().extra.as_ref()).unwrap().keys().unwrap().1.unwrap();
|
||||
|
||||
assert_eq!(keys.len(), 3);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
send_to_wallet_rpc_integrated,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, _, _) = initialize_rpcs().await;
|
||||
|
||||
// make the addr
|
||||
let mut payment_id = [0u8; 8];
|
||||
OsRng.fill_bytes(&mut payment_id);
|
||||
let addr = make_integrated_address(&wallet_rpc, payment_id).await;
|
||||
|
||||
builder.add_payment(MoneroAddress::from_str(Network::Mainnet, &addr).unwrap(), 1000000);
|
||||
(builder.build().unwrap(), (wallet_rpc, payment_id))
|
||||
},
|
||||
|_, _, tx: Transaction, _, data: (SimpleRequestRpc, [u8; 8])| async move {
|
||||
// confirm receipt
|
||||
let _: EmptyResponse = data.0.json_rpc_call("refresh", None).await.unwrap();
|
||||
let transfer: TransfersResponse = data
|
||||
.0
|
||||
.json_rpc_call("get_transfer_by_txid", Some(json!({ "txid": hex::encode(tx.hash()) })))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(transfer.transfer.subaddr_index, Index { major: 0, minor: 0 });
|
||||
assert_eq!(transfer.transfer.payment_id, hex::encode(data.1));
|
||||
assert_eq!(transfer.transfer.amount, 1000000);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
test!(
|
||||
send_to_wallet_rpc_with_arb_data,
|
||||
(
|
||||
|_, mut builder: Builder, _| async move {
|
||||
// initialize rpc
|
||||
let (wallet_rpc, _, wallet_rpc_addr) = initialize_rpcs().await;
|
||||
|
||||
// add destination
|
||||
builder.add_payment(wallet_rpc_addr, 1000000);
|
||||
|
||||
// Make 2 data that is the full 255 bytes
|
||||
for _ in 0 .. 2 {
|
||||
let data = vec![b'a'; MAX_ARBITRARY_DATA_SIZE];
|
||||
builder.add_data(data).unwrap();
|
||||
}
|
||||
|
||||
(builder.build().unwrap(), wallet_rpc)
|
||||
},
|
||||
|_, _, tx: Transaction, _, data: SimpleRequestRpc| async move {
|
||||
// confirm receipt
|
||||
let _: EmptyResponse = data.json_rpc_call("refresh", None).await.unwrap();
|
||||
let transfer: TransfersResponse = data
|
||||
.json_rpc_call("get_transfer_by_txid", Some(json!({ "txid": hex::encode(tx.hash()) })))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(transfer.transfer.subaddr_index, Index { major: 0, minor: 0 });
|
||||
assert_eq!(transfer.transfer.amount, 1000000);
|
||||
},
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user