1
+ use alloc:: collections:: BTreeMap ;
2
+
1
3
use bdk_chain:: {
2
4
indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime , Merge ,
3
5
} ;
6
+ use bitcoin:: { OutPoint , Txid } ;
4
7
use miniscript:: { Descriptor , DescriptorPublicKey } ;
5
8
use serde:: { Deserialize , Serialize } ;
6
9
7
10
type IndexedTxGraphChangeSet =
8
11
indexed_tx_graph:: ChangeSet < ConfirmationBlockTime , keychain_txout:: ChangeSet > ;
9
12
13
+ use crate :: UtxoLock ;
14
+
10
15
/// A change set for [`Wallet`]
11
16
///
12
17
/// ## Definition
@@ -114,6 +119,8 @@ pub struct ChangeSet {
114
119
pub tx_graph : tx_graph:: ChangeSet < ConfirmationBlockTime > ,
115
120
/// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
116
121
pub indexer : keychain_txout:: ChangeSet ,
122
+ /// Changes to locked outpoints.
123
+ pub locked_outpoints : BTreeMap < OutPoint , UtxoLock > ,
117
124
}
118
125
119
126
impl Merge for ChangeSet {
@@ -142,6 +149,11 @@ impl Merge for ChangeSet {
142
149
self . network = other. network ;
143
150
}
144
151
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 ) ;
156
+
145
157
Merge :: merge ( & mut self . local_chain , other. local_chain ) ;
146
158
Merge :: merge ( & mut self . tx_graph , other. tx_graph ) ;
147
159
Merge :: merge ( & mut self . indexer , other. indexer ) ;
@@ -154,6 +166,7 @@ impl Merge for ChangeSet {
154
166
&& self . local_chain . is_empty ( )
155
167
&& self . tx_graph . is_empty ( )
156
168
&& self . indexer . is_empty ( )
169
+ && self . locked_outpoints . is_empty ( )
157
170
}
158
171
}
159
172
@@ -163,6 +176,8 @@ impl ChangeSet {
163
176
pub const WALLET_SCHEMA_NAME : & ' static str = "bdk_wallet" ;
164
177
/// Name of table to store wallet descriptors and network.
165
178
pub const WALLET_TABLE_NAME : & ' static str = "bdk_wallet" ;
179
+ /// Name of table to store wallet locked outpoints.
180
+ pub const WALLET_UTXO_LOCK_TABLE_NAME : & ' static str = "bdk_wallet_locked_outpoints" ;
166
181
167
182
/// Get v0 sqlite [ChangeSet] schema
168
183
pub fn schema_v0 ( ) -> alloc:: string:: String {
@@ -177,12 +192,26 @@ impl ChangeSet {
177
192
)
178
193
}
179
194
195
+ /// Get v1 sqlite [`ChangeSet`] schema. Schema v1 adds a table for locked outpoints.
196
+ pub fn schema_v1 ( ) -> alloc:: string:: String {
197
+ format ! (
198
+ "CREATE TABLE {} ( \
199
+ txid TEXT NOT NULL, \
200
+ vout INTEGER NOT NULL, \
201
+ is_locked INTEGER, \
202
+ expiration_height INTEGER, \
203
+ PRIMARY KEY(txid, vout) \
204
+ ) STRICT;",
205
+ Self :: WALLET_UTXO_LOCK_TABLE_NAME ,
206
+ )
207
+ }
208
+
180
209
/// Initialize sqlite tables for wallet tables.
181
210
pub fn init_sqlite_tables ( db_tx : & chain:: rusqlite:: Transaction ) -> chain:: rusqlite:: Result < ( ) > {
182
211
crate :: rusqlite_impl:: migrate_schema (
183
212
db_tx,
184
213
Self :: WALLET_SCHEMA_NAME ,
185
- & [ & Self :: schema_v0 ( ) ] ,
214
+ & [ & Self :: schema_v0 ( ) , & Self :: schema_v1 ( ) ] ,
186
215
) ?;
187
216
188
217
bdk_chain:: local_chain:: ChangeSet :: init_sqlite_tables ( db_tx) ?;
@@ -220,6 +249,31 @@ impl ChangeSet {
220
249
changeset. network = network. map ( Impl :: into_inner) ;
221
250
}
222
251
252
+ // Select locked outpoints.
253
+ let mut stmt = db_tx. prepare ( & format ! (
254
+ "SELECT txid, vout, is_locked, expiration_height FROM {}" ,
255
+ Self :: WALLET_UTXO_LOCK_TABLE_NAME ,
256
+ ) ) ?;
257
+ let rows = stmt. query_map ( [ ] , |row| {
258
+ Ok ( (
259
+ row. get :: < _ , Impl < Txid > > ( "txid" ) ?,
260
+ row. get :: < _ , u32 > ( "vout" ) ?,
261
+ row. get :: < _ , bool > ( "is_locked" ) ?,
262
+ row. get :: < _ , Option < u32 > > ( "expiration_height" ) ?,
263
+ ) )
264
+ } ) ?;
265
+ for row in rows {
266
+ 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
273
+ . locked_outpoints
274
+ . insert ( utxo_lock. outpoint , utxo_lock) ;
275
+ }
276
+
223
277
changeset. local_chain = local_chain:: ChangeSet :: from_sqlite ( db_tx) ?;
224
278
changeset. tx_graph = tx_graph:: ChangeSet :: < _ > :: from_sqlite ( db_tx) ?;
225
279
changeset. indexer = keychain_txout:: ChangeSet :: from_sqlite ( db_tx) ?;
@@ -268,6 +322,21 @@ impl ChangeSet {
268
322
} ) ?;
269
323
}
270
324
325
+ // Insert locked outpoints.
326
+ 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" ,
328
+ Self :: WALLET_UTXO_LOCK_TABLE_NAME ,
329
+ ) ) ?;
330
+ for ( & outpoint, utxo_lock) in & self . locked_outpoints {
331
+ let OutPoint { txid, vout } = outpoint;
332
+ stmt. execute ( named_params ! {
333
+ ":txid" : Impl ( txid) ,
334
+ ":vout" : vout,
335
+ ":is_locked" : utxo_lock. is_locked,
336
+ ":expiration_height" : utxo_lock. expiration_height,
337
+ } ) ?;
338
+ }
339
+
271
340
self . local_chain . persist_to_sqlite ( db_tx) ?;
272
341
self . tx_graph . persist_to_sqlite ( db_tx) ?;
273
342
self . indexer . persist_to_sqlite ( db_tx) ?;
0 commit comments