1
1
use bdk_chain:: {
2
2
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime , Merge ,
3
3
} ;
4
+ use bitcoin:: { OutPoint , Txid } ;
4
5
use miniscript:: { Descriptor , DescriptorPublicKey } ;
5
6
use serde:: { Deserialize , Serialize } ;
6
7
7
8
type IndexedTxGraphChangeSet =
8
9
indexed_tx_graph:: ChangeSet < ConfirmationBlockTime , keychain_txout:: ChangeSet > ;
9
10
10
- /// A change set for [`Wallet`]
11
+ use crate :: locked_outpoints;
12
+
13
+ /// A change set for [`Wallet`].
11
14
///
12
15
/// ## Definition
13
16
///
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
15
18
/// core library components. Specifically, it serves two primary functions:
16
19
///
17
20
/// 1) Recording incremental changes to the in-memory representation that need to be persisted to
@@ -114,6 +117,8 @@ pub struct ChangeSet {
114
117
pub tx_graph : tx_graph:: ChangeSet < ConfirmationBlockTime > ,
115
118
/// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
116
119
pub indexer : keychain_txout:: ChangeSet ,
120
+ /// Changes to locked outpoints.
121
+ pub locked_outpoints : locked_outpoints:: ChangeSet ,
117
122
}
118
123
119
124
impl Merge for ChangeSet {
@@ -142,6 +147,9 @@ impl Merge for ChangeSet {
142
147
self . network = other. network ;
143
148
}
144
149
150
+ // merge locked outpoints
151
+ self . locked_outpoints . merge ( other. locked_outpoints ) ;
152
+
145
153
Merge :: merge ( & mut self . local_chain , other. local_chain ) ;
146
154
Merge :: merge ( & mut self . tx_graph , other. tx_graph ) ;
147
155
Merge :: merge ( & mut self . indexer , other. indexer ) ;
@@ -154,6 +162,7 @@ impl Merge for ChangeSet {
154
162
&& self . local_chain . is_empty ( )
155
163
&& self . tx_graph . is_empty ( )
156
164
&& self . indexer . is_empty ( )
165
+ && self . locked_outpoints . is_empty ( )
157
166
}
158
167
}
159
168
@@ -163,6 +172,8 @@ impl ChangeSet {
163
172
pub const WALLET_SCHEMA_NAME : & ' static str = "bdk_wallet" ;
164
173
/// Name of table to store wallet descriptors and network.
165
174
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" ;
166
177
167
178
/// Get v0 sqlite [ChangeSet] schema
168
179
pub fn schema_v0 ( ) -> alloc:: string:: String {
@@ -177,12 +188,24 @@ impl ChangeSet {
177
188
)
178
189
}
179
190
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
+
180
203
/// Initialize sqlite tables for wallet tables.
181
204
pub fn init_sqlite_tables ( db_tx : & chain:: rusqlite:: Transaction ) -> chain:: rusqlite:: Result < ( ) > {
182
205
crate :: rusqlite_impl:: migrate_schema (
183
206
db_tx,
184
207
Self :: WALLET_SCHEMA_NAME ,
185
- & [ & Self :: schema_v0 ( ) ] ,
208
+ & [ & Self :: schema_v0 ( ) , & Self :: schema_v1 ( ) ] ,
186
209
) ?;
187
210
188
211
bdk_chain:: local_chain:: ChangeSet :: init_sqlite_tables ( db_tx) ?;
@@ -220,6 +243,24 @@ impl ChangeSet {
220
243
changeset. network = network. map ( Impl :: into_inner) ;
221
244
}
222
245
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
+
223
264
changeset. local_chain = local_chain:: ChangeSet :: from_sqlite ( db_tx) ?;
224
265
changeset. tx_graph = tx_graph:: ChangeSet :: < _ > :: from_sqlite ( db_tx) ?;
225
266
changeset. indexer = keychain_txout:: ChangeSet :: from_sqlite ( db_tx) ?;
@@ -268,6 +309,31 @@ impl ChangeSet {
268
309
} ) ?;
269
310
}
270
311
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
+
271
337
self . local_chain . persist_to_sqlite ( db_tx) ?;
272
338
self . tx_graph . persist_to_sqlite ( db_tx) ?;
273
339
self . indexer . persist_to_sqlite ( db_tx) ?;
@@ -311,3 +377,12 @@ impl From<keychain_txout::ChangeSet> for ChangeSet {
311
377
}
312
378
}
313
379
}
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