diff --git a/src/cli.rs b/src/cli.rs index 9dc37a32..3dd23c04 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::str::FromStr; use clap::{App, Arg, ArgMatches, SubCommand}; @@ -110,7 +111,7 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> { Arg::with_name("policy") .long("policy") .value_name("POLICY") - .help("Selects which policy will be used to satisfy the descriptor") + .help("Selects which policy should be used to satisfy the descriptor") .takes_value(true) .number_of_values(1), ), @@ -252,9 +253,9 @@ where let unspendable = sub_matches .values_of("unspendable") .map(|s| s.map(|i| parse_outpoint(i).unwrap()).collect()); - let policy: Option> = sub_matches + let policy: Option<_> = sub_matches .value_of("policy") - .map(|s| serde_json::from_str::>>(&s).unwrap()); + .map(|s| serde_json::from_str::>>(&s).unwrap()); let result = wallet.create_tx( addressees, diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 9955164b..4e10390b 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -14,6 +14,7 @@ use miniscript::{Descriptor, Miniscript, Terminal}; #[allow(unused_imports)] use log::{debug, error, info, trace}; +use super::checksum::get_checksum; use super::error::Error; use crate::descriptor::{Key, MiniscriptExtractPolicy}; use crate::psbt::PSBTSatisfier; @@ -93,6 +94,11 @@ impl SatisfiableItem { _ => true, } } + + pub fn id(&self) -> String { + get_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem")) + .expect("Failed to compute a SatisfiableItem id") + } } fn combinations(vec: &Vec, size: usize) -> Vec> { @@ -328,6 +334,8 @@ impl From for Satisfaction { #[derive(Debug, Clone, Serialize)] pub struct Policy { + id: String, + #[serde(flatten)] item: SatisfiableItem, satisfaction: Satisfaction, @@ -376,8 +384,8 @@ impl Condition { #[derive(Debug)] pub enum PolicyError { - NotEnoughItemsSelected(usize), - TooManyItemsSelected(usize), + NotEnoughItemsSelected(String), + TooManyItemsSelected(String), IndexOutOfRange(usize), AddOnLeaf, AddOnPartialComplete, @@ -388,6 +396,7 @@ pub enum PolicyError { impl Policy { pub fn new(item: SatisfiableItem) -> Self { Policy { + id: item.id(), item, satisfaction: Satisfaction::None, contribution: Satisfaction::None, @@ -479,17 +488,12 @@ impl Policy { } pub fn requires_path(&self) -> bool { - self.get_requirements(&vec![]).is_err() + self.get_requirements(&BTreeMap::new()).is_err() } - pub fn get_requirements(&self, path: &Vec>) -> Result { - self.recursive_get_requirements(path, 0) - } - - fn recursive_get_requirements( + pub fn get_requirements( &self, - path: &Vec>, - index: usize, + path: &BTreeMap>, ) -> Result { // if items.len() == threshold, selected can be omitted and we take all of them by default let default = match &self.item { @@ -498,7 +502,7 @@ impl Policy { } _ => vec![], }; - let selected = match path.get(index) { + let selected = match path.get(&self.id) { _ if !default.is_empty() => &default, Some(arr) => arr, _ => &default, @@ -508,7 +512,7 @@ impl Policy { SatisfiableItem::Thresh { items, threshold } => { let mapped_req = items .iter() - .map(|i| i.recursive_get_requirements(path, index + 1)) + .map(|i| i.get_requirements(path)) .collect::, _>>()?; // if all the requirements are null we don't care about `selected` because there @@ -521,7 +525,9 @@ impl Policy { // an empty value for this step in case of n-of-n, because `selected` is set to all // the elements above if selected.len() < *threshold { - return Err(PolicyError::NotEnoughItemsSelected(index)); + return Err(PolicyError::NotEnoughItemsSelected(self.id.clone())); + } else if selected.len() > *threshold { + return Err(PolicyError::TooManyItemsSelected(self.id.clone())); } // check the selected items, see if there are conflicting requirements @@ -536,7 +542,7 @@ impl Policy { Ok(requirements) } - _ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)), + _ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(self.id.clone())), SatisfiableItem::AbsoluteTimelock { value } => Ok(Condition { csv: None, timelock: Some(*value), diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index c0451230..7ebbdd99 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -126,7 +126,7 @@ where addressees: Vec<(Address, u64)>, send_all: bool, fee_perkb: f32, - policy_path: Option>>, + policy_path: Option>>, utxos: Option>, unspendable: Option>, ) -> Result<(PSBT, TransactionDetails), Error> { @@ -134,7 +134,7 @@ where if policy.requires_path() && policy_path.is_none() { return Err(Error::SpendingPolicyRequired); } - let requirements = policy.get_requirements(&policy_path.unwrap_or(vec![]))?; + let requirements = policy.get_requirements(&policy_path.unwrap_or(BTreeMap::new()))?; debug!("requirements: {:?}", requirements); let mut tx = Transaction {