Skip to content

Commit cfac1aa

Browse files
committed
feat: Add wallet::locked_outpoints::ChangeSet
1 parent 618f2fa commit cfac1aa

File tree

5 files changed

+104
-54
lines changed

5 files changed

+104
-54
lines changed

clippy.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# TODO fix, see: https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
2-
enum-variant-size-threshold = 1064
2+
enum-variant-size-threshold = 2048
33
# TODO fix, see: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err
4-
large-error-threshold = 1024
4+
large-error-threshold = 2048

wallet/src/wallet/changeset.rs

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use alloc::collections::BTreeMap;
2-
31
use bdk_chain::{
42
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
53
};
@@ -10,7 +8,7 @@ use serde::{Deserialize, Serialize};
108
type IndexedTxGraphChangeSet =
119
indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>;
1210

13-
use crate::UtxoLock;
11+
use crate::locked_outpoints;
1412

1513
/// A change set for [`Wallet`]
1614
///
@@ -120,7 +118,7 @@ pub struct ChangeSet {
120118
/// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
121119
pub indexer: keychain_txout::ChangeSet,
122120
/// Changes to locked outpoints.
123-
pub locked_outpoints: BTreeMap<OutPoint, UtxoLock>,
121+
pub locked_outpoints: locked_outpoints::ChangeSet,
124122
}
125123

126124
impl Merge for ChangeSet {
@@ -149,10 +147,8 @@ impl Merge for ChangeSet {
149147
self.network = other.network;
150148
}
151149

152-
// To merge `locked_outpoints` we extend the existing collection. If there's
153-
// an existing entry for a given outpoint, it is overwritten by the
154-
// new utxo lock.
155-
self.locked_outpoints.extend(other.locked_outpoints);
150+
// merge locked outpoints
151+
self.locked_outpoints.merge(other.locked_outpoints);
156152

157153
Merge::merge(&mut self.local_chain, other.local_chain);
158154
Merge::merge(&mut self.tx_graph, other.tx_graph);
@@ -262,16 +258,16 @@ impl ChangeSet {
262258
row.get::<_, Option<u32>>("expiration_height")?,
263259
))
264260
})?;
261+
let locked_outpoints = &mut changeset.locked_outpoints;
265262
for row in rows {
266263
let (Impl(txid), vout, is_locked, expiration_height) = row?;
267-
let utxo_lock = UtxoLock {
268-
outpoint: OutPoint::new(txid, vout),
269-
is_locked,
270-
expiration_height,
271-
};
272-
changeset
264+
let outpoint = OutPoint::new(txid, vout);
265+
locked_outpoints
273266
.locked_outpoints
274-
.insert(utxo_lock.outpoint, utxo_lock);
267+
.insert(outpoint, is_locked);
268+
locked_outpoints
269+
.expiration_heights
270+
.insert(outpoint, expiration_height);
275271
}
276272

277273
changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?;
@@ -324,16 +320,30 @@ impl ChangeSet {
324320

325321
// Insert locked outpoints.
326322
let mut stmt = db_tx.prepare_cached(&format!(
327-
"INSERT INTO {}(txid, vout, is_locked, expiration_height) VALUES(:txid, :vout, :is_locked, :expiration_height) ON CONFLICT DO UPDATE SET is_locked=:is_locked, expiration_height=:expiration_height",
323+
"INSERT INTO {}(txid, vout, is_locked) VALUES(:txid, :vout, :is_locked) ON CONFLICT DO UPDATE SET is_locked=:is_locked",
324+
Self::WALLET_OUTPOINT_LOCK_TABLE_NAME,
325+
))?;
326+
let locked_outpoints = &self.locked_outpoints.locked_outpoints;
327+
for (&outpoint, is_locked) in locked_outpoints.iter() {
328+
let OutPoint { txid, vout } = outpoint;
329+
stmt.execute(named_params! {
330+
":txid": Impl(txid),
331+
":vout": vout,
332+
":is_locked": is_locked,
333+
})?;
334+
}
335+
// Insert locked outpoints expiration heights.
336+
let mut stmt = db_tx.prepare_cached(&format!(
337+
"INSERT INTO {}(txid, vout, expiration_height) VALUES(:txid, :vout, :expiration_height) ON CONFLICT DO UPDATE SET expiration_height=:expiration_height",
328338
Self::WALLET_OUTPOINT_LOCK_TABLE_NAME,
329339
))?;
330-
for (&outpoint, utxo_lock) in &self.locked_outpoints {
340+
let expiration_heights = &self.locked_outpoints.expiration_heights;
341+
for (&outpoint, expiration_height) in expiration_heights.iter() {
331342
let OutPoint { txid, vout } = outpoint;
332343
stmt.execute(named_params! {
333344
":txid": Impl(txid),
334345
":vout": vout,
335-
":is_locked": utxo_lock.is_locked,
336-
":expiration_height": utxo_lock.expiration_height,
346+
":expiration_height": expiration_height,
337347
})?;
338348
}
339349

wallet/src/wallet/locked_outpoints.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//! Module containing the locked outpoints change set.
2+
3+
use bdk_chain::Merge;
4+
use bitcoin::OutPoint;
5+
use serde::{Deserialize, Serialize};
6+
7+
use crate::collections::BTreeMap;
8+
9+
/// Represents changes to locked outpoints.
10+
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
11+
pub struct ChangeSet {
12+
/// The lock status of an outpoint, `true == is_locked`.
13+
pub locked_outpoints: BTreeMap<OutPoint, bool>,
14+
/// The expiration height of the lock.
15+
pub expiration_heights: BTreeMap<OutPoint, Option<u32>>,
16+
}
17+
18+
impl Merge for ChangeSet {
19+
fn merge(&mut self, other: Self) {
20+
self.locked_outpoints.extend(other.locked_outpoints);
21+
self.expiration_heights.extend(other.expiration_heights);
22+
}
23+
24+
fn is_empty(&self) -> bool {
25+
self.locked_outpoints.is_empty() && self.expiration_heights.is_empty()
26+
}
27+
}

wallet/src/wallet/mod.rs

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ mod changeset;
5353
pub mod coin_selection;
5454
pub mod error;
5555
pub mod export;
56+
pub mod locked_outpoints;
5657
mod params;
5758
mod persisted;
5859
pub mod signer;
@@ -657,7 +658,23 @@ impl Wallet {
657658
let mut indexed_graph = IndexedTxGraph::new(index);
658659
indexed_graph.apply_changeset(changeset.indexer.into());
659660
indexed_graph.apply_changeset(changeset.tx_graph.into());
660-
let locked_outpoints = changeset.locked_outpoints;
661+
662+
// Apply locked outpoints
663+
let mut locked_outpoints = BTreeMap::new();
664+
let locked_outpoints::ChangeSet {
665+
locked_outpoints: locked_utxos,
666+
expiration_heights,
667+
} = changeset.locked_outpoints;
668+
for (outpoint, is_locked) in locked_utxos {
669+
locked_outpoints.insert(
670+
outpoint,
671+
UtxoLock {
672+
outpoint,
673+
is_locked,
674+
expiration_height: expiration_heights.get(&outpoint).cloned().flatten(),
675+
},
676+
);
677+
}
661678

662679
let stage = ChangeSet::default();
663680

@@ -2421,45 +2438,41 @@ impl Wallet {
24212438
pub fn lock_outpoint(&mut self, outpoint: OutPoint, expiration_height: Option<u32>) {
24222439
use alloc::collections::btree_map;
24232440
let lock_value = true;
2441+
let mut changeset = locked_outpoints::ChangeSet::default();
24242442

2425-
// We only stage a change if the lock status changed. Here that means
2426-
// checking if the outpoint is not currently locked, or if the expiration
2427-
// height changed.
2428-
let is_changed = match self.locked_outpoints.entry(outpoint) {
2443+
// If the lock status changed, update the entry and record the change
2444+
// in the changeset.
2445+
match self.locked_outpoints.entry(outpoint) {
24292446
btree_map::Entry::Occupied(mut e) => {
2430-
let mut is_changed = false;
24312447
let utxo = e.get_mut();
24322448
if !utxo.is_locked {
24332449
utxo.is_locked = lock_value;
2434-
is_changed = true;
2450+
changeset.locked_outpoints.insert(outpoint, lock_value);
24352451
}
24362452
if utxo.expiration_height != expiration_height {
24372453
utxo.expiration_height = expiration_height;
2438-
is_changed = true;
2454+
changeset
2455+
.expiration_heights
2456+
.insert(outpoint, expiration_height);
24392457
}
2440-
is_changed
24412458
}
24422459
btree_map::Entry::Vacant(e) => {
24432460
e.insert(UtxoLock {
24442461
outpoint,
24452462
is_locked: lock_value,
24462463
expiration_height,
24472464
});
2448-
true
2465+
changeset.locked_outpoints.insert(outpoint, lock_value);
2466+
changeset
2467+
.expiration_heights
2468+
.insert(outpoint, expiration_height);
24492469
}
24502470
};
24512471

2452-
if is_changed {
2453-
let utxo_lock = UtxoLock {
2454-
outpoint,
2455-
is_locked: lock_value,
2456-
expiration_height,
2457-
};
2458-
self.stage.merge(ChangeSet {
2459-
locked_outpoints: [(outpoint, utxo_lock)].into(),
2460-
..Default::default()
2461-
});
2462-
}
2472+
self.stage.merge(ChangeSet {
2473+
locked_outpoints: changeset,
2474+
..Default::default()
2475+
});
24632476
}
24642477

24652478
/// Unlock the wallet output of the specified `outpoint`.
@@ -2475,12 +2488,11 @@ impl Wallet {
24752488
let utxo = e.get_mut();
24762489
if utxo.is_locked {
24772490
utxo.is_locked = lock_value;
2478-
let utxo_lock = UtxoLock {
2479-
is_locked: lock_value,
2480-
..*utxo
2481-
};
24822491
self.stage.merge(ChangeSet {
2483-
locked_outpoints: [(outpoint, utxo_lock)].into(),
2492+
locked_outpoints: locked_outpoints::ChangeSet {
2493+
locked_outpoints: [(outpoint, lock_value)].into(),
2494+
..Default::default()
2495+
},
24842496
..Default::default()
24852497
});
24862498
}
@@ -2696,9 +2708,8 @@ impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime>> for Wallet {
26962708
/// Only 1 [`UtxoLock`] may be applied to a particular outpoint at a time. Refer to the docs
26972709
/// for [`Wallet::lock_outpoint`]. Note that the lock status is user-defined and does not take
26982710
/// into account any timelocks directly encoded by the descriptor.
2699-
#[derive(
2700-
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
2701-
)]
2711+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2712+
#[non_exhaustive]
27022713
pub struct UtxoLock {
27032714
/// Outpoint.
27042715
pub outpoint: OutPoint,

wallet/tests/wallet.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,10 @@ fn test_lock_outpoint_persist() -> anyhow::Result<()> {
142142
assert_eq!(
143143
changeset
144144
.locked_outpoints
145+
.expiration_heights
145146
.get(&outpoint)
146-
.unwrap()
147-
.expiration_height,
147+
.cloned()
148+
.unwrap(),
148149
Some(expiry)
149150
);
150151

@@ -154,9 +155,10 @@ fn test_lock_outpoint_persist() -> anyhow::Result<()> {
154155
assert_eq!(
155156
changeset
156157
.locked_outpoints
158+
.expiration_heights
157159
.get(&outpoint)
158-
.unwrap()
159-
.expiration_height,
160+
.cloned()
161+
.unwrap(),
160162
Some(expiry)
161163
);
162164
wallet.persist(&mut conn)?;

0 commit comments

Comments
 (0)