[tests] Add a proc macro to generate tests for OnlineBlockchain types
This commit is contained in:
@@ -15,6 +15,13 @@ use crate::FeeRate;
|
||||
|
||||
pub struct ElectrumBlockchain(Option<Client>);
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "test-electrum")]
|
||||
#[magical_blockchain_tests(crate)]
|
||||
fn local_electrs() -> ElectrumBlockchain {
|
||||
ElectrumBlockchain::from(Client::new(&testutils::get_electrum_url(), None).unwrap())
|
||||
}
|
||||
|
||||
impl std::convert::From<Client> for ElectrumBlockchain {
|
||||
fn from(client: Client) -> Self {
|
||||
ElectrumBlockchain(Some(client))
|
||||
|
||||
@@ -186,7 +186,6 @@ pub trait ElectrumLikeSync {
|
||||
);
|
||||
let mut updates = database.begin_batch();
|
||||
let tx = match database.get_tx(&txid, true)? {
|
||||
// TODO: do we need the raw?
|
||||
Some(mut saved_tx) => {
|
||||
// update the height if it's different (in case of reorg)
|
||||
if saved_tx.height != height {
|
||||
@@ -204,12 +203,20 @@ pub trait ElectrumLikeSync {
|
||||
// went wrong
|
||||
saved_tx.transaction.unwrap()
|
||||
}
|
||||
None => maybe_await!(self.els_transaction_get(&txid))?,
|
||||
None => {
|
||||
let fetched_tx = maybe_await!(self.els_transaction_get(&txid))?;
|
||||
database.set_raw_tx(&fetched_tx)?;
|
||||
|
||||
fetched_tx
|
||||
}
|
||||
};
|
||||
|
||||
let mut incoming: u64 = 0;
|
||||
let mut outgoing: u64 = 0;
|
||||
|
||||
let mut inputs_sum: u64 = 0;
|
||||
let mut outputs_sum: u64 = 0;
|
||||
|
||||
// look for our own inputs
|
||||
for (i, input) in tx.input.iter().enumerate() {
|
||||
// the fact that we visit addresses in a BFS fashion starting from the external addresses
|
||||
@@ -217,17 +224,37 @@ pub trait ElectrumLikeSync {
|
||||
// the transactions at a lower depth have already been indexed, so if an outpoint is ours
|
||||
// we are guaranteed to have it in the db).
|
||||
if let Some(previous_output) = database.get_previous_output(&input.previous_output)? {
|
||||
inputs_sum += previous_output.value;
|
||||
|
||||
if database.is_mine(&previous_output.script_pubkey)? {
|
||||
outgoing += previous_output.value;
|
||||
|
||||
debug!("{} input #{} is mine, removing from utxo", txid, i);
|
||||
updates.del_utxo(&input.previous_output)?;
|
||||
}
|
||||
} else {
|
||||
// The input is not ours, but we still need to count it for the fees. so fetch the
|
||||
// tx (from the database or from network) and check it
|
||||
let tx = match database.get_tx(&input.previous_output.txid, true)? {
|
||||
Some(saved_tx) => saved_tx.transaction.unwrap(),
|
||||
None => {
|
||||
let fetched_tx =
|
||||
maybe_await!(self.els_transaction_get(&input.previous_output.txid))?;
|
||||
database.set_raw_tx(&fetched_tx)?;
|
||||
|
||||
fetched_tx
|
||||
}
|
||||
};
|
||||
|
||||
inputs_sum += tx.output[input.previous_output.vout as usize].value;
|
||||
}
|
||||
}
|
||||
|
||||
let mut to_check_later = vec![];
|
||||
for (i, output) in tx.output.iter().enumerate() {
|
||||
// to compute the fees later
|
||||
outputs_sum += output.value;
|
||||
|
||||
// this output is ours, we have a path to derive it
|
||||
if let Some((script_type, child)) =
|
||||
database.get_path_from_script_pubkey(&output.script_pubkey)?
|
||||
@@ -259,6 +286,7 @@ pub trait ElectrumLikeSync {
|
||||
sent: outgoing,
|
||||
height,
|
||||
timestamp: 0,
|
||||
fees: inputs_sum - outputs_sum,
|
||||
};
|
||||
info!("Saving tx {}", txid);
|
||||
updates.set_tx(&tx)?;
|
||||
|
||||
@@ -8,6 +8,8 @@ use crate::types::*;
|
||||
pub mod keyvalue;
|
||||
pub mod memory;
|
||||
|
||||
pub use memory::MemoryDatabase;
|
||||
|
||||
pub trait BatchOperations {
|
||||
fn set_script_pubkey(
|
||||
&mut self,
|
||||
@@ -235,6 +237,7 @@ pub mod test {
|
||||
timestamp: 123456,
|
||||
received: 1337,
|
||||
sent: 420420,
|
||||
fees: 140,
|
||||
height: Some(1000),
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ pub enum Error {
|
||||
SendAllMultipleOutputs,
|
||||
OutputBelowDustLimit(usize),
|
||||
InsufficientFunds,
|
||||
InvalidAddressNetork(Address),
|
||||
InvalidAddressNetwork(Address),
|
||||
UnknownUTXO,
|
||||
DifferentTransactions,
|
||||
|
||||
|
||||
10
src/lib.rs
10
src/lib.rs
@@ -31,6 +31,16 @@ pub extern crate sled;
|
||||
#[cfg(feature = "cli-utils")]
|
||||
pub mod cli;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate testutils;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate testutils_macros;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate serial_test;
|
||||
|
||||
#[macro_use]
|
||||
pub mod error;
|
||||
pub mod blockchain;
|
||||
|
||||
@@ -6,7 +6,7 @@ use bitcoin::hash_types::Txid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// TODO serde flatten?
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ScriptType {
|
||||
External = 0,
|
||||
Internal = 1,
|
||||
@@ -48,5 +48,6 @@ pub struct TransactionDetails {
|
||||
pub timestamp: u64,
|
||||
pub received: u64,
|
||||
pub sent: u64,
|
||||
pub fees: u64,
|
||||
pub height: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ mod test {
|
||||
timestamp: 12345678,
|
||||
received: 100_000,
|
||||
sent: 0,
|
||||
fees: 500,
|
||||
height: Some(5000),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -24,7 +24,7 @@ pub mod tx_builder;
|
||||
pub mod utils;
|
||||
|
||||
use tx_builder::TxBuilder;
|
||||
use utils::{FeeRate, IsDust};
|
||||
use utils::IsDust;
|
||||
|
||||
use crate::blockchain::{noop_progress, Blockchain, OfflineBlockchain, OnlineBlockchain};
|
||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||
@@ -190,8 +190,9 @@ where
|
||||
false => *satoshi,
|
||||
};
|
||||
|
||||
if address.network != self.network {
|
||||
return Err(Error::InvalidAddressNetork(address.clone()));
|
||||
// TODO: proper checks for testnet/regtest p2sh/p2pkh
|
||||
if address.network != self.network && self.network != Network::Regtest {
|
||||
return Err(Error::InvalidAddressNetwork(address.clone()));
|
||||
} else if self.is_mine(&address.script_pubkey())? {
|
||||
received += value;
|
||||
}
|
||||
@@ -263,7 +264,8 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let change_val = total_amount - outgoing - (fee_amount.ceil() as u64);
|
||||
let mut fee_amount = fee_amount.ceil() as u64;
|
||||
let change_val = total_amount - outgoing - fee_amount;
|
||||
if !builder.send_all && !change_val.is_dust() {
|
||||
let mut change_output = change_output.unwrap();
|
||||
change_output.value = change_val;
|
||||
@@ -271,8 +273,6 @@ where
|
||||
|
||||
tx.output.push(change_output);
|
||||
} else if builder.send_all && !change_val.is_dust() {
|
||||
// set the outgoing value to whatever we've put in
|
||||
outgoing = total_amount;
|
||||
// there's only one output, send everything to it
|
||||
tx.output[0].value = change_val;
|
||||
|
||||
@@ -280,6 +280,9 @@ where
|
||||
if self.is_mine(&tx.output[0].script_pubkey)? {
|
||||
received = change_val;
|
||||
}
|
||||
} else if !builder.send_all && change_val.is_dust() {
|
||||
// skip the change output because it's dust, this adds up to the fees
|
||||
fee_amount += change_val;
|
||||
} else if builder.send_all {
|
||||
// send_all but the only output would be below dust limit
|
||||
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
|
||||
@@ -339,7 +342,8 @@ where
|
||||
txid,
|
||||
timestamp: time::get_timestamp(),
|
||||
received,
|
||||
sent: outgoing,
|
||||
sent: total_amount,
|
||||
fees: fee_amount,
|
||||
height: None,
|
||||
};
|
||||
|
||||
@@ -750,6 +754,8 @@ where
|
||||
pub fn sync(&self, max_address_param: Option<u32>) -> Result<(), Error> {
|
||||
debug!("Begin sync...");
|
||||
|
||||
let mut run_setup = false;
|
||||
|
||||
let max_address = match self.descriptor.is_fixed() {
|
||||
true => 0,
|
||||
false => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
|
||||
@@ -760,6 +766,7 @@ where
|
||||
.get_script_pubkey_from_path(ScriptType::External, max_address)?
|
||||
.is_none()
|
||||
{
|
||||
run_setup = true;
|
||||
self.cache_addresses(ScriptType::External, 0, max_address)?;
|
||||
}
|
||||
|
||||
@@ -775,15 +782,24 @@ where
|
||||
.get_script_pubkey_from_path(ScriptType::Internal, max_address)?
|
||||
.is_none()
|
||||
{
|
||||
run_setup = true;
|
||||
self.cache_addresses(ScriptType::Internal, 0, max_address)?;
|
||||
}
|
||||
}
|
||||
|
||||
maybe_await!(self.client.sync(
|
||||
None,
|
||||
self.database.borrow_mut().deref_mut(),
|
||||
noop_progress(),
|
||||
))
|
||||
if run_setup {
|
||||
maybe_await!(self.client.setup(
|
||||
None,
|
||||
self.database.borrow_mut().deref_mut(),
|
||||
noop_progress(),
|
||||
))
|
||||
} else {
|
||||
maybe_await!(self.client.sync(
|
||||
None,
|
||||
self.database.borrow_mut().deref_mut(),
|
||||
noop_progress(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client(&self) -> &B {
|
||||
|
||||
Reference in New Issue
Block a user