Skip to content
Closed
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
56 changes: 53 additions & 3 deletions crates/wallet/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
// 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, Weight};
use chain::{ChainPosition, ConfirmationBlockTime};

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 @@ -133,3 +135,51 @@ impl Utxo {
}
}
}

/// Represents a condition of a spending path 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 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