bdk/src/wallet/mod.rs

1076 lines
38 KiB
Rust
Raw Normal View History

2020-02-07 23:22:28 +01:00
use std::cell::RefCell;
use std::cmp;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::convert::TryFrom;
use std::io::{Read, Write};
2020-02-15 21:27:51 +01:00
use std::str::FromStr;
2020-02-07 23:22:28 +01:00
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder;
use bitcoin::consensus::encode::serialize;
use bitcoin::util::bip32::{ChildNumber, DerivationPath};
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{
Address, Network, OutPoint, PublicKey, Script, SigHashType, Transaction, TxIn, TxOut, Txid,
};
use miniscript::BitcoinSig;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
pub mod offline_stream;
pub mod utils;
use self::utils::{ChunksIterator, IsDust};
use crate::database::{BatchDatabase, BatchOperations};
2020-02-15 21:27:51 +01:00
use crate::descriptor::{get_checksum, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, Policy};
2020-02-07 23:22:28 +01:00
use crate::error::Error;
2020-02-15 21:27:51 +01:00
use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
2020-02-07 23:22:28 +01:00
use crate::signer::Signer;
use crate::types::*;
#[cfg(any(feature = "electrum", feature = "default"))]
use electrum_client::types::*;
#[cfg(any(feature = "electrum", feature = "default"))]
use electrum_client::Client;
#[cfg(not(any(feature = "electrum", feature = "default")))]
use std::marker::PhantomData as Client;
pub struct Wallet<S: Read + Write, D: BatchDatabase> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
network: Network,
client: Option<RefCell<Client<S>>>,
2020-02-17 14:22:53 +01:00
database: RefCell<D>,
2020-02-07 23:22:28 +01:00
}
// offline actions, always available
impl<S, D> Wallet<S, D>
where
S: Read + Write,
D: BatchDatabase,
{
pub fn new_offline(
2020-02-15 21:27:51 +01:00
descriptor: &str,
change_descriptor: Option<&str>,
2020-02-07 23:22:28 +01:00
network: Network,
2020-02-15 21:27:51 +01:00
mut database: D,
) -> Result<Self, Error> {
database.check_descriptor_checksum(
ScriptType::External,
get_checksum(descriptor)?.as_bytes(),
)?;
let descriptor = ExtendedDescriptor::from_str(descriptor)?;
let change_descriptor = match change_descriptor {
Some(desc) => {
database.check_descriptor_checksum(
ScriptType::Internal,
get_checksum(desc)?.as_bytes(),
)?;
2020-02-17 14:22:53 +01:00
let parsed = ExtendedDescriptor::from_str(desc)?;
if !parsed.same_structure(descriptor.as_ref()) {
return Err(Error::DifferentDescriptorStructure);
}
Some(parsed)
2020-02-15 21:27:51 +01:00
}
None => None,
};
Ok(Wallet {
2020-02-07 23:22:28 +01:00
descriptor,
change_descriptor,
network,
client: None,
database: RefCell::new(database),
2020-02-15 21:27:51 +01:00
})
2020-02-07 23:22:28 +01:00
}
pub fn get_new_address(&self) -> Result<Address, Error> {
let index = self
.database
.borrow_mut()
.increment_last_index(ScriptType::External)?;
// TODO: refill the address pool if index is close to the last cached addr
self.descriptor
.derive(index)?
.address(self.network)
.ok_or(Error::ScriptDoesntHaveAddressForm)
}
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
self.get_path(script).map(|x| x.is_some())
}
pub fn list_unspent(&self) -> Result<Vec<UTXO>, Error> {
self.database.borrow().iter_utxos()
}
pub fn list_transactions(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
self.database.borrow().iter_txs(include_raw)
}
pub fn get_balance(&self) -> Result<u64, Error> {
Ok(self
.list_unspent()?
.iter()
.fold(0, |sum, i| sum + i.txout.value))
}
// TODO: add a flag to ignore change in coin selection
pub fn create_tx(
&self,
addressees: Vec<(Address, u64)>,
send_all: bool,
fee_perkb: f32,
policy_path: Option<Vec<Vec<usize>>>,
utxos: Option<Vec<OutPoint>>,
unspendable: Option<Vec<OutPoint>>,
) -> Result<(PSBT, TransactionDetails), Error> {
2020-02-17 14:22:53 +01:00
let policy = self.descriptor.extract_policy()?.unwrap();
2020-02-07 23:22:28 +01:00
if policy.requires_path() && policy_path.is_none() {
return Err(Error::SpendingPolicyRequired);
}
2020-02-17 14:22:53 +01:00
let requirements = policy.get_requirements(&policy_path.unwrap_or(vec![]))?;
2020-02-07 23:22:28 +01:00
debug!("requirements: {:?}", requirements);
let mut tx = Transaction {
version: 2,
lock_time: requirements.timelock.unwrap_or(0),
input: vec![],
output: vec![],
};
let fee_rate = fee_perkb * 100_000.0;
if send_all && addressees.len() != 1 {
return Err(Error::SendAllMultipleOutputs);
}
// we keep it as a float while we accumulate it, and only round it at the end
let mut fee_val: f32 = 0.0;
let mut outgoing: u64 = 0;
let mut received: u64 = 0;
let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0;
fee_val += calc_fee_bytes(tx.get_weight());
for (index, (address, satoshi)) in addressees.iter().enumerate() {
let value = match send_all {
true => 0,
false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
false => *satoshi,
};
// TODO: check address network
if self.is_mine(&address.script_pubkey())? {
received += value;
}
let new_out = TxOut {
script_pubkey: address.script_pubkey(),
value,
};
fee_val += calc_fee_bytes(serialize(&new_out).len() * 4);
tx.output.push(new_out);
outgoing += value;
}
// TODO: assumes same weight to spend external and internal
let input_witness_weight = self.descriptor.max_satisfaction_weight();
let (available_utxos, use_all_utxos) =
self.get_available_utxos(&utxos, &unspendable, send_all)?;
let (mut inputs, paths, selected_amount, mut fee_val) = self.coin_select(
available_utxos,
use_all_utxos,
fee_rate,
outgoing,
input_witness_weight,
fee_val,
)?;
2020-02-17 14:22:53 +01:00
let n_sequence = if let Some(csv) = requirements.csv {
csv
} else if requirements.timelock.is_some() {
0xFFFFFFFE
} else {
0xFFFFFFFF
};
inputs.iter_mut().for_each(|i| i.sequence = n_sequence);
2020-02-07 23:22:28 +01:00
tx.input.append(&mut inputs);
// prepare the change output
let change_output = match send_all {
true => None,
false => {
let change_script = self.get_change_address()?;
let change_output = TxOut {
script_pubkey: change_script,
value: 0,
};
// take the change into account for fees
fee_val += calc_fee_bytes(serialize(&change_output).len() * 4);
Some(change_output)
}
};
let change_val = selected_amount - outgoing - (fee_val.ceil() as u64);
if !send_all && !change_val.is_dust() {
let mut change_output = change_output.unwrap();
change_output.value = change_val;
received += change_val;
tx.output.push(change_output);
} else if send_all && !change_val.is_dust() {
// set the outgoing value to whatever we've put in
outgoing = selected_amount;
// there's only one output, send everything to it
tx.output[0].value = change_val;
// send_all to our address
if self.is_mine(&tx.output[0].script_pubkey)? {
received = change_val;
}
} else if send_all {
// send_all but the only output would be below dust limit
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
}
// TODO: shuffle the outputs
let txid = tx.txid();
let mut psbt = PSBT::from_unsigned_tx(tx)?;
// add metadata for the inputs
for ((psbt_input, (script_type, path)), input) in psbt
.inputs
.iter_mut()
.zip(paths.into_iter())
.zip(psbt.global.unsigned_tx.input.iter())
{
let path: Vec<ChildNumber> = path.into();
let index = match path.last() {
Some(ChildNumber::Normal { index }) => *index,
Some(ChildNumber::Hardened { index }) => *index,
None => 0,
};
let desc = self.get_descriptor_for(script_type);
psbt_input.hd_keypaths = desc.get_hd_keypaths(index).unwrap();
let derived_descriptor = desc.derive(index).unwrap();
// TODO: figure out what do redeem_script and witness_script mean
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
let prev_output = input.previous_output;
let prev_tx = self
.database
.borrow()
.get_raw_tx(&prev_output.txid)?
.unwrap(); // TODO: remove unwrap
if derived_descriptor.is_witness() {
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
} else {
psbt_input.non_witness_utxo = Some(prev_tx);
};
// we always sign with SIGHASH_ALL
psbt_input.sighash_type = Some(SigHashType::All);
}
// TODO: add metadata for the outputs, like derivation paths for change addrs
/*for psbt_output in psbt.outputs.iter_mut().zip(psbt.global.unsigned_tx.output.iter()) {
}*/
let transaction_details = TransactionDetails {
transaction: None,
txid: txid,
timestamp: Self::get_timestamp(),
received,
sent: outgoing,
height: None,
};
Ok((psbt, transaction_details))
}
2020-02-17 14:22:53 +01:00
// TODO: move down to the "internals"
fn add_hd_keypaths(&self, psbt: &mut PSBT) -> Result<(), Error> {
2020-02-15 21:27:51 +01:00
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
for n in 0..psbt.inputs.len() {
input_utxos.push(psbt.get_utxo_for(n).clone());
}
2020-02-07 23:22:28 +01:00
// try to add hd_keypaths if we've already seen the output
2020-02-15 21:27:51 +01:00
for (psbt_input, out) in psbt.inputs.iter_mut().zip(input_utxos.iter()) {
2020-02-07 23:22:28 +01:00
debug!("searching hd_keypaths for out: {:?}", out);
if let Some(out) = out {
let option_path = self
.database
.borrow()
.get_path_from_script_pubkey(&out.script_pubkey)?;
debug!("found descriptor path {:?}", option_path);
let (script_type, path) = match option_path {
None => continue,
Some((script_type, path)) => (script_type, path),
};
// TODO: this is duplicated code
let index = match path.into_iter().last() {
Some(ChildNumber::Normal { index }) => *index,
Some(ChildNumber::Hardened { index }) => *index,
None => 0,
};
// merge hd_keypaths
2020-02-15 21:27:51 +01:00
let desc = self.get_descriptor_for(script_type);
2020-02-07 23:22:28 +01:00
let mut hd_keypaths = desc.get_hd_keypaths(index)?;
psbt_input.hd_keypaths.append(&mut hd_keypaths);
}
}
2020-02-17 14:22:53 +01:00
Ok(())
}
// TODO: define an enum for signing errors
pub fn sign(&self, mut psbt: PSBT) -> Result<(PSBT, bool), Error> {
// this helps us doing our job later
self.add_hd_keypaths(&mut psbt)?;
let tx = &psbt.global.unsigned_tx;
2020-02-07 23:22:28 +01:00
let mut signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, &self.descriptor)?;
if let Some(desc) = &self.change_descriptor {
let change_signer = PSBTSigner::from_descriptor(&psbt.global.unsigned_tx, desc)?;
signer.extend(change_signer)?;
}
// sign everything we can. TODO: ideally we should only sign with the keys in the policy
// path selected, if present
for (i, input) in psbt.inputs.iter_mut().enumerate() {
let sighash = input.sighash_type.unwrap_or(SigHashType::All);
let prevout = tx.input[i].previous_output;
let mut partial_sigs = BTreeMap::new();
{
let mut push_sig = |pubkey: &PublicKey, opt_sig: Option<BitcoinSig>| {
if let Some((signature, sighash)) = opt_sig {
let mut concat_sig = Vec::new();
concat_sig.extend_from_slice(&signature.serialize_der());
concat_sig.extend_from_slice(&[sighash as u8]);
//input.partial_sigs.insert(*pubkey, concat_sig);
partial_sigs.insert(*pubkey, concat_sig);
}
};
if let Some(non_wit_utxo) = &input.non_witness_utxo {
if non_wit_utxo.txid() != prevout.txid {
return Err(Error::InputTxidMismatch((non_wit_utxo.txid(), prevout)));
}
let prev_script = &non_wit_utxo.output
[psbt.global.unsigned_tx.input[i].previous_output.vout as usize]
.script_pubkey;
// return (signature, sighash) from here
let sign_script = if let Some(redeem_script) = &input.redeem_script {
if &redeem_script.to_p2sh() != prev_script {
return Err(Error::InputRedeemScriptMismatch((
prev_script.clone(),
redeem_script.clone(),
)));
}
redeem_script
} else {
prev_script
};
for (pubkey, (fing, path)) in &input.hd_keypaths {
push_sig(
pubkey,
signer.sig_legacy_from_fingerprint(
i,
sighash,
fing,
path,
sign_script,
)?,
);
}
// TODO: this sucks, we sign with every key
for pubkey in signer.all_public_keys() {
push_sig(
pubkey,
signer.sig_legacy_from_pubkey(i, sighash, pubkey, sign_script)?,
);
}
} else if let Some(witness_utxo) = &input.witness_utxo {
let value = witness_utxo.value;
let script = match &input.redeem_script {
Some(script) if script.to_p2sh() != witness_utxo.script_pubkey => {
return Err(Error::InputRedeemScriptMismatch((
witness_utxo.script_pubkey.clone(),
script.clone(),
)))
}
Some(script) => script,
None => &witness_utxo.script_pubkey,
};
let sign_script = if script.is_v0_p2wpkh() {
self.to_p2pkh(&script.as_bytes()[2..])
} else if script.is_v0_p2wsh() {
match &input.witness_script {
None => Err(Error::InputMissingWitnessScript(i)),
Some(witness_script) if script != &witness_script.to_v0_p2wsh() => {
Err(Error::InputRedeemScriptMismatch((
script.clone(),
witness_script.clone(),
)))
}
Some(witness_script) => Ok(witness_script),
}?
.clone()
} else {
return Err(Error::InputUnknownSegwitScript(script.clone()));
};
for (pubkey, (fing, path)) in &input.hd_keypaths {
push_sig(
pubkey,
signer.sig_segwit_from_fingerprint(
i,
sighash,
fing,
path,
&sign_script,
value,
)?,
);
}
// TODO: this sucks, we sign with every key
for pubkey in signer.all_public_keys() {
push_sig(
pubkey,
signer.sig_segwit_from_pubkey(
i,
sighash,
pubkey,
&sign_script,
value,
)?,
);
}
} else {
return Err(Error::MissingUTXO);
}
}
// push all the signatures into the psbt
input.partial_sigs.append(&mut partial_sigs);
}
// attempt to finalize
2020-02-15 21:27:51 +01:00
let finalized = self.finalize_psbt(tx.clone(), &mut psbt);
2020-02-07 23:22:28 +01:00
Ok((psbt, finalized))
}
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
match (script_type, self.change_descriptor.as_ref()) {
2020-02-17 14:22:53 +01:00
(ScriptType::External, _) => Ok(self.descriptor.extract_policy()?),
2020-02-07 23:22:28 +01:00
(ScriptType::Internal, None) => Ok(None),
2020-02-17 14:22:53 +01:00
(ScriptType::Internal, Some(desc)) => Ok(desc.extract_policy()?),
2020-02-07 23:22:28 +01:00
}
}
// Internals
fn get_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
fn get_path(&self, script: &Script) -> Result<Option<(ScriptType, DerivationPath)>, Error> {
self.database.borrow().get_path_from_script_pubkey(script)
}
fn get_descriptor_for(&self, script_type: ScriptType) -> &ExtendedDescriptor {
let desc = match script_type {
ScriptType::External => &self.descriptor,
ScriptType::Internal => &self.change_descriptor.as_ref().unwrap_or(&self.descriptor),
};
desc
}
fn to_p2pkh(&self, pubkey_hash: &[u8]) -> Script {
Builder::new()
.push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(pubkey_hash)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script()
}
fn get_change_address(&self) -> Result<Script, Error> {
let (desc, script_type) = if self.change_descriptor.is_none() {
(&self.descriptor, ScriptType::External)
} else {
(
self.change_descriptor.as_ref().unwrap(),
ScriptType::Internal,
)
};
// TODO: refill the address pool if index is close to the last cached addr
let index = self
.database
.borrow_mut()
.increment_last_index(script_type)?;
Ok(desc.derive(index)?.script_pubkey())
}
fn get_available_utxos(
&self,
utxo: &Option<Vec<OutPoint>>,
unspendable: &Option<Vec<OutPoint>>,
send_all: bool,
) -> Result<(Vec<UTXO>, bool), Error> {
// TODO: should we consider unconfirmed received rbf txs as "unspendable" too by default?
let unspendable_set = match unspendable {
None => HashSet::new(),
Some(vec) => vec.into_iter().collect(),
};
match utxo {
// with manual coin selection we always want to spend all the selected utxos, no matter
// what (even if they are marked as unspendable)
Some(raw_utxos) => {
// TODO: unwrap to remove
let full_utxos: Vec<_> = raw_utxos
.iter()
.map(|u| self.database.borrow().get_utxo(&u).unwrap())
.collect();
if !full_utxos.iter().all(|u| u.is_some()) {
return Err(Error::UnknownUTXO);
}
Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true))
}
// otherwise limit ourselves to the spendable utxos and the `send_all` setting
None => Ok((
self.list_unspent()?
.into_iter()
.filter(|u| !unspendable_set.contains(&u.outpoint))
.collect(),
send_all,
)),
}
}
fn coin_select(
&self,
mut utxos: Vec<UTXO>,
use_all_utxos: bool,
fee_rate: f32,
outgoing: u64,
input_witness_weight: usize,
mut fee_val: f32,
) -> Result<(Vec<TxIn>, Vec<(ScriptType, DerivationPath)>, u64, f32), Error> {
let mut answer = Vec::new();
let mut paths = Vec::new();
let calc_fee_bytes = |wu| (wu as f32) * fee_rate / 4.0;
debug!(
"coin select: outgoing = `{}`, fee_val = `{}`, fee_rate = `{}`",
outgoing, fee_val, fee_rate
);
// sort so that we pick them starting from the larger. TODO: proper coin selection
utxos.sort_by(|a, b| a.txout.value.partial_cmp(&b.txout.value).unwrap());
let mut selected_amount: u64 = 0;
while use_all_utxos || selected_amount < outgoing + (fee_val.ceil() as u64) {
let utxo = match utxos.pop() {
Some(utxo) => utxo,
None if selected_amount < outgoing + (fee_val.ceil() as u64) => {
return Err(Error::InsufficientFunds)
}
None if use_all_utxos => break,
None => return Err(Error::InsufficientFunds),
};
let new_in = TxIn {
previous_output: utxo.outpoint,
script_sig: Script::default(),
sequence: 0xFFFFFFFD, // TODO: change according to rbf/csv
witness: vec![],
};
fee_val += calc_fee_bytes(serialize(&new_in).len() * 4 + input_witness_weight);
debug!("coin select new fee_val = `{}`", fee_val);
answer.push(new_in);
selected_amount += utxo.txout.value;
let path = self
.database
.borrow()
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
.unwrap(); // TODO: remove unrwap
paths.push(path);
}
Ok((answer, paths, selected_amount, fee_val))
}
2020-02-15 21:27:51 +01:00
fn finalize_psbt(&self, mut tx: Transaction, psbt: &mut PSBT) -> bool {
2020-02-07 23:22:28 +01:00
for (n, input) in tx.input.iter_mut().enumerate() {
2020-02-15 21:27:51 +01:00
// safe to run only on the descriptor because we assume the change descriptor also has
// the same structure
let desc = self.descriptor.derive_from_psbt_input(psbt, n);
debug!("reconstructed descriptor is {:?}", desc);
let desc = match desc {
Err(_) => return false,
Ok(desc) => desc,
2020-02-07 23:22:28 +01:00
};
// TODO: use height once we sync headers
let satisfier = PSBTSatisfier::new(&psbt.inputs[n], None, None);
match desc.satisfy(input, satisfier) {
Ok(_) => continue,
Err(e) => {
debug!("satisfy error {:?} for input {}", e, n);
return false;
}
}
}
// consume tx to extract its input's script_sig and witnesses and move them into the psbt
for (input, psbt_input) in tx.input.into_iter().zip(psbt.inputs.iter_mut()) {
psbt_input.final_script_sig = Some(input.script_sig);
psbt_input.final_script_witness = Some(input.witness);
}
true
}
}
#[cfg(any(feature = "electrum", feature = "default"))]
impl<S, D> Wallet<S, D>
where
S: Read + Write,
D: BatchDatabase,
{
pub fn new(
2020-02-15 21:27:51 +01:00
descriptor: &str,
change_descriptor: Option<&str>,
2020-02-07 23:22:28 +01:00
network: Network,
2020-02-15 21:27:51 +01:00
mut database: D,
2020-02-07 23:22:28 +01:00
client: Client<S>,
2020-02-15 21:27:51 +01:00
) -> Result<Self, Error> {
database.check_descriptor_checksum(
ScriptType::External,
get_checksum(descriptor)?.as_bytes(),
)?;
let descriptor = ExtendedDescriptor::from_str(descriptor)?;
let change_descriptor = match change_descriptor {
Some(desc) => {
database.check_descriptor_checksum(
ScriptType::Internal,
get_checksum(desc)?.as_bytes(),
)?;
2020-02-17 14:22:53 +01:00
let parsed = ExtendedDescriptor::from_str(desc)?;
if !parsed.same_structure(descriptor.as_ref()) {
return Err(Error::DifferentDescriptorStructure);
}
Some(parsed)
2020-02-15 21:27:51 +01:00
}
None => None,
};
Ok(Wallet {
2020-02-07 23:22:28 +01:00
descriptor,
change_descriptor,
network,
client: Some(RefCell::new(client)),
database: RefCell::new(database),
2020-02-15 21:27:51 +01:00
})
2020-02-07 23:22:28 +01:00
}
fn get_previous_output(&self, outpoint: &OutPoint) -> Option<TxOut> {
// the fact that we visit addresses in a BFS fashion starting from the external addresses
// should ensure that this query is always consistent (i.e. when we get to call this all
// 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).
self.database
.borrow()
.get_raw_tx(&outpoint.txid)
.unwrap()
.map(|previous_tx| previous_tx.output[outpoint.vout as usize].clone())
}
fn check_tx_and_descendant(
&self,
txid: &Txid,
height: Option<u32>,
cur_script: &Script,
change_max_deriv: &mut u32,
) -> Result<Vec<Script>, Error> {
debug!(
"check_tx_and_descendant of {}, height: {:?}, script: {}",
txid, height, cur_script
);
let mut updates = self.database.borrow().begin_batch();
let tx = match self.database.borrow().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 {
info!(
"updating height from {:?} to {:?} for tx {}",
saved_tx.height, height, txid
);
saved_tx.height = height;
updates.set_tx(&saved_tx)?;
}
debug!("already have {} in db, returning the cached version", txid);
// unwrap since we explicitly ask for the raw_tx, if it's not present something
// went wrong
saved_tx.transaction.unwrap()
}
None => self
.client
.as_ref()
.unwrap()
.borrow_mut()
.transaction_get(&txid)?,
};
let mut incoming: u64 = 0;
let mut outgoing: u64 = 0;
// look for our own inputs
for (i, input) in tx.input.iter().enumerate() {
if let Some(previous_output) = self.get_previous_output(&input.previous_output) {
if self.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)?;
}
}
}
let mut to_check_later = vec![];
for (i, output) in tx.output.iter().enumerate() {
// this output is ours, we have a path to derive it
if let Some((script_type, path)) = self.get_path(&output.script_pubkey)? {
debug!("{} output #{} is mine, adding utxo", txid, i);
updates.set_utxo(&UTXO {
outpoint: OutPoint::new(tx.txid(), i as u32),
txout: output.clone(),
})?;
incoming += output.value;
if output.script_pubkey != *cur_script {
debug!("{} output #{} script {} was not current script, adding script to be checked later", txid, i, output.script_pubkey);
to_check_later.push(output.script_pubkey.clone())
}
// derive as many change addrs as external addresses that we've seen
if script_type == ScriptType::Internal
&& u32::from(path.as_ref()[0]) > *change_max_deriv
{
*change_max_deriv = u32::from(path.as_ref()[0]);
}
}
}
let tx = TransactionDetails {
txid: tx.txid(),
transaction: Some(tx),
received: incoming,
sent: outgoing,
height,
timestamp: 0,
};
info!("Saving tx {}", txid);
updates.set_tx(&tx)?;
self.database.borrow_mut().commit_batch(updates)?;
Ok(to_check_later)
}
fn check_history(
&self,
script_pubkey: Script,
txs: Vec<GetHistoryRes>,
change_max_deriv: &mut u32,
) -> Result<Vec<Script>, Error> {
let mut to_check_later = Vec::new();
debug!(
"history of {} script {} has {} tx",
Address::from_script(&script_pubkey, self.network).unwrap(),
script_pubkey,
txs.len()
);
for tx in txs {
let height: Option<u32> = match tx.height {
0 | -1 => None,
x => u32::try_from(x).ok(),
};
to_check_later.extend_from_slice(&self.check_tx_and_descendant(
&tx.tx_hash,
height,
&script_pubkey,
change_max_deriv,
)?);
}
Ok(to_check_later)
}
pub fn sync(
&self,
max_address: Option<u32>,
batch_query_size: Option<usize>,
) -> Result<(), Error> {
debug!("begin sync...");
// TODO: consider taking an RwLock as writere here to prevent other "read-only" calls to
// break because the db is in an inconsistent state
let max_address = if self.descriptor.is_fixed() {
0
} else {
max_address.unwrap_or(100)
};
let batch_query_size = batch_query_size.unwrap_or(20);
let stop_gap = batch_query_size;
let path = DerivationPath::from(vec![ChildNumber::Normal { index: max_address }]);
let last_addr = self
.database
.borrow()
.get_script_pubkey_from_path(ScriptType::External, &path)?;
// cache a few of our addresses
if last_addr.is_none() {
let mut address_batch = self.database.borrow().begin_batch();
let start = Instant::now();
for i in 0..=max_address {
let derived = self.descriptor.derive(i).unwrap();
let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]);
address_batch.set_script_pubkey(
&derived.script_pubkey(),
ScriptType::External,
&full_path,
)?;
}
if self.change_descriptor.is_some() {
for i in 0..=max_address {
let derived = self.change_descriptor.as_ref().unwrap().derive(i).unwrap();
let full_path = DerivationPath::from(vec![ChildNumber::Normal { index: i }]);
address_batch.set_script_pubkey(
&derived.script_pubkey(),
ScriptType::Internal,
&full_path,
)?;
}
}
info!(
"derivation of {} addresses, took {} ms",
max_address,
start.elapsed().as_millis()
);
self.database.borrow_mut().commit_batch(address_batch)?;
}
// check unconfirmed tx, delete so they are retrieved later
let mut del_batch = self.database.borrow().begin_batch();
for tx in self.database.borrow().iter_txs(false)? {
if tx.height.is_none() {
del_batch.del_tx(&tx.txid, false)?;
}
}
self.database.borrow_mut().commit_batch(del_batch)?;
// maximum derivation index for a change address that we've seen during sync
let mut change_max_deriv = 0;
let mut already_checked: HashSet<Script> = HashSet::new();
let mut to_check_later = VecDeque::with_capacity(batch_query_size);
// insert the first chunk
let mut iter_scriptpubkeys = self
.database
.borrow()
.iter_script_pubkeys(Some(ScriptType::External))?
.into_iter();
let chunk: Vec<Script> = iter_scriptpubkeys.by_ref().take(batch_query_size).collect();
for item in chunk.into_iter().rev() {
to_check_later.push_front(item);
}
let mut iterating_external = true;
let mut index = 0;
let mut last_found = 0;
while !to_check_later.is_empty() {
trace!("to_check_later size {}", to_check_later.len());
let until = cmp::min(to_check_later.len(), batch_query_size);
let chunk: Vec<Script> = to_check_later.drain(..until).collect();
let call_result = self
.client
.as_ref()
.unwrap()
.borrow_mut()
2020-02-17 14:22:53 +01:00
.batch_script_get_history(chunk.iter())?;
2020-02-07 23:22:28 +01:00
for (script, history) in chunk.into_iter().zip(call_result.into_iter()) {
trace!("received history for {:?}, size {}", script, history.len());
if !history.is_empty() {
last_found = index;
let mut check_later_scripts = self
.check_history(script, history, &mut change_max_deriv)?
.into_iter()
.filter(|x| already_checked.insert(x.clone()))
.collect();
to_check_later.append(&mut check_later_scripts);
}
index += 1;
}
match iterating_external {
true if index - last_found >= stop_gap => iterating_external = false,
true => {
trace!("pushing one more batch from `iter_scriptpubkeys`. index = {}, last_found = {}, stop_gap = {}", index, last_found, stop_gap);
let chunk: Vec<Script> =
iter_scriptpubkeys.by_ref().take(batch_query_size).collect();
for item in chunk.into_iter().rev() {
to_check_later.push_front(item);
}
}
_ => {}
}
}
// check utxo
// TODO: try to minimize network requests and re-use scripts if possible
let mut batch = self.database.borrow().begin_batch();
for chunk in ChunksIterator::new(
self.database.borrow().iter_utxos()?.into_iter(),
batch_query_size,
) {
let scripts: Vec<_> = chunk.iter().map(|u| &u.txout.script_pubkey).collect();
let call_result = self
.client
.as_ref()
.unwrap()
.borrow_mut()
.batch_script_list_unspent(scripts)?;
// check which utxos are actually still unspent
for (utxo, list_unspent) in chunk.into_iter().zip(call_result.iter()) {
debug!(
"outpoint {:?} is unspent for me, list unspent is {:?}",
utxo.outpoint, list_unspent
);
let mut spent = true;
for unspent in list_unspent {
let res_outpoint = OutPoint::new(unspent.tx_hash, unspent.tx_pos as u32);
if utxo.outpoint == res_outpoint {
spent = false;
break;
}
}
if spent {
info!("{} not anymore unspent, removing", utxo.outpoint);
batch.del_utxo(&utxo.outpoint)?;
}
}
}
let current_ext = self
.database
.borrow()
.get_last_index(ScriptType::External)?
.unwrap_or(0);
let first_ext_new = last_found as u32 + 1;
if first_ext_new > current_ext {
info!("Setting external index to {}", first_ext_new);
self.database
.borrow_mut()
.set_last_index(ScriptType::External, first_ext_new)?;
}
let current_int = self
.database
.borrow()
.get_last_index(ScriptType::Internal)?
.unwrap_or(0);
let first_int_new = change_max_deriv + 1;
if first_int_new > current_int {
info!("Setting internal index to {}", first_int_new);
self.database
.borrow_mut()
.set_last_index(ScriptType::Internal, first_int_new)?;
}
self.database.borrow_mut().commit_batch(batch)?;
Ok(())
}
2020-04-21 16:39:00 +02:00
pub fn broadcast(&self, psbt: PSBT) -> Result<(Txid, Transaction), Error> {
2020-02-07 23:22:28 +01:00
let extracted = psbt.extract_tx();
self.client
.as_ref()
.unwrap()
.borrow_mut()
.transaction_broadcast(&extracted)?;
2020-04-21 16:39:00 +02:00
Ok((extracted.txid(), extracted))
2020-02-07 23:22:28 +01:00
}
}