.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")
} 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);
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> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
network: Network,
+ current_height: Option<u32>,
+
client: RefCell<B>,
database: RefCell<D>,
}
change_descriptor,
network,
+ current_height: None,
+
client: RefCell::new(B::offline()),
database: RefCell::new(database),
})
}
// 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
self.add_hd_keypaths(&mut psbt)?;
}
// 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((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() {
// safe to run only on the descriptor because we assume the change descriptor also has
// the same structure
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);
}
}
}
psbt_input.final_script_witness = Some(input.witness);
}
- true
+ Ok(true)
}
}
change_descriptor: Option<&str>,
network: Network,
mut database: D,
- client: B,
+ mut client: B,
) -> Result<Self, Error> {
database.check_descriptor_checksum(
ScriptType::External,
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),
})