[wallet] Add an option to change the assumed current height

This commit is contained in:
Alekos Filini 2020-05-06 17:17:14 +02:00
parent 45aa001e10
commit e32559a06a
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
4 changed files with 61 additions and 12 deletions

View File

@ -161,6 +161,15 @@ fn main() {
.takes_value(true) .takes_value(true)
.number_of_values(1) .number_of_values(1)
.required(true), .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(
SubCommand::with_name("broadcast") SubCommand::with_name("broadcast")
@ -322,7 +331,10 @@ fn main() {
} else if let Some(sub_matches) = matches.subcommand_matches("sign") { } else if let Some(sub_matches) = matches.subcommand_matches("sign") {
let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap(); let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
let psbt: PartiallySignedTransaction = deserialize(&psbt).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!("PSBT: {}", base64::encode(&serialize(&psbt)));
println!("Finalized: {}", finalized); println!("Finalized: {}", finalized);

View File

@ -29,6 +29,10 @@ impl<T: Read + Write> Blockchain for ElectrumBlockchain<T> {
fn offline() -> Self { fn offline() -> Self {
ElectrumBlockchain(None) ElectrumBlockchain(None)
} }
fn is_online(&self) -> bool {
self.0.is_some()
}
} }
impl<T: Read + Write> OnlineBlockchain for ElectrumBlockchain<T> { impl<T: Read + Write> OnlineBlockchain for ElectrumBlockchain<T> {
@ -190,6 +194,8 @@ impl<T: Read + Write> OnlineBlockchain for ElectrumBlockchain<T> {
} }
fn get_height(&mut self) -> Result<usize, Error> { fn get_height(&mut self) -> Result<usize, Error> {
// TODO: unsubscribe when added to the client, or is there a better call to use here?
Ok(self Ok(self
.0 .0
.as_mut() .as_mut()

View File

@ -18,6 +18,8 @@ pub enum Capability {
} }
pub trait Blockchain { pub trait Blockchain {
fn is_online(&self) -> bool;
fn offline() -> Self; fn offline() -> Self;
} }
@ -26,6 +28,10 @@ impl Blockchain for OfflineBlockchain {
fn offline() -> Self { fn offline() -> Self {
OfflineBlockchain OfflineBlockchain
} }
fn is_online(&self) -> bool {
false
}
} }
pub trait OnlineBlockchain: Blockchain { pub trait OnlineBlockchain: Blockchain {

View File

@ -31,14 +31,13 @@ use crate::types::*;
pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>; pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
//#[cfg(feature = "electrum")]
//pub type ElectrumWallet<S, D> = Wallet<crate::blockchain::ElectrumBlockchain<electrum_client::Client<S>>, D>;
pub struct Wallet<B: Blockchain, D: BatchDatabase> { pub struct Wallet<B: Blockchain, D: BatchDatabase> {
descriptor: ExtendedDescriptor, descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>, change_descriptor: Option<ExtendedDescriptor>,
network: Network, network: Network,
current_height: Option<u32>,
client: RefCell<B>, client: RefCell<B>,
database: RefCell<D>, database: RefCell<D>,
} }
@ -82,6 +81,8 @@ where
change_descriptor, change_descriptor,
network, network,
current_height: None,
client: RefCell::new(B::offline()), client: RefCell::new(B::offline()),
database: RefCell::new(database), database: RefCell::new(database),
}) })
@ -342,7 +343,7 @@ where
} }
// TODO: define an enum for signing errors // 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<u32>) -> Result<(PSBT, bool), Error> {
// this helps us doing our job later // this helps us doing our job later
self.add_hd_keypaths(&mut psbt)?; self.add_hd_keypaths(&mut psbt)?;
@ -482,7 +483,7 @@ where
} }
// attempt to finalize // 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)) Ok((psbt, finalized))
} }
@ -635,7 +636,12 @@ where
Ok((answer, paths, selected_amount, fee_val)) 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<u32>,
) -> Result<bool, Error> {
for (n, input) in tx.input.iter_mut().enumerate() { for (n, input) in tx.input.iter_mut().enumerate() {
// safe to run only on the descriptor because we assume the change descriptor also has // safe to run only on the descriptor because we assume the change descriptor also has
// the same structure // the same structure
@ -643,18 +649,33 @@ where
debug!("reconstructed descriptor is {:?}", desc); debug!("reconstructed descriptor is {:?}", desc);
let desc = match desc { let desc = match desc {
Err(_) => return false, Err(_) => return Ok(false),
Ok(desc) => desc, 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 // 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) { match desc.satisfy(input, satisfier) {
Ok(_) => continue, Ok(_) => continue,
Err(e) => { Err(e) => {
debug!("satisfy error {:?} for input {}", e, n); 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); psbt_input.final_script_witness = Some(input.witness);
} }
true Ok(true)
} }
} }
@ -679,7 +700,7 @@ where
change_descriptor: Option<&str>, change_descriptor: Option<&str>,
network: Network, network: Network,
mut database: D, mut database: D,
client: B, mut client: B,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
database.check_descriptor_checksum( database.check_descriptor_checksum(
ScriptType::External, ScriptType::External,
@ -703,11 +724,15 @@ where
None => None, None => None,
}; };
let current_height = Some(client.get_height()? as u32);
Ok(Wallet { Ok(Wallet {
descriptor, descriptor,
change_descriptor, change_descriptor,
network, network,
current_height,
client: RefCell::new(client), client: RefCell::new(client),
database: RefCell::new(database), database: RefCell::new(database),
}) })