# `miniscript::plan` API Suggestions code: https://github.com/ValuedMammal/bdk/tree/feat/plan ## Motivation - The `Plan` construct lets us get the satisfaction weight for a particular spend path which is important for coin selection. For complex descriptors where some paths are more expensive than others this will be more advantageous than relying on the max weight to satisfy. - `Plan` allows updating a PSBT in a smarter way by only filling in fields that relate to the chosen spend assets. ## Design * Add member `assets` to `TxParams` * Add method `TxBuilder::add_assets` ```rust pub struct TxParams { pub(crate) assets: Option<Assets>, ... } ``` * Add method `Wallet::try_plan` for creating a spend plan for a local output ```rust impl Wallet { pub fn try_plan(&self, output: &LocalOutput, assets: &Assets) -> Result<Plan, PlanError> { ... } } ``` At a minimum the wallet can collect a base set of assets by scanning the descriptor. This won't be sufficient for all kinds of assets, but ensures the wallet isn't left without any. So for a basic single-sig wallet everything should just work without having to manually supply "key" assets. If a descriptor has different spend paths with timelocks, then the user will add them via `TxBuilder`, for example ```rust let desc = "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"; let (mut wallet, _) = get_funded_wallet(desc); let rel_locktime = relative::LockTime::from_height(6); let mut builder = wallet.build_tx(); builder.add_assets(Assets::new().older(rel_locktime)); ``` In the above example, we assume the wallet has already added the `pk` by inferring it from the descriptor. Then the user adds the `rel_locktime` using `older` to indicate they intend to meet this condition as well. *Note that in the ideal case the wallet should be smart enough to satisfy all the conditions it knows about. For example if the policy of the descriptor mandates that an output be `n` blocks old before it can be spent, we'll know whether a coin is eligible to be used in a transaction by computing its age as the difference between the current local height and the confirmation height of the coin. <hr> Internally we use a descriptor's satisfaction weight to figure the cost associated with spending an output. Instead of using `max_weight_to_satisfy` which could be a lot for a complex descriptor, we use `Plan::satisfaction_weight` where the particular spend plan is built from a descriptor derived at a definite index given by a `LocalOutput`. ```rust let desc = self.public_descriptor(keychain); let definite_desc = desc .at_derivation_index(index) .expect("must be valid index"); let plan = definite_desc.plan(&assets)?; let satisfaction_weight = plan.satisfaction_weight(); ``` This `plan` method will fail if the given assets aren't enough to create a plan, for example if we forgot to set `older` in the wallet example above. It's ok if we cannot successfully plan every possible utxo, however if we have coins available and we're unable to produce any plans, then we return a `PlanError` to the user clearly stating the assets are insufficient. ### UX How to supply assets? The most common asset is a "key" for which we intend to produce a signature because any normal bitcoin script will contain at least one CHECKSIG. Other kinds of assets are hash preimages and timelocks. Note however the design of the `Assets` type is such that we're not required to handle any key material. A "key" in this context is actually just metadata for the key-source (aka origin) and appears like this `((Fingerprint, DerivationPath), CanSign)`. So if we have a descriptor with many possible spending paths, the particular path to use is defined by which "keys" we decide to include. ```rust let fingerprint = Fingerprint::from_hex("f6a5cb8b").unwrap(); let derivation_path = DerivationPath::from_str("86h/0h/0h/0").unwrap(); let origin = (fingerprint, derivation_path); let abs_locktime = absolute::LockTime::from_consensus(1732934156); let mut assets = Assets::new().after(abs_locktime); assets.keys.insert((origin, CanSign::default())); ``` The business of gathering the desired "keys" can be made less tedious by having a method of `DescriptorExt` for finding a DescriptorPublicKey based on a predicate and then if found, adding it to the asset collection. ```rust pub trait DescriptorExt { /// Find the [`DescriptorPublicKey`] which matches a predicate if it can be found on the descriptor. fn find_descriptor_pubkey<F>(&self, pred: F) -> Option<&DescriptorPublicKey> where F: Fn(&DescriptorPublicKey) -> bool; } ``` - Maybe create an `AssetsBuilder` that makes it easy to add assets from a descriptor. ### Deployment Three phases 1. Update `example_cli` to use ms `plan` 2. Integrate ms plan into wallet internals 3. Consider using plans to finalize psbt inputs? ### Open questions - Should all "foreign" inputs also be required to provide a plan? - Consider having a method `add_plan_utxo` to include the plan directly. (Is this a better UX than add_assets?) - With the above plan we're looking for a drop-in replacement for bdk's current policy module. If successful, will there be a reason to keep around the old policy mod? Is it a serious loss or cost to UX if we nuke it? - pros: better feature, development QoL, less maintenance - cons: removing `policies` method means we can no longer view the Policy structure