Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet)!: use miniscript plan in place of old policy mod #1786

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions crates/wallet/src/descriptor/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ use crate::descriptor::ExtractPolicy;
use crate::keys::ExtScriptContext;
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{After, Older, SecpCtx};
use crate::Condition;

use super::checksum::calc_checksum;
use super::error::Error;
Expand Down Expand Up @@ -444,18 +445,6 @@ pub struct Policy {
pub contribution: Satisfaction,
}

/// An extra condition that must be satisfied but that is out of control of the user
/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
pub struct Condition {
/// Optional CheckSequenceVerify condition
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<Sequence>,
/// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<absolute::LockTime>,
}

impl Condition {
fn merge_nlocktime(
a: absolute::LockTime,
Expand All @@ -478,7 +467,7 @@ impl Condition {
}
}

pub(crate) fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
fn merge(mut self, other: &Condition) -> Result<Self, PolicyError> {
match (self.csv, other.csv) {
(Some(a), Some(b)) => self.csv = Some(Self::merge_nsequence(a, b)?),
(None, any) => self.csv = any,
Expand Down
5 changes: 5 additions & 0 deletions crates/wallet/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ pub fn get_test_single_sig_cltv() -> &'static str {
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
}

/// `wsh` descriptor with policy `and(pk(A),after(500000001))`
pub fn get_test_single_sig_cltv_timestamp() -> &'static str {
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(500000001)))"
}

/// taproot single key descriptor
pub fn get_test_tr_single_sig() -> &'static str {
"tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
Expand Down
97 changes: 94 additions & 3 deletions crates/wallet/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
// licenses.

use alloc::boxed::Box;
use chain::{ChainPosition, ConfirmationBlockTime};
use core::convert::AsRef;
use serde::{Deserialize, Serialize};

use bitcoin::transaction::{OutPoint, Sequence, TxOut};
use bitcoin::{psbt, Weight};
use bitcoin::{absolute, psbt, relative, Weight};
use chain::{ChainPosition, ConfirmationBlockTime};
use miniscript::plan::Plan;

use serde::{Deserialize, Serialize};
use crate::error::PlanError;
use crate::utils::merge_nsequence;

/// Types of keychains
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
Expand Down Expand Up @@ -77,6 +80,31 @@ pub struct WeightedUtxo {
pub utxo: Utxo,
}

/// A [`Utxo`] and accompanying [`Plan`]
#[derive(Debug, Clone)]
pub struct PlannedUtxo {
/// plan
pub plan: Plan,
/// utxo
pub utxo: Utxo,
}

impl PlannedUtxo {
/// Create new [`PlannedUtxo`]
pub fn new(plan: Plan, utxo: Utxo) -> Self {
Self { plan, utxo }
}

/// Get a weighted utxo
pub fn weighted_utxo(&self) -> WeightedUtxo {
let wu = self.plan.satisfaction_weight();
WeightedUtxo {
satisfaction_weight: Weight::from_wu_usize(wu),
utxo: self.utxo.clone(),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// An unspent transaction output (UTXO).
pub enum Utxo {
Expand Down Expand Up @@ -133,3 +161,66 @@ impl Utxo {
}
}
}

/// Represents a condition that must be satisfied
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, serde::Serialize)]
pub struct Condition {
/// sequence value used as the argument to `OP_CHECKSEQUENCEVERIFY`
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<Sequence>,
/// absolute timelock value used as the argument to `OP_CHECKLOCKTIMEVERIFY`
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<absolute::LockTime>,
}

impl Condition {
/// Merges two absolute locktimes. Errors if `a` and `b` do not have the same unit.
pub(crate) fn merge_abs_locktime(
a: Option<absolute::LockTime>,
b: Option<absolute::LockTime>,
) -> Result<Option<absolute::LockTime>, PlanError> {
match (a, b) {
(None, b) => Ok(b),
(a, None) => Ok(a),
(Some(a), Some(b)) => {
if a.is_block_height() != b.is_block_height() {
Err(PlanError::MixedTimelockUnits)
} else if b > a {
Ok(Some(b))
} else {
Ok(Some(a))
}
}
}
}

/// Merges two relative locktimes. Errors if `a` and `b` do not have the same unit.
pub(crate) fn merge_rel_locktime(
a: Option<relative::LockTime>,
b: Option<relative::LockTime>,
) -> Result<Option<relative::LockTime>, PlanError> {
match (a, b) {
(a, None) => Ok(a),
(None, b) => Ok(b),
(Some(a), Some(b)) => {
let seq = merge_nsequence(a.to_sequence(), b.to_sequence())?;
Ok(seq.to_relative_lock_time())
}
}
}

/// Merges the conditions of `other` with `self` keeping the greater of the two
/// for each individual condition. Locktime types must not be mixed or else a
/// [`PlanError`] is returned.
pub(crate) fn merge_condition(mut self, other: Condition) -> Result<Self, PlanError> {
self.timelock = Self::merge_abs_locktime(self.timelock, other.timelock)?;

match (self.csv, other.csv) {
(Some(a), Some(b)) => self.csv = Some(merge_nsequence(a, b)?),
(None, b) => self.csv = b,
_ => {}
}

Ok(self)
}
}
45 changes: 28 additions & 17 deletions crates/wallet/src/wallet/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@

//! Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)

use crate::descriptor::policy::PolicyError;
use crate::descriptor::DescriptorError;
use crate::descriptor::{self, DescriptorError};
use crate::wallet::coin_selection;
use crate::{descriptor, KeychainKind};
use alloc::string::String;
use bitcoin::{absolute, psbt, Amount, OutPoint, Sequence, Txid};
use core::fmt;
use miniscript::{DefiniteDescriptorKey, Descriptor};

/// Errors returned by miniscript when updating inconsistent PSBTs
#[derive(Debug, Clone)]
Expand All @@ -43,17 +42,34 @@ impl fmt::Display for MiniscriptPsbtError {
#[cfg(feature = "std")]
impl std::error::Error for MiniscriptPsbtError {}

/// Error when preparing the conditions of a spending plan
#[derive(Debug, Clone)]
pub enum PlanError {
/// Attempted to mix height- and time-based locktimes
MixedTimelockUnits,
/// Error creating a spend [`Plan`](miniscript::plan::Plan)
Plan(Descriptor<DefiniteDescriptorKey>),
}

impl fmt::Display for PlanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MixedTimelockUnits => write!(f, "cannot mix locktime units"),
Self::Plan(d) => write!(f, "failed to create plan for descriptor {}", d),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for PlanError {}

#[derive(Debug)]
/// Error returned from [`TxBuilder::finish`]
///
/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish
pub enum CreateTxError {
/// There was a problem with the descriptors passed in
Descriptor(DescriptorError),
/// There was a problem while extracting and manipulating policies
Policy(PolicyError),
/// Spending policy is not compatible with this [`KeychainKind`]
SpendingPolicyRequired(KeychainKind),
/// Requested invalid transaction version '0'
Version0,
/// Requested transaction version `1`, but at least `2` is needed to use OP_CSV
Expand Down Expand Up @@ -104,16 +120,14 @@ pub enum CreateTxError {
MissingNonWitnessUtxo(OutPoint),
/// Miniscript PSBT error
MiniscriptPsbt(MiniscriptPsbtError),
/// Error creating a spending plan
Plan(PlanError),
}

impl fmt::Display for CreateTxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Descriptor(e) => e.fmt(f),
Self::Policy(e) => e.fmt(f),
CreateTxError::SpendingPolicyRequired(keychain_kind) => {
write!(f, "Spending policy required: {:?}", keychain_kind)
}
CreateTxError::Version0 => {
write!(f, "Invalid version `0`")
}
Expand Down Expand Up @@ -171,6 +185,9 @@ impl fmt::Display for CreateTxError {
CreateTxError::MiniscriptPsbt(err) => {
write!(f, "Miniscript PSBT error: {}", err)
}
CreateTxError::Plan(e) => {
write!(f, "{}", e)
}
}
}
}
Expand All @@ -181,12 +198,6 @@ impl From<descriptor::error::Error> for CreateTxError {
}
}

impl From<PolicyError> for CreateTxError {
fn from(err: PolicyError) -> Self {
CreateTxError::Policy(err)
}
}

impl From<MiniscriptPsbtError> for CreateTxError {
fn from(err: MiniscriptPsbtError) -> Self {
CreateTxError::MiniscriptPsbt(err)
Expand Down
Loading
Loading