[tests] Add a proc macro to generate tests for OnlineBlockchain types

This commit is contained in:
Alekos Filini
2020-08-10 10:49:34 +02:00
parent c90c752f21
commit 9e5023670e
16 changed files with 1018 additions and 17 deletions

View File

@@ -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))

View File

@@ -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)?;

View File

@@ -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),
};

View File

@@ -10,7 +10,7 @@ pub enum Error {
SendAllMultipleOutputs,
OutputBelowDustLimit(usize),
InsufficientFunds,
InvalidAddressNetork(Address),
InvalidAddressNetwork(Address),
UnknownUTXO,
DifferentTransactions,

View File

@@ -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;

View File

@@ -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>,
}

View File

@@ -129,6 +129,7 @@ mod test {
timestamp: 12345678,
received: 100_000,
sent: 0,
fees: 500,
height: Some(5000),
})
.unwrap();

View File

@@ -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 {