Skip to content

Commit 302faff

Browse files
committed
fix!: Account for MTP in locktime calculations
Correctly calculate spendability of coins that are timelocked by taking MTP into account. API changes are a result of the different requirements between relative and absolute locktimes.
1 parent 981889f commit 302faff

7 files changed

Lines changed: 201 additions & 82 deletions

File tree

examples/anti_fee_sniping.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ fn main() -> anyhow::Result<()> {
7171
let selection = wallet
7272
.all_candidates()
7373
.regroup(group_by_spk())
74-
.filter(filter_unspendable_now(tip_height, tip_time))
74+
.filter(filter_unspendable_now(tip_height, Some(tip_time)))
7575
.into_selection(
7676
selection_algorithm_lowest_fee_bnb(longterm_feerate, 100_000),
7777
SelectorParams::new(

examples/common.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use bdk_chain::{
66
};
77
use bdk_coin_select::{ChangePolicy, DrainWeights};
88
use bdk_testenv::{bitcoincore_rpc::RpcApi, TestEnv};
9-
use bdk_tx::{CanonicalUnspents, Input, InputCandidates, RbfParams, TxStatus, TxWithStatus};
9+
use bdk_tx::{
10+
CanonicalUnspents, ConfirmationStatus, Input, InputCandidates, RbfParams, TxWithStatus,
11+
};
1012
use bitcoin::{absolute, Address, Amount, BlockHash, OutPoint, Transaction, TxOut, Txid};
1113
use miniscript::{
1214
plan::{Assets, Plan},
@@ -71,17 +73,22 @@ impl Wallet {
7173
)
7274
}
7375

74-
/// TODO: Add to chain sources.
76+
/// Info for the block at the tip.
77+
///
78+
/// Returns a tuple of:
79+
/// - Tip's height. I.e. `tip.height`
80+
/// - Tip's MTP. I.e. `MTP(tip.height)`
7581
pub fn tip_info(
7682
&self,
7783
client: &impl RpcApi,
7884
) -> anyhow::Result<(absolute::Height, absolute::Time)> {
79-
let tip = self.chain.tip().block_id();
80-
let tip_info = client.get_block_header_info(&tip.hash)?;
81-
let tip_height = absolute::Height::from_consensus(tip.height)?;
82-
let tip_time =
83-
absolute::Time::from_consensus(tip_info.median_time.unwrap_or(tip_info.time) as _)?;
84-
Ok((tip_height, tip_time))
85+
let tip_hash = self.chain.tip().block_id().hash;
86+
let tip_info = client.get_block_header_info(&tip_hash)?;
87+
let tip_height = absolute::Height::from_consensus(tip_info.height as u32)?;
88+
let tip_mtp = absolute::Time::from_consensus(
89+
tip_info.median_time.expect("must have median time") as _,
90+
)?;
91+
Ok((tip_height, tip_mtp))
8592
}
8693

8794
// TODO: Maybe create an `AssetsBuilder` or `AssetsExt` that makes it easier to add
@@ -112,15 +119,16 @@ impl Wallet {
112119
}
113120

114121
pub fn canonical_txs(&self) -> impl Iterator<Item = TxWithStatus<Arc<Transaction>>> + '_ {
115-
pub fn status_from_position(pos: ChainPosition<ConfirmationBlockTime>) -> Option<TxStatus> {
122+
pub fn status_from_position(
123+
pos: ChainPosition<ConfirmationBlockTime>,
124+
) -> Option<ConfirmationStatus> {
116125
match pos {
117-
bdk_chain::ChainPosition::Confirmed { anchor, .. } => Some(TxStatus {
126+
bdk_chain::ChainPosition::Confirmed { anchor, .. } => Some(ConfirmationStatus {
118127
height: absolute::Height::from_consensus(
119128
anchor.confirmation_height_upper_bound(),
120129
)
121130
.expect("must convert to height"),
122-
time: absolute::Time::from_consensus(anchor.confirmation_time as _)
123-
.expect("must convert from time"),
131+
prev_mtp: None, // TODO: Use `CheckPoint::prev_mtp`
124132
}),
125133
bdk_chain::ChainPosition::Unconfirmed { .. } => None,
126134
}

examples/synopsis.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ fn main() -> anyhow::Result<()> {
3939
println!("Received {txid}");
4040
println!("Balance (pending): {}", wallet.balance());
4141

42-
let (tip_height, tip_time) = wallet.tip_info(env.rpc_client())?;
42+
let (tip_height, tip_mtp) = wallet.tip_info(env.rpc_client())?;
4343
let longterm_feerate = FeeRate::from_sat_per_vb_unchecked(1);
4444

4545
let recipient_addr = env
@@ -51,7 +51,7 @@ fn main() -> anyhow::Result<()> {
5151
let selection = wallet
5252
.all_candidates()
5353
.regroup(group_by_spk())
54-
.filter(filter_unspendable_now(tip_height, tip_time))
54+
.filter(filter_unspendable_now(tip_height, Some(tip_mtp)))
5555
.into_selection(
5656
selection_algorithm_lowest_fee_bnb(longterm_feerate, 100_000),
5757
SelectorParams::new(

src/canonical_unspents.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ use bitcoin::{psbt, OutPoint, Sequence, Transaction, TxOut, Txid};
66
use miniscript::{bitcoin, plan::Plan};
77

88
use crate::{
9-
collections::HashMap, input::CoinbaseMismatch, FromPsbtInputError, Input, RbfSet, TxStatus,
9+
collections::HashMap, input::CoinbaseMismatch, ConfirmationStatus, FromPsbtInputError, Input,
10+
RbfSet,
1011
};
1112

1213
/// Tx with confirmation status.
13-
pub type TxWithStatus<T> = (T, Option<TxStatus>);
14+
pub type TxWithStatus<T> = (T, Option<ConfirmationStatus>);
1415

1516
/// Our canonical view of unspent outputs.
1617
#[derive(Debug, Clone)]
1718
pub struct CanonicalUnspents {
1819
txs: HashMap<Txid, Arc<Transaction>>,
19-
statuses: HashMap<Txid, TxStatus>,
20+
statuses: HashMap<Txid, ConfirmationStatus>,
2021
spends: HashMap<OutPoint, Txid>,
2122
}
2223

0 commit comments

Comments
 (0)