mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 04:09:23 +00:00
Add ScannableBlock abstraction in the RPC
Makes scanning synchronous and only error upon a malicious node/unplanned for hard fork.
This commit is contained in:
@@ -73,6 +73,19 @@ pub enum RpcError {
|
|||||||
InvalidPriority,
|
InvalidPriority,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A block which is able to be scanned.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct ScannableBlock {
|
||||||
|
/// The block which is being scanned.
|
||||||
|
pub block: Block,
|
||||||
|
/// The non-miner transactions within this block.
|
||||||
|
pub transactions: Vec<Transaction<Pruned>>,
|
||||||
|
/// The output index for the first RingCT output within this block.
|
||||||
|
///
|
||||||
|
/// None if there are no RingCT outputs within this block, Some otherwise.
|
||||||
|
pub output_index_for_first_ringct_output: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A struct containing a fee rate.
|
/// A struct containing a fee rate.
|
||||||
///
|
///
|
||||||
/// The fee rate is defined as a per-weight cost, along with a mask for rounding purposes.
|
/// The fee rate is defined as a per-weight cost, along with a mask for rounding purposes.
|
||||||
@@ -570,6 +583,95 @@ pub trait Rpc: Sync + Clone + Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a block's scannable form.
|
||||||
|
fn get_scannable_block(
|
||||||
|
&self,
|
||||||
|
block: Block,
|
||||||
|
) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
|
||||||
|
async move {
|
||||||
|
let transactions = self.get_pruned_transactions(&block.transactions).await?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Requesting the output index for each output we sucessfully scan would cause a loss of
|
||||||
|
privacy. We could instead request the output indexes for all outputs we scan, yet this
|
||||||
|
would notably increase the amount of RPC calls we make.
|
||||||
|
|
||||||
|
We solve this by requesting the output index for the first RingCT output in the block, which
|
||||||
|
should be within the miner transaction. Then, as we scan transactions, we update the output
|
||||||
|
index ourselves.
|
||||||
|
|
||||||
|
Please note we only will scan RingCT outputs so we only need to track the RingCT output
|
||||||
|
index. This decision was made due to spending CN outputs potentially having burdensome
|
||||||
|
requirements (the need to make a v1 TX due to insufficient decoys).
|
||||||
|
|
||||||
|
We bound ourselves to only scanning RingCT outputs by only scanning v2 transactions. This is
|
||||||
|
safe and correct since:
|
||||||
|
|
||||||
|
1) v1 transactions cannot create RingCT outputs.
|
||||||
|
|
||||||
|
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
||||||
|
/src/cryptonote_basic/cryptonote_format_utils.cpp#L866-L869
|
||||||
|
|
||||||
|
2) v2 miner transactions implicitly create RingCT outputs.
|
||||||
|
|
||||||
|
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
||||||
|
/src/blockchain_db/blockchain_db.cpp#L232-L241
|
||||||
|
|
||||||
|
3) v2 transactions must create RingCT outputs.
|
||||||
|
|
||||||
|
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c45
|
||||||
|
/src/cryptonote_core/blockchain.cpp#L3055-L3065
|
||||||
|
|
||||||
|
That does bound on the hard fork version being >= 3, yet all v2 TXs have a hard fork
|
||||||
|
version > 3.
|
||||||
|
|
||||||
|
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
||||||
|
/src/cryptonote_core/blockchain.cpp#L3417
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get the index for the first output
|
||||||
|
let mut output_index_for_first_ringct_output = None;
|
||||||
|
let miner_tx_hash = block.miner_transaction.hash();
|
||||||
|
let miner_tx = Transaction::<Pruned>::from(block.miner_transaction.clone());
|
||||||
|
for (hash, tx) in core::iter::once((&miner_tx_hash, &miner_tx))
|
||||||
|
.chain(block.transactions.iter().zip(&transactions))
|
||||||
|
{
|
||||||
|
// If this isn't a RingCT output, or there are no outputs, move to the next TX
|
||||||
|
if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = *self.get_o_indexes(*hash).await?.first().ok_or_else(|| {
|
||||||
|
RpcError::InvalidNode(
|
||||||
|
"requested output indexes for a TX with outputs and got none".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
output_index_for_first_ringct_output = Some(index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ScannableBlock { block, transactions, output_index_for_first_ringct_output })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a block's scannable form by its hash.
|
||||||
|
// TODO: get_blocks.bin
|
||||||
|
fn get_scannable_block_by_hash(
|
||||||
|
&self,
|
||||||
|
hash: [u8; 32],
|
||||||
|
) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
|
||||||
|
async move { self.get_scannable_block(self.get_block(hash).await?).await }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a block's scannable form by its number.
|
||||||
|
// TODO: get_blocks_by_height.bin
|
||||||
|
fn get_scannable_block_by_number(
|
||||||
|
&self,
|
||||||
|
number: usize,
|
||||||
|
) -> impl Send + Future<Output = Result<ScannableBlock, RpcError>> {
|
||||||
|
async move { self.get_scannable_block(self.get_block_by_number(number).await?).await }
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the currently estimated fee rate from the node.
|
/// Get the currently estimated fee rate from the node.
|
||||||
///
|
///
|
||||||
/// This may be manipulated to unsafe levels and MUST be sanity checked.
|
/// This may be manipulated to unsafe levels and MUST be sanity checked.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub use monero_rpc as rpc;
|
|||||||
pub use monero_address as address;
|
pub use monero_address as address;
|
||||||
|
|
||||||
mod view_pair;
|
mod view_pair;
|
||||||
pub use view_pair::{ViewPair, GuaranteedViewPair};
|
pub use view_pair::{ViewPairError, ViewPair, GuaranteedViewPair};
|
||||||
|
|
||||||
/// Structures and functionality for working with transactions' extra fields.
|
/// Structures and functionality for working with transactions' extra fields.
|
||||||
pub mod extra;
|
pub mod extra;
|
||||||
@@ -33,7 +33,7 @@ pub(crate) mod output;
|
|||||||
pub use output::WalletOutput;
|
pub use output::WalletOutput;
|
||||||
|
|
||||||
mod scan;
|
mod scan;
|
||||||
pub use scan::{Scanner, GuaranteedScanner};
|
pub use scan::{ScanError, Scanner, GuaranteedScanner};
|
||||||
|
|
||||||
mod decoys;
|
mod decoys;
|
||||||
pub use decoys::OutputWithDecoys;
|
pub use decoys::OutputWithDecoys;
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std_shims::{alloc::format, vec, vec::Vec, string::ToString, collections::HashMap};
|
use std_shims::{vec, vec::Vec, collections::HashMap};
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||||
|
|
||||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, edwards::CompressedEdwardsY};
|
use curve25519_dalek::{constants::ED25519_BASEPOINT_TABLE, edwards::CompressedEdwardsY};
|
||||||
|
|
||||||
use monero_rpc::{RpcError, Rpc};
|
use monero_rpc::ScannableBlock;
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
io::*,
|
io::*,
|
||||||
primitives::Commitment,
|
primitives::Commitment,
|
||||||
transaction::{Timelock, Pruned, Transaction},
|
transaction::{Timelock, Pruned, Transaction},
|
||||||
block::Block,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
address::SubaddressIndex, ViewPair, GuaranteedViewPair, output::*, PaymentId, Extra,
|
address::SubaddressIndex, ViewPair, GuaranteedViewPair, output::*, PaymentId, Extra,
|
||||||
@@ -67,6 +66,18 @@ impl Timelocked {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors when scanning a block.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "std", derive(thiserror::Error))]
|
||||||
|
pub enum ScanError {
|
||||||
|
/// The block was for an unsupported protocol version.
|
||||||
|
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]
|
||||||
|
UnsupportedProtocol(u8),
|
||||||
|
/// The ScannableBlock was invalid.
|
||||||
|
#[cfg_attr(feature = "std", error("invalid scannable block ({0})"))]
|
||||||
|
InvalidScannableBlock(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct InternalScanner {
|
struct InternalScanner {
|
||||||
pair: ViewPair,
|
pair: ViewPair,
|
||||||
@@ -107,10 +118,10 @@ impl InternalScanner {
|
|||||||
|
|
||||||
fn scan_transaction(
|
fn scan_transaction(
|
||||||
&self,
|
&self,
|
||||||
tx_start_index_on_blockchain: u64,
|
output_index_for_first_ringct_output: u64,
|
||||||
tx_hash: [u8; 32],
|
tx_hash: [u8; 32],
|
||||||
tx: &Transaction<Pruned>,
|
tx: &Transaction<Pruned>,
|
||||||
) -> Result<Timelocked, RpcError> {
|
) -> Result<Timelocked, ScanError> {
|
||||||
// Only scan TXs creating RingCT outputs
|
// Only scan TXs creating RingCT outputs
|
||||||
// For the full details on why this check is equivalent, please see the documentation in `scan`
|
// For the full details on why this check is equivalent, please see the documentation in `scan`
|
||||||
if tx.version() != 2 {
|
if tx.version() != 2 {
|
||||||
@@ -197,14 +208,14 @@ impl InternalScanner {
|
|||||||
} else {
|
} else {
|
||||||
let Transaction::V2 { proofs: Some(ref proofs), .. } = &tx else {
|
let Transaction::V2 { proofs: Some(ref proofs), .. } = &tx else {
|
||||||
// Invalid transaction, as of consensus rules at the time of writing this code
|
// Invalid transaction, as of consensus rules at the time of writing this code
|
||||||
Err(RpcError::InvalidNode("non-miner v2 transaction without RCT proofs".to_string()))?
|
Err(ScanError::InvalidScannableBlock("non-miner v2 transaction without RCT proofs"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
commitment = match proofs.base.encrypted_amounts.get(o) {
|
commitment = match proofs.base.encrypted_amounts.get(o) {
|
||||||
Some(amount) => output_derivations.decrypt(amount),
|
Some(amount) => output_derivations.decrypt(amount),
|
||||||
// Invalid transaction, as of consensus rules at the time of writing this code
|
// Invalid transaction, as of consensus rules at the time of writing this code
|
||||||
None => Err(RpcError::InvalidNode(
|
None => Err(ScanError::InvalidScannableBlock(
|
||||||
"RCT proofs without an encrypted amount per output".to_string(),
|
"RCT proofs without an encrypted amount per output",
|
||||||
))?,
|
))?,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,7 +234,7 @@ impl InternalScanner {
|
|||||||
index_in_transaction: o.try_into().unwrap(),
|
index_in_transaction: o.try_into().unwrap(),
|
||||||
},
|
},
|
||||||
relative_id: RelativeId {
|
relative_id: RelativeId {
|
||||||
index_on_blockchain: tx_start_index_on_blockchain + u64::try_from(o).unwrap(),
|
index_on_blockchain: output_index_for_first_ringct_output + u64::try_from(o).unwrap(),
|
||||||
},
|
},
|
||||||
data: OutputData { key: output_key, key_offset, commitment },
|
data: OutputData { key: output_key, key_offset, commitment },
|
||||||
metadata: Metadata {
|
metadata: Metadata {
|
||||||
@@ -243,12 +254,22 @@ impl InternalScanner {
|
|||||||
Ok(Timelocked(res))
|
Ok(Timelocked(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
fn scan(&mut self, block: ScannableBlock) -> Result<Timelocked, ScanError> {
|
||||||
|
// This is the output index for the first RingCT output within the block
|
||||||
|
// We mutate it to be the output index for the first RingCT for each transaction
|
||||||
|
let ScannableBlock { block, transactions, output_index_for_first_ringct_output } = block;
|
||||||
|
if block.transactions.len() != transactions.len() {
|
||||||
|
Err(ScanError::InvalidScannableBlock(
|
||||||
|
"scanning a ScannableBlock with more/less transactions than it should have",
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
let Some(mut output_index_for_first_ringct_output) = output_index_for_first_ringct_output
|
||||||
|
else {
|
||||||
|
return Ok(Timelocked(vec![]));
|
||||||
|
};
|
||||||
|
|
||||||
if block.header.hardfork_version > 16 {
|
if block.header.hardfork_version > 16 {
|
||||||
Err(RpcError::InternalError(format!(
|
Err(ScanError::UnsupportedProtocol(block.header.hardfork_version))?;
|
||||||
"scanning a hardfork {} block, when we only support up to 16",
|
|
||||||
block.header.hardfork_version
|
|
||||||
)))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We obtain all TXs in full
|
// We obtain all TXs in full
|
||||||
@@ -256,80 +277,17 @@ impl InternalScanner {
|
|||||||
block.miner_transaction.hash(),
|
block.miner_transaction.hash(),
|
||||||
Transaction::<Pruned>::from(block.miner_transaction.clone()),
|
Transaction::<Pruned>::from(block.miner_transaction.clone()),
|
||||||
)];
|
)];
|
||||||
let txs = rpc.get_pruned_transactions(&block.transactions).await?;
|
for (hash, tx) in block.transactions.iter().zip(transactions) {
|
||||||
for (hash, tx) in block.transactions.iter().zip(txs) {
|
|
||||||
txs_with_hashes.push((*hash, tx));
|
txs_with_hashes.push((*hash, tx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Requesting the output index for each output we sucessfully scan would cause a loss of privacy
|
|
||||||
We could instead request the output indexes for all outputs we scan, yet this would notably
|
|
||||||
increase the amount of RPC calls we make.
|
|
||||||
|
|
||||||
We solve this by requesting the output index for the first RingCT output in the block, which
|
|
||||||
should be within the miner transaction. Then, as we scan transactions, we update the output
|
|
||||||
index ourselves.
|
|
||||||
|
|
||||||
Please note we only will scan RingCT outputs so we only need to track the RingCT output
|
|
||||||
index. This decision was made due to spending CN outputs potentially having burdensome
|
|
||||||
requirements (the need to make a v1 TX due to insufficient decoys).
|
|
||||||
|
|
||||||
We bound ourselves to only scanning RingCT outputs by only scanning v2 transactions. This is
|
|
||||||
safe and correct since:
|
|
||||||
|
|
||||||
1) v1 transactions cannot create RingCT outputs.
|
|
||||||
|
|
||||||
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
|
||||||
/src/cryptonote_basic/cryptonote_format_utils.cpp#L866-L869
|
|
||||||
|
|
||||||
2) v2 miner transactions implicitly create RingCT outputs.
|
|
||||||
|
|
||||||
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
|
||||||
/src/blockchain_db/blockchain_db.cpp#L232-L241
|
|
||||||
|
|
||||||
3) v2 transactions must create RingCT outputs.
|
|
||||||
|
|
||||||
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c45
|
|
||||||
/src/cryptonote_core/blockchain.cpp#L3055-L3065
|
|
||||||
|
|
||||||
That does bound on the hard fork version being >= 3, yet all v2 TXs have a hard fork
|
|
||||||
version > 3.
|
|
||||||
|
|
||||||
https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454
|
|
||||||
/src/cryptonote_core/blockchain.cpp#L3417
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get the starting index
|
|
||||||
let mut tx_start_index_on_blockchain = {
|
|
||||||
let mut tx_start_index_on_blockchain = None;
|
|
||||||
for (hash, tx) in &txs_with_hashes {
|
|
||||||
// If this isn't a RingCT output, or there are no outputs, move to the next TX
|
|
||||||
if (!matches!(tx, Transaction::V2 { .. })) || tx.prefix().outputs.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = *rpc.get_o_indexes(*hash).await?.first().ok_or_else(|| {
|
|
||||||
RpcError::InvalidNode(
|
|
||||||
"requested output indexes for a TX with outputs and got none".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
tx_start_index_on_blockchain = Some(index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Some(tx_start_index_on_blockchain) = tx_start_index_on_blockchain else {
|
|
||||||
// Block had no RingCT outputs
|
|
||||||
return Ok(Timelocked(vec![]));
|
|
||||||
};
|
|
||||||
tx_start_index_on_blockchain
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut res = Timelocked(vec![]);
|
let mut res = Timelocked(vec![]);
|
||||||
for (hash, tx) in txs_with_hashes {
|
for (hash, tx) in txs_with_hashes {
|
||||||
// Push all outputs into our result
|
// Push all outputs into our result
|
||||||
{
|
{
|
||||||
let mut this_txs_outputs = vec![];
|
let mut this_txs_outputs = vec![];
|
||||||
core::mem::swap(
|
core::mem::swap(
|
||||||
&mut self.scan_transaction(tx_start_index_on_blockchain, hash, &tx)?.0,
|
&mut self.scan_transaction(output_index_for_first_ringct_output, hash, &tx)?.0,
|
||||||
&mut this_txs_outputs,
|
&mut this_txs_outputs,
|
||||||
);
|
);
|
||||||
res.0.extend(this_txs_outputs);
|
res.0.extend(this_txs_outputs);
|
||||||
@@ -337,7 +295,7 @@ impl InternalScanner {
|
|||||||
|
|
||||||
// Update the RingCT starting index for the next TX
|
// Update the RingCT starting index for the next TX
|
||||||
if matches!(tx, Transaction::V2 { .. }) {
|
if matches!(tx, Transaction::V2 { .. }) {
|
||||||
tx_start_index_on_blockchain += u64::try_from(tx.prefix().outputs.len()).unwrap()
|
output_index_for_first_ringct_output += u64::try_from(tx.prefix().outputs.len()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,8 +342,8 @@ impl Scanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Scan a block.
|
/// Scan a block.
|
||||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
pub fn scan(&mut self, block: ScannableBlock) -> Result<Timelocked, ScanError> {
|
||||||
self.0.scan(rpc, block).await
|
self.0.scan(block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +371,7 @@ impl GuaranteedScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Scan a block.
|
/// Scan a block.
|
||||||
pub async fn scan(&mut self, rpc: &impl Rpc, block: &Block) -> Result<Timelocked, RpcError> {
|
pub fn scan(&mut self, block: ScannableBlock) -> Result<Timelocked, ScanError> {
|
||||||
self.0.scan(rpc, block).await
|
self.0.scan(block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use monero_serai::transaction::Transaction;
|
use monero_serai::transaction::Transaction;
|
||||||
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||||
use monero_wallet::{rpc::Rpc, extra::MAX_ARBITRARY_DATA_SIZE, send::SendError};
|
use monero_wallet::{rpc::Rpc, extra::MAX_ARBITRARY_DATA_SIZE, send::SendError};
|
||||||
|
|
||||||
mod runner;
|
mod runner;
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
type SRR = SimpleRequestRpc;
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
add_single_data_less_than_max,
|
add_single_data_less_than_max,
|
||||||
(
|
(
|
||||||
@@ -15,9 +19,8 @@ test!(
|
|||||||
builder.add_payment(addr, 5);
|
builder.add_payment(addr, 5);
|
||||||
(builder.build().unwrap(), (arbitrary_data,))
|
(builder.build().unwrap(), (arbitrary_data,))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: (Vec<u8>,)| async move {
|
|_rpc: SRR, block, tx: Transaction, mut scanner: Scanner, data: (Vec<u8>,)| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.arbitrary_data()[0], data.0);
|
assert_eq!(output.arbitrary_data()[0], data.0);
|
||||||
@@ -42,9 +45,8 @@ test!(
|
|||||||
builder.add_payment(addr, 5);
|
builder.add_payment(addr, 5);
|
||||||
(builder.build().unwrap(), data)
|
(builder.build().unwrap(), data)
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: Vec<Vec<u8>>| async move {
|
|_rpc: SRR, block, tx: Transaction, mut scanner: Scanner, data: Vec<Vec<u8>>| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.arbitrary_data(), data);
|
assert_eq!(output.arbitrary_data(), data);
|
||||||
@@ -70,9 +72,8 @@ test!(
|
|||||||
builder.add_payment(addr, 5);
|
builder.add_payment(addr, 5);
|
||||||
(builder.build().unwrap(), data)
|
(builder.build().unwrap(), data)
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, data: Vec<u8>| async move {
|
|_rpc: SRR, block, tx: Transaction, mut scanner: Scanner, data: Vec<u8>| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.arbitrary_data(), vec![data]);
|
assert_eq!(output.arbitrary_data(), vec![data]);
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ test!(
|
|||||||
builder.add_payment(addr, 2000000000000);
|
builder.add_payment(addr, 2000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 2000000000000);
|
assert_eq!(output.commitment().amount, 2000000000000);
|
||||||
output
|
output
|
||||||
@@ -94,9 +93,8 @@ test!(
|
|||||||
builder.add_payment(addr, 2000000000000);
|
builder.add_payment(addr, 2000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 2000000000000);
|
assert_eq!(output.commitment().amount, 2000000000000);
|
||||||
output
|
output
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ pub async fn get_miner_tx_output(rpc: &SimpleRequestRpc, view: &ViewPair) -> Wal
|
|||||||
rpc.generate_blocks(&view.legacy_address(Network::Mainnet), 60).await.unwrap();
|
rpc.generate_blocks(&view.legacy_address(Network::Mainnet), 60).await.unwrap();
|
||||||
|
|
||||||
let block = rpc.get_block_by_number(start).await.unwrap();
|
let block = rpc.get_block_by_number(start).await.unwrap();
|
||||||
scanner.scan(rpc, &block).await.unwrap().ignore_additional_timelock().swap_remove(0)
|
scanner
|
||||||
|
.scan(rpc.get_scannable_block(block).await.unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.ignore_additional_timelock()
|
||||||
|
.swap_remove(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure the weight and fee match the expected calculation.
|
/// Make sure the weight and fee match the expected calculation.
|
||||||
@@ -315,6 +319,7 @@ macro_rules! test {
|
|||||||
rpc.publish_transaction(&signed).await.unwrap();
|
rpc.publish_transaction(&signed).await.unwrap();
|
||||||
let block =
|
let block =
|
||||||
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
||||||
|
let block = rpc.get_scannable_block(block).await.unwrap();
|
||||||
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
||||||
check_weight_and_fee(&tx, fee_rate);
|
check_weight_and_fee(&tx, fee_rate);
|
||||||
let scanner = Scanner::new(view.clone());
|
let scanner = Scanner::new(view.clone());
|
||||||
@@ -336,6 +341,7 @@ macro_rules! test {
|
|||||||
rpc.publish_transaction(&signed).await.unwrap();
|
rpc.publish_transaction(&signed).await.unwrap();
|
||||||
let block =
|
let block =
|
||||||
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
mine_until_unlocked(&rpc, &random_address().2, signed.hash()).await;
|
||||||
|
let block = rpc.get_scannable_block(block).await.unwrap();
|
||||||
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
let tx = rpc.get_transaction(signed.hash()).await.unwrap();
|
||||||
if stringify!($name) != "spend_one_input_to_two_outputs_no_change" {
|
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,
|
// Skip weight and fee check for the above test because when there is no change,
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
use monero_serai::transaction::Transaction;
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||||
use monero_wallet::{rpc::Rpc, address::SubaddressIndex, extra::PaymentId, GuaranteedScanner};
|
use monero_wallet::{
|
||||||
|
transaction::Transaction, rpc::Rpc, address::SubaddressIndex, extra::PaymentId, GuaranteedScanner,
|
||||||
|
};
|
||||||
|
|
||||||
mod runner;
|
mod runner;
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
type SRR = SimpleRequestRpc;
|
||||||
|
type Tx = Transaction;
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
scan_standard_address,
|
scan_standard_address,
|
||||||
(
|
(
|
||||||
@@ -12,8 +18,8 @@ test!(
|
|||||||
builder.add_payment(view.legacy_address(Network::Mainnet), 5);
|
builder.add_payment(view.legacy_address(Network::Mainnet), 5);
|
||||||
(builder.build().unwrap(), scanner)
|
(builder.build().unwrap(), scanner)
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: Scanner| async move {
|
|_rpc: SRR, block, tx: Transaction, _, mut state: Scanner| async move {
|
||||||
let output = state.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
let output = state.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
let dummy_payment_id = PaymentId::Encrypted([0u8; 8]);
|
let dummy_payment_id = PaymentId::Encrypted([0u8; 8]);
|
||||||
@@ -35,9 +41,8 @@ test!(
|
|||||||
builder.add_payment(view.subaddress(Network::Mainnet, subaddress), 5);
|
builder.add_payment(view.subaddress(Network::Mainnet, subaddress), 5);
|
||||||
(builder.build().unwrap(), (scanner, subaddress))
|
(builder.build().unwrap(), (scanner, subaddress))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
|
|_rpc: SRR, block, tx: Transaction, _, mut state: (Scanner, SubaddressIndex)| async move {
|
||||||
let output =
|
let output = state.0.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.subaddress(), Some(state.1));
|
assert_eq!(output.subaddress(), Some(state.1));
|
||||||
@@ -58,9 +63,8 @@ test!(
|
|||||||
builder.add_payment(view.legacy_integrated_address(Network::Mainnet, payment_id), 5);
|
builder.add_payment(view.legacy_integrated_address(Network::Mainnet, payment_id), 5);
|
||||||
(builder.build().unwrap(), (scanner, payment_id))
|
(builder.build().unwrap(), (scanner, payment_id))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
|
|_rpc: SRR, block, tx: Transaction, _, mut state: (Scanner, [u8; 8])| async move {
|
||||||
let output =
|
let output = state.0.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||||
@@ -77,9 +81,8 @@ test!(
|
|||||||
builder.add_payment(view.address(Network::Mainnet, None, None), 5);
|
builder.add_payment(view.address(Network::Mainnet, None, None), 5);
|
||||||
(builder.build().unwrap(), scanner)
|
(builder.build().unwrap(), scanner)
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut scanner: GuaranteedScanner| async move {
|
|_rpc: SRR, block, tx: Transaction, _, mut scanner: GuaranteedScanner| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.subaddress(), None);
|
assert_eq!(output.subaddress(), None);
|
||||||
@@ -100,9 +103,8 @@ test!(
|
|||||||
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), None), 5);
|
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), None), 5);
|
||||||
(builder.build().unwrap(), (scanner, subaddress))
|
(builder.build().unwrap(), (scanner, subaddress))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: (GuaranteedScanner, SubaddressIndex)| async move {
|
|_rpc: SRR, block, tx: Tx, _, mut state: (GuaranteedScanner, SubaddressIndex)| async move {
|
||||||
let output =
|
let output = state.0.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.subaddress(), Some(state.1));
|
assert_eq!(output.subaddress(), Some(state.1));
|
||||||
@@ -122,9 +124,8 @@ test!(
|
|||||||
builder.add_payment(view.address(Network::Mainnet, None, Some(payment_id)), 5);
|
builder.add_payment(view.address(Network::Mainnet, None, Some(payment_id)), 5);
|
||||||
(builder.build().unwrap(), (scanner, payment_id))
|
(builder.build().unwrap(), (scanner, payment_id))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: (GuaranteedScanner, [u8; 8])| async move {
|
|_rpc: SRR, block, tx: Transaction, _, mut state: (GuaranteedScanner, [u8; 8])| async move {
|
||||||
let output =
|
let output = state.0.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||||
@@ -132,7 +133,6 @@ test!(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
test!(
|
test!(
|
||||||
scan_guaranteed_integrated_subaddress,
|
scan_guaranteed_integrated_subaddress,
|
||||||
(
|
(
|
||||||
@@ -149,14 +149,8 @@ test!(
|
|||||||
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), Some(payment_id)), 5);
|
builder.add_payment(view.address(Network::Mainnet, Some(subaddress), Some(payment_id)), 5);
|
||||||
(builder.build().unwrap(), (scanner, payment_id, subaddress))
|
(builder.build().unwrap(), (scanner, payment_id, subaddress))
|
||||||
},
|
},
|
||||||
|
|
|_rpc, block, tx: Tx, _, mut state: (GuaranteedScanner, [u8; 8], SubaddressIndex)| async move {
|
||||||
rpc,
|
let output = state.0.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
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.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
assert_eq!(output.payment_id(), Some(PaymentId::Encrypted(state.1)));
|
||||||
|
|||||||
@@ -4,13 +4,21 @@ use rand_core::OsRng;
|
|||||||
|
|
||||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||||
use monero_wallet::{
|
use monero_wallet::{
|
||||||
ringct::RctType, transaction::Transaction, rpc::Rpc, address::SubaddressIndex, extra::Extra,
|
ringct::RctType,
|
||||||
|
transaction::Transaction,
|
||||||
|
rpc::{ScannableBlock, Rpc},
|
||||||
|
address::SubaddressIndex,
|
||||||
|
extra::Extra,
|
||||||
WalletOutput, OutputWithDecoys,
|
WalletOutput, OutputWithDecoys,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod runner;
|
mod runner;
|
||||||
use runner::{SignableTransactionBuilder, ring_len};
|
use runner::{SignableTransactionBuilder, ring_len};
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
type SRR = SimpleRequestRpc;
|
||||||
|
type SB = ScannableBlock;
|
||||||
|
|
||||||
// Set up inputs, select decoys, then add them to the TX builder
|
// Set up inputs, select decoys, then add them to the TX builder
|
||||||
async fn add_inputs(
|
async fn add_inputs(
|
||||||
rct_type: RctType,
|
rct_type: RctType,
|
||||||
@@ -40,9 +48,8 @@ test!(
|
|||||||
builder.add_payment(addr, 5);
|
builder.add_payment(addr, 5);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 5);
|
assert_eq!(output.commitment().amount, 5);
|
||||||
},
|
},
|
||||||
@@ -57,8 +64,8 @@ test!(
|
|||||||
builder.add_payment(addr, 2000000000000);
|
builder.add_payment(addr, 2000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let mut outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let mut outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 2);
|
assert_eq!(outputs.len(), 2);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
@@ -74,9 +81,8 @@ test!(
|
|||||||
builder.add_payment(addr, 6);
|
builder.add_payment(addr, 6);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 6);
|
assert_eq!(output.commitment().amount, 6);
|
||||||
},
|
},
|
||||||
@@ -93,8 +99,8 @@ test!(
|
|||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
@@ -130,17 +136,15 @@ test!(
|
|||||||
.add_payment(sub_view.subaddress(Network::Mainnet, SubaddressIndex::new(0, 1).unwrap()), 1);
|
.add_payment(sub_view.subaddress(Network::Mainnet, SubaddressIndex::new(0, 1).unwrap()), 1);
|
||||||
(builder.build().unwrap(), (change_view, sub_view))
|
(builder.build().unwrap(), (change_view, sub_view))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, views: (ViewPair, ViewPair)| async move {
|
|_rpc: SRR, block: SB, tx: Transaction, _, views: (ViewPair, ViewPair)| async move {
|
||||||
// Make sure the change can pick up its output
|
// Make sure the change can pick up its output
|
||||||
let mut change_scanner = Scanner::new(views.0);
|
let mut change_scanner = Scanner::new(views.0);
|
||||||
assert!(
|
assert!(change_scanner.scan(block.clone()).unwrap().not_additionally_locked().len() == 1);
|
||||||
change_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().len() == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
// Make sure the subaddress can pick up its output
|
// Make sure the subaddress can pick up its output
|
||||||
let mut sub_scanner = Scanner::new(views.1);
|
let mut sub_scanner = Scanner::new(views.1);
|
||||||
sub_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
sub_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
||||||
let sub_outputs = sub_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let sub_outputs = sub_scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert!(sub_outputs.len() == 1);
|
assert!(sub_outputs.len() == 1);
|
||||||
assert_eq!(sub_outputs[0].transaction(), tx.hash());
|
assert_eq!(sub_outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(sub_outputs[0].commitment().amount, 1);
|
assert_eq!(sub_outputs[0].commitment().amount, 1);
|
||||||
@@ -165,8 +169,8 @@ test!(
|
|||||||
builder.add_payment(addr, 2000000000000);
|
builder.add_payment(addr, 2000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 2000000000000);
|
assert_eq!(outputs[0].commitment().amount, 2000000000000);
|
||||||
@@ -179,9 +183,8 @@ test!(
|
|||||||
builder.add_payment(addr, 2);
|
builder.add_payment(addr, 2);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx.hash());
|
assert_eq!(output.transaction(), tx.hash());
|
||||||
assert_eq!(output.commitment().amount, 2);
|
assert_eq!(output.commitment().amount, 2);
|
||||||
},
|
},
|
||||||
@@ -195,8 +198,8 @@ test!(
|
|||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
@@ -212,8 +215,8 @@ test!(
|
|||||||
}
|
}
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let mut scanned_tx = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let mut scanned_tx = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
|
|
||||||
let mut output_amounts = HashSet::new();
|
let mut output_amounts = HashSet::new();
|
||||||
for i in 0 .. 15 {
|
for i in 0 .. 15 {
|
||||||
@@ -237,8 +240,8 @@ test!(
|
|||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
@@ -263,10 +266,14 @@ test!(
|
|||||||
|
|
||||||
(builder.build().unwrap(), (scanner, subaddresses))
|
(builder.build().unwrap(), (scanner, subaddresses))
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, _, mut state: (Scanner, Vec<SubaddressIndex>)| async move {
|
|_rpc: SimpleRequestRpc,
|
||||||
|
block,
|
||||||
|
tx: Transaction,
|
||||||
|
_,
|
||||||
|
mut state: (Scanner, Vec<SubaddressIndex>)| async move {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
let mut scanned_tx = state.0.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let mut scanned_tx = state.0.scan(block).unwrap().not_additionally_locked();
|
||||||
|
|
||||||
let mut output_amounts_by_subaddress = HashMap::new();
|
let mut output_amounts_by_subaddress = HashMap::new();
|
||||||
for i in 0 .. 15 {
|
for i in 0 .. 15 {
|
||||||
@@ -294,8 +301,8 @@ test!(
|
|||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
@@ -320,8 +327,8 @@ test!(
|
|||||||
|
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let mut outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let mut outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 2);
|
assert_eq!(outputs.len(), 2);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[1].transaction(), tx.hash());
|
assert_eq!(outputs[1].transaction(), tx.hash());
|
||||||
@@ -345,8 +352,8 @@ test!(
|
|||||||
builder.add_payment(addr, 1000000000000);
|
builder.add_payment(addr, 1000000000000);
|
||||||
(builder.build().unwrap(), ())
|
(builder.build().unwrap(), ())
|
||||||
},
|
},
|
||||||
|rpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
|_rpc: SimpleRequestRpc, block, tx: Transaction, mut scanner: Scanner, ()| async move {
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
assert_eq!(outputs[0].transaction(), tx.hash());
|
assert_eq!(outputs[0].transaction(), tx.hash());
|
||||||
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
assert_eq!(outputs[0].commitment().amount, 1000000000000);
|
||||||
@@ -381,11 +388,11 @@ test!(
|
|||||||
builder.add_payment(view.legacy_address(Network::Mainnet), 1);
|
builder.add_payment(view.legacy_address(Network::Mainnet), 1);
|
||||||
(builder.build().unwrap(), change_view)
|
(builder.build().unwrap(), change_view)
|
||||||
},
|
},
|
||||||
|rpc, block, _, _, change_view: ViewPair| async move {
|
|_rpc: SimpleRequestRpc, block, _, _, change_view: ViewPair| async move {
|
||||||
// Make sure the change can pick up its output
|
// Make sure the change can pick up its output
|
||||||
let mut change_scanner = Scanner::new(change_view);
|
let mut change_scanner = Scanner::new(change_view);
|
||||||
change_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
change_scanner.register_subaddress(SubaddressIndex::new(0, 1).unwrap());
|
||||||
let outputs = change_scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = change_scanner.scan(block).unwrap().not_additionally_locked();
|
||||||
assert!(outputs.len() == 1);
|
assert!(outputs.len() == 1);
|
||||||
assert!(outputs[0].subaddress().unwrap().account() == 0);
|
assert!(outputs[0].subaddress().unwrap().account() == 0);
|
||||||
assert!(outputs[0].subaddress().unwrap().address() == 1);
|
assert!(outputs[0].subaddress().unwrap().address() == 1);
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) {
|
|||||||
|
|
||||||
// unlock it
|
// unlock it
|
||||||
let block = runner::mine_until_unlocked(&daemon_rpc, &wallet_rpc_addr, tx_hash).await;
|
let block = runner::mine_until_unlocked(&daemon_rpc, &wallet_rpc_addr, tx_hash).await;
|
||||||
|
let block = daemon_rpc.get_scannable_block(block).await.unwrap();
|
||||||
|
|
||||||
// Create the scanner
|
// Create the scanner
|
||||||
let mut scanner = Scanner::new(view_pair);
|
let mut scanner = Scanner::new(view_pair);
|
||||||
@@ -114,8 +115,7 @@ async fn from_wallet_rpc_to_self(spec: AddressSpec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve it and scan it
|
// Retrieve it and scan it
|
||||||
let output =
|
let output = scanner.scan(block).unwrap().not_additionally_locked().swap_remove(0);
|
||||||
scanner.scan(&daemon_rpc, &block).await.unwrap().not_additionally_locked().swap_remove(0);
|
|
||||||
assert_eq!(output.transaction(), tx_hash);
|
assert_eq!(output.transaction(), tx_hash);
|
||||||
|
|
||||||
runner::check_weight_and_fee(&daemon_rpc.get_transaction(tx_hash).await.unwrap(), fee_rate);
|
runner::check_weight_and_fee(&daemon_rpc.get_transaction(tx_hash).await.unwrap(), fee_rate);
|
||||||
|
|||||||
@@ -520,7 +520,13 @@ impl Network for Monero {
|
|||||||
|
|
||||||
async fn get_outputs(&self, block: &Block, key: EdwardsPoint) -> Vec<Output> {
|
async fn get_outputs(&self, block: &Block, key: EdwardsPoint) -> Vec<Output> {
|
||||||
let outputs = loop {
|
let outputs = loop {
|
||||||
match Self::scanner(key).scan(&self.rpc, block).await {
|
match self
|
||||||
|
.rpc
|
||||||
|
.get_scannable_block(block.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{e:?}"))
|
||||||
|
.and_then(|block| Self::scanner(key).scan(block).map_err(|e| format!("{e:?}")))
|
||||||
|
{
|
||||||
Ok(outputs) => break outputs,
|
Ok(outputs) => break outputs,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("couldn't scan block {}: {e:?}", hex::encode(block.id()));
|
log::error!("couldn't scan block {}: {e:?}", hex::encode(block.id()));
|
||||||
@@ -738,8 +744,10 @@ impl Network for Monero {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let new_block = self.rpc.get_block_by_number(new_block).await.unwrap();
|
let new_block = self.rpc.get_block_by_number(new_block).await.unwrap();
|
||||||
let mut outputs =
|
let mut outputs = Self::test_scanner()
|
||||||
Self::test_scanner().scan(&self.rpc, &new_block).await.unwrap().ignore_additional_timelock();
|
.scan(self.rpc.get_scannable_block(new_block.clone()).await.unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.ignore_additional_timelock();
|
||||||
let output = outputs.swap_remove(0);
|
let output = outputs.swap_remove(0);
|
||||||
|
|
||||||
let amount = output.commitment().amount;
|
let amount = output.commitment().amount;
|
||||||
|
|||||||
@@ -357,8 +357,7 @@ async fn mint_and_burn_test() {
|
|||||||
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)).unwrap();
|
let view_pair = ViewPair::new(ED25519_BASEPOINT_POINT, Zeroizing::new(Scalar::ONE)).unwrap();
|
||||||
let mut scanner = Scanner::new(view_pair.clone());
|
let mut scanner = Scanner::new(view_pair.clone());
|
||||||
let output = scanner
|
let output = scanner
|
||||||
.scan(&rpc, &rpc.get_block_by_number(1).await.unwrap())
|
.scan(rpc.get_scannable_block_by_number(1).await.unwrap())
|
||||||
.await
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
.additional_timelock_satisfied_by(rpc.get_height().await.unwrap(), 0)
|
||||||
.swap_remove(0);
|
.swap_remove(0);
|
||||||
@@ -587,7 +586,10 @@ async fn mint_and_burn_test() {
|
|||||||
while i < (5 * 6) {
|
while i < (5 * 6) {
|
||||||
if let Ok(block) = rpc.get_block_by_number(start_monero_block).await {
|
if let Ok(block) = rpc.get_block_by_number(start_monero_block).await {
|
||||||
start_monero_block += 1;
|
start_monero_block += 1;
|
||||||
let outputs = scanner.scan(&rpc, &block).await.unwrap().not_additionally_locked();
|
let outputs = scanner
|
||||||
|
.scan(rpc.get_scannable_block(block.clone()).await.unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.not_additionally_locked();
|
||||||
if !outputs.is_empty() {
|
if !outputs.is_empty() {
|
||||||
assert_eq!(outputs.len(), 1);
|
assert_eq!(outputs.len(), 1);
|
||||||
|
|
||||||
|
|||||||
@@ -429,8 +429,7 @@ impl Wallet {
|
|||||||
block.transactions.contains(&last_tx.1)
|
block.transactions.contains(&last_tx.1)
|
||||||
{
|
{
|
||||||
outputs = Scanner::new(view_pair.clone())
|
outputs = Scanner::new(view_pair.clone())
|
||||||
.scan(&rpc, &block)
|
.scan(rpc.get_scannable_block(block).await.unwrap())
|
||||||
.await
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ignore_additional_timelock();
|
.ignore_additional_timelock();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user