From e32559a06a6070058bb1243b02f871bcf0af9102 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Wed, 6 May 2020 17:17:14 +0200 Subject: [PATCH] [wallet] Add an option to change the assumed current height --- examples/repl.rs | 14 +++++++++++- src/blockchain/electrum.rs | 6 +++++ src/blockchain/mod.rs | 6 +++++ src/wallet/mod.rs | 47 +++++++++++++++++++++++++++++--------- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index 66598514..13ee0951 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -161,6 +161,15 @@ fn main() { .takes_value(true) .number_of_values(1) .required(true), + ) + .arg( + Arg::with_name("assume_height") + .long("assume_height") + .value_name("HEIGHT") + .help("Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor") + .takes_value(true) + .number_of_values(1) + .required(false), )) .subcommand( SubCommand::with_name("broadcast") @@ -322,7 +331,10 @@ fn main() { } else if let Some(sub_matches) = matches.subcommand_matches("sign") { let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap(); let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); - let (psbt, finalized) = wallet.sign(psbt).unwrap(); + let assume_height = sub_matches + .value_of("assume_height") + .and_then(|s| Some(s.parse().unwrap())); + let (psbt, finalized) = wallet.sign(psbt, assume_height).unwrap(); println!("PSBT: {}", base64::encode(&serialize(&psbt))); println!("Finalized: {}", finalized); diff --git a/src/blockchain/electrum.rs b/src/blockchain/electrum.rs index 63903fad..9195beb8 100644 --- a/src/blockchain/electrum.rs +++ b/src/blockchain/electrum.rs @@ -29,6 +29,10 @@ impl Blockchain for ElectrumBlockchain { fn offline() -> Self { ElectrumBlockchain(None) } + + fn is_online(&self) -> bool { + self.0.is_some() + } } impl OnlineBlockchain for ElectrumBlockchain { @@ -190,6 +194,8 @@ impl OnlineBlockchain for ElectrumBlockchain { } fn get_height(&mut self) -> Result { + // TODO: unsubscribe when added to the client, or is there a better call to use here? + Ok(self .0 .as_mut() diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 5ea3fdb1..1060f095 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -18,6 +18,8 @@ pub enum Capability { } pub trait Blockchain { + fn is_online(&self) -> bool; + fn offline() -> Self; } @@ -26,6 +28,10 @@ impl Blockchain for OfflineBlockchain { fn offline() -> Self { OfflineBlockchain } + + fn is_online(&self) -> bool { + false + } } pub trait OnlineBlockchain: Blockchain { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 2350441d..b0332c13 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -31,14 +31,13 @@ use crate::types::*; pub type OfflineWallet = Wallet; -//#[cfg(feature = "electrum")] -//pub type ElectrumWallet = Wallet>, D>; - pub struct Wallet { descriptor: ExtendedDescriptor, change_descriptor: Option, network: Network, + current_height: Option, + client: RefCell, database: RefCell, } @@ -82,6 +81,8 @@ where change_descriptor, network, + current_height: None, + client: RefCell::new(B::offline()), database: RefCell::new(database), }) @@ -342,7 +343,7 @@ where } // TODO: define an enum for signing errors - pub fn sign(&self, mut psbt: PSBT) -> Result<(PSBT, bool), Error> { + pub fn sign(&self, mut psbt: PSBT, assume_height: Option) -> Result<(PSBT, bool), Error> { // this helps us doing our job later self.add_hd_keypaths(&mut psbt)?; @@ -482,7 +483,7 @@ where } // attempt to finalize - let finalized = self.finalize_psbt(tx.clone(), &mut psbt); + let finalized = self.finalize_psbt(tx.clone(), &mut psbt, assume_height)?; Ok((psbt, finalized)) } @@ -635,7 +636,12 @@ where Ok((answer, paths, selected_amount, fee_val)) } - fn finalize_psbt(&self, mut tx: Transaction, psbt: &mut PSBT) -> bool { + fn finalize_psbt( + &self, + mut tx: Transaction, + psbt: &mut PSBT, + assume_height: Option, + ) -> Result { for (n, input) in tx.input.iter_mut().enumerate() { // safe to run only on the descriptor because we assume the change descriptor also has // the same structure @@ -643,18 +649,33 @@ where debug!("reconstructed descriptor is {:?}", desc); let desc = match desc { - Err(_) => return false, + Err(_) => return Ok(false), Ok(desc) => desc, }; + // if the height is None in the database it means it's still unconfirmed, so consider + // that as a very high value + let create_height = self + .database + .borrow() + .get_tx(&input.previous_output.txid, false)? + .and_then(|tx| Some(tx.height.unwrap_or(std::u32::MAX))); + let current_height = assume_height.or(self.current_height); + + debug!( + "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}", + n, input.previous_output, create_height, current_height + ); + // TODO: use height once we sync headers - let satisfier = PSBTSatisfier::new(&psbt.inputs[n], true, None, None); + let satisfier = + PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height); match desc.satisfy(input, satisfier) { Ok(_) => continue, Err(e) => { debug!("satisfy error {:?} for input {}", e, n); - return false; + return Ok(false); } } } @@ -665,7 +686,7 @@ where psbt_input.final_script_witness = Some(input.witness); } - true + Ok(true) } } @@ -679,7 +700,7 @@ where change_descriptor: Option<&str>, network: Network, mut database: D, - client: B, + mut client: B, ) -> Result { database.check_descriptor_checksum( ScriptType::External, @@ -703,11 +724,15 @@ where None => None, }; + let current_height = Some(client.get_height()? as u32); + Ok(Wallet { descriptor, change_descriptor, network, + current_height, + client: RefCell::new(client), database: RefCell::new(database), })