@@ -4,14 +4,16 @@ use bdk_chain::{
44use miniscript:: { Descriptor , DescriptorPublicKey } ;
55use serde:: { Deserialize , Serialize } ;
66
7+ use crate :: locked_outpoints;
8+
79type IndexedTxGraphChangeSet =
810 indexed_tx_graph:: ChangeSet < ConfirmationBlockTime , keychain_txout:: ChangeSet > ;
911
10- /// A change set for [`Wallet`]
12+ /// A change set for [`Wallet`].
1113///
1214/// ## Definition
1315///
14- /// The change set is responsible for transmiting data between the persistent storage layer and the
16+ /// The change set is responsible for transmitting data between the persistent storage layer and the
1517/// core library components. Specifically, it serves two primary functions:
1618///
1719/// 1) Recording incremental changes to the in-memory representation that need to be persisted to
@@ -114,6 +116,8 @@ pub struct ChangeSet {
114116 pub tx_graph : tx_graph:: ChangeSet < ConfirmationBlockTime > ,
115117 /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
116118 pub indexer : keychain_txout:: ChangeSet ,
119+ /// Changes to locked outpoints.
120+ pub locked_outpoints : locked_outpoints:: ChangeSet ,
117121}
118122
119123impl Merge for ChangeSet {
@@ -142,6 +146,9 @@ impl Merge for ChangeSet {
142146 self . network = other. network ;
143147 }
144148
149+ // merge locked outpoints
150+ self . locked_outpoints . merge ( other. locked_outpoints ) ;
151+
145152 Merge :: merge ( & mut self . local_chain , other. local_chain ) ;
146153 Merge :: merge ( & mut self . tx_graph , other. tx_graph ) ;
147154 Merge :: merge ( & mut self . indexer , other. indexer ) ;
@@ -154,6 +161,7 @@ impl Merge for ChangeSet {
154161 && self . local_chain . is_empty ( )
155162 && self . tx_graph . is_empty ( )
156163 && self . indexer . is_empty ( )
164+ && self . locked_outpoints . is_empty ( )
157165 }
158166}
159167
@@ -163,6 +171,8 @@ impl ChangeSet {
163171 pub const WALLET_SCHEMA_NAME : & ' static str = "bdk_wallet" ;
164172 /// Name of table to store wallet descriptors and network.
165173 pub const WALLET_TABLE_NAME : & ' static str = "bdk_wallet" ;
174+ /// Name of table to store wallet locked outpoints.
175+ pub const WALLET_OUTPOINT_LOCK_TABLE_NAME : & ' static str = "bdk_wallet_locked_outpoints" ;
166176
167177 /// Get v0 sqlite [ChangeSet] schema
168178 pub fn schema_v0 ( ) -> alloc:: string:: String {
@@ -177,12 +187,24 @@ impl ChangeSet {
177187 )
178188 }
179189
190+ /// Get v1 sqlite [`ChangeSet`] schema. Schema v1 adds a table for locked outpoints.
191+ pub fn schema_v1 ( ) -> alloc:: string:: String {
192+ format ! (
193+ "CREATE TABLE {} ( \
194+ txid TEXT NOT NULL, \
195+ vout INTEGER NOT NULL, \
196+ PRIMARY KEY(txid, vout) \
197+ ) STRICT;",
198+ Self :: WALLET_OUTPOINT_LOCK_TABLE_NAME ,
199+ )
200+ }
201+
180202 /// Initialize sqlite tables for wallet tables.
181203 pub fn init_sqlite_tables ( db_tx : & chain:: rusqlite:: Transaction ) -> chain:: rusqlite:: Result < ( ) > {
182204 crate :: rusqlite_impl:: migrate_schema (
183205 db_tx,
184206 Self :: WALLET_SCHEMA_NAME ,
185- & [ & Self :: schema_v0 ( ) ] ,
207+ & [ & Self :: schema_v0 ( ) , & Self :: schema_v1 ( ) ] ,
186208 ) ?;
187209
188210 bdk_chain:: local_chain:: ChangeSet :: init_sqlite_tables ( db_tx) ?;
@@ -194,6 +216,7 @@ impl ChangeSet {
194216
195217 /// Recover a [`ChangeSet`] from sqlite database.
196218 pub fn from_sqlite ( db_tx : & chain:: rusqlite:: Transaction ) -> chain:: rusqlite:: Result < Self > {
219+ use bitcoin:: { OutPoint , Txid } ;
197220 use chain:: rusqlite:: OptionalExtension ;
198221 use chain:: Impl ;
199222
@@ -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 . 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,30 @@ impl ChangeSet {
268309 } ) ?;
269310 }
270311
312+ // Insert or delete locked outpoints.
313+ let mut insert_stmt = db_tx. prepare_cached ( & format ! (
314+ "INSERT OR IGNORE 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+ for ( & outpoint, & is_locked) in & self . locked_outpoints . outpoints {
322+ let bitcoin:: OutPoint { txid, vout } = outpoint;
323+ if is_locked {
324+ insert_stmt. execute ( named_params ! {
325+ ":txid" : Impl ( txid) ,
326+ ":vout" : vout,
327+ } ) ?;
328+ } else {
329+ delete_stmt. execute ( named_params ! {
330+ ":txid" : Impl ( txid) ,
331+ ":vout" : vout,
332+ } ) ?;
333+ }
334+ }
335+
271336 self . local_chain . persist_to_sqlite ( db_tx) ?;
272337 self . tx_graph . persist_to_sqlite ( db_tx) ?;
273338 self . indexer . persist_to_sqlite ( db_tx) ?;
@@ -311,3 +376,12 @@ impl From<keychain_txout::ChangeSet> for ChangeSet {
311376 }
312377 }
313378}
379+
380+ impl From < locked_outpoints:: ChangeSet > for ChangeSet {
381+ fn from ( locked_outpoints : locked_outpoints:: ChangeSet ) -> Self {
382+ Self {
383+ locked_outpoints,
384+ ..Default :: default ( )
385+ }
386+ }
387+ }
0 commit comments