11use bdk_chain:: {
22 indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime , Merge ,
33} ;
4+ use bitcoin:: { OutPoint , Txid } ;
45use miniscript:: { Descriptor , DescriptorPublicKey } ;
56use serde:: { Deserialize , Serialize } ;
67
78type IndexedTxGraphChangeSet =
89 indexed_tx_graph:: ChangeSet < ConfirmationBlockTime , keychain_txout:: ChangeSet > ;
910
10- /// A change set for [`Wallet`]
11+ use crate :: locked_outpoints;
12+
13+ /// A change set for [`Wallet`].
1114///
1215/// ## Definition
1316///
14- /// The change set is responsible for transmiting data between the persistent storage layer and the
17+ /// The change set is responsible for transmitting data between the persistent storage layer and the
1518/// core library components. Specifically, it serves two primary functions:
1619///
1720/// 1) Recording incremental changes to the in-memory representation that need to be persisted to
@@ -114,6 +117,8 @@ pub struct ChangeSet {
114117 pub tx_graph : tx_graph:: ChangeSet < ConfirmationBlockTime > ,
115118 /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
116119 pub indexer : keychain_txout:: ChangeSet ,
120+ /// Changes to locked outpoints.
121+ pub locked_outpoints : locked_outpoints:: ChangeSet ,
117122}
118123
119124impl Merge for ChangeSet {
@@ -142,6 +147,9 @@ impl Merge for ChangeSet {
142147 self . network = other. network ;
143148 }
144149
150+ // merge locked outpoints
151+ self . locked_outpoints . merge ( other. locked_outpoints ) ;
152+
145153 Merge :: merge ( & mut self . local_chain , other. local_chain ) ;
146154 Merge :: merge ( & mut self . tx_graph , other. tx_graph ) ;
147155 Merge :: merge ( & mut self . indexer , other. indexer ) ;
@@ -154,6 +162,7 @@ impl Merge for ChangeSet {
154162 && self . local_chain . is_empty ( )
155163 && self . tx_graph . is_empty ( )
156164 && self . indexer . is_empty ( )
165+ && self . locked_outpoints . is_empty ( )
157166 }
158167}
159168
@@ -163,6 +172,8 @@ impl ChangeSet {
163172 pub const WALLET_SCHEMA_NAME : & ' static str = "bdk_wallet" ;
164173 /// Name of table to store wallet descriptors and network.
165174 pub const WALLET_TABLE_NAME : & ' static str = "bdk_wallet" ;
175+ /// Name of table to store wallet locked outpoints.
176+ pub const WALLET_OUTPOINT_LOCK_TABLE_NAME : & ' static str = "bdk_wallet_locked_outpoints" ;
166177
167178 /// Get v0 sqlite [ChangeSet] schema
168179 pub fn schema_v0 ( ) -> alloc:: string:: String {
@@ -177,12 +188,24 @@ impl ChangeSet {
177188 )
178189 }
179190
191+ /// Get v1 sqlite [`ChangeSet`] schema. Schema v1 adds a table for locked outpoints.
192+ pub fn schema_v1 ( ) -> alloc:: string:: String {
193+ format ! (
194+ "CREATE TABLE {} ( \
195+ txid TEXT NOT NULL, \
196+ vout INTEGER NOT NULL, \
197+ PRIMARY KEY(txid, vout) \
198+ ) STRICT;",
199+ Self :: WALLET_OUTPOINT_LOCK_TABLE_NAME ,
200+ )
201+ }
202+
180203 /// Initialize sqlite tables for wallet tables.
181204 pub fn init_sqlite_tables ( db_tx : & chain:: rusqlite:: Transaction ) -> chain:: rusqlite:: Result < ( ) > {
182205 crate :: rusqlite_impl:: migrate_schema (
183206 db_tx,
184207 Self :: WALLET_SCHEMA_NAME ,
185- & [ & Self :: schema_v0 ( ) ] ,
208+ & [ & Self :: schema_v0 ( ) , & Self :: schema_v1 ( ) ] ,
186209 ) ?;
187210
188211 bdk_chain:: local_chain:: ChangeSet :: init_sqlite_tables ( db_tx) ?;
@@ -220,6 +243,24 @@ impl ChangeSet {
220243 changeset. network = network. map ( Impl :: into_inner) ;
221244 }
222245
246+ // Select locked outpoints.
247+ let mut stmt = db_tx. prepare ( & format ! (
248+ "SELECT txid, vout FROM {}" ,
249+ Self :: WALLET_OUTPOINT_LOCK_TABLE_NAME ,
250+ ) ) ?;
251+ let rows = stmt. query_map ( [ ] , |row| {
252+ Ok ( (
253+ row. get :: < _ , Impl < Txid > > ( "txid" ) ?,
254+ row. get :: < _ , u32 > ( "vout" ) ?,
255+ ) )
256+ } ) ?;
257+ let locked_outpoints = & mut changeset. locked_outpoints . locked_outpoints ;
258+ for row in rows {
259+ let ( Impl ( txid) , vout) = row?;
260+ let outpoint = OutPoint :: new ( txid, vout) ;
261+ locked_outpoints. insert ( outpoint, true ) ;
262+ }
263+
223264 changeset. local_chain = local_chain:: ChangeSet :: from_sqlite ( db_tx) ?;
224265 changeset. tx_graph = tx_graph:: ChangeSet :: < _ > :: from_sqlite ( db_tx) ?;
225266 changeset. indexer = keychain_txout:: ChangeSet :: from_sqlite ( db_tx) ?;
@@ -268,6 +309,31 @@ impl ChangeSet {
268309 } ) ?;
269310 }
270311
312+ // Insert or delete locked outpoints.
313+ let mut insert_stmt = db_tx. prepare_cached ( & format ! (
314+ "REPLACE INTO {}(txid, vout) VALUES(:txid, :vout)" ,
315+ Self :: WALLET_OUTPOINT_LOCK_TABLE_NAME
316+ ) ) ?;
317+ let mut delete_stmt = db_tx. prepare_cached ( & format ! (
318+ "DELETE FROM {} WHERE txid=:txid AND vout=:vout" ,
319+ Self :: WALLET_OUTPOINT_LOCK_TABLE_NAME ,
320+ ) ) ?;
321+ let locked_outpoints = & self . locked_outpoints . locked_outpoints ;
322+ for ( & outpoint, & is_locked) in locked_outpoints. iter ( ) {
323+ let OutPoint { txid, vout } = outpoint;
324+ if is_locked {
325+ insert_stmt. execute ( named_params ! {
326+ ":txid" : Impl ( txid) ,
327+ ":vout" : vout,
328+ } ) ?;
329+ } else {
330+ delete_stmt. execute ( named_params ! {
331+ ":txid" : Impl ( txid) ,
332+ ":vout" : vout,
333+ } ) ?;
334+ }
335+ }
336+
271337 self . local_chain . persist_to_sqlite ( db_tx) ?;
272338 self . tx_graph . persist_to_sqlite ( db_tx) ?;
273339 self . indexer . persist_to_sqlite ( db_tx) ?;
@@ -311,3 +377,12 @@ impl From<keychain_txout::ChangeSet> for ChangeSet {
311377 }
312378 }
313379}
380+
381+ impl From < locked_outpoints:: ChangeSet > for ChangeSet {
382+ fn from ( locked_outpoints : locked_outpoints:: ChangeSet ) -> Self {
383+ Self {
384+ locked_outpoints,
385+ ..Default :: default ( )
386+ }
387+ }
388+ }
0 commit comments