• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 5834188079

pending completion
5834188079

Pull #1071

github

web-flow
Merge 68b42331c into 0ba6bbe11
Pull Request #1071: Update rust bitcoin (BDK 0.28)

563 of 563 new or added lines in 28 files covered. (100.0%)

14625 of 18342 relevant lines covered (79.74%)

9267.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.17
/src/database/sqlite.rs
1
// Bitcoin Dev Kit
2
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
3
//
4
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
5
//
6
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
7
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
8
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
9
// You may not use this file except in accordance with one or both of these
10
// licenses.
11
use std::path::Path;
12
use std::path::PathBuf;
13

14
use bitcoin::consensus::encode::{deserialize, serialize};
15
use bitcoin::hash_types::Txid;
16
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, TxOut};
17

18
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
19
use crate::error::Error;
20
use crate::types::*;
21

22
use rusqlite::{named_params, Connection};
23

24
static MIGRATIONS: &[&str] = &[
25
    "CREATE TABLE version (version INTEGER)",
26
    "INSERT INTO version VALUES (1)",
27
    "CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
28
    "CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
29
    "CREATE INDEX idx_script ON script_pubkeys(script);",
30
    "CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB);",
31
    "CREATE INDEX idx_txid_vout ON utxos(txid, vout);",
32
    "CREATE TABLE transactions (txid BLOB, raw_tx BLOB);",
33
    "CREATE INDEX idx_txid ON transactions(txid);",
34
    "CREATE TABLE transaction_details (txid BLOB, timestamp INTEGER, received INTEGER, sent INTEGER, fee INTEGER, height INTEGER, verified INTEGER DEFAULT 0);",
35
    "CREATE INDEX idx_txdetails_txid ON transaction_details(txid);",
36
    "CREATE TABLE last_derivation_indices (keychain TEXT, value INTEGER);",
37
    "CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);",
38
    "CREATE TABLE checksums (keychain TEXT, checksum BLOB);",
39
    "CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
40
    "CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);",
41
    "ALTER TABLE transaction_details RENAME TO transaction_details_old;",
42
    "CREATE TABLE transaction_details (txid BLOB, timestamp INTEGER, received INTEGER, sent INTEGER, fee INTEGER, height INTEGER);",
43
    "INSERT INTO transaction_details SELECT txid, timestamp, received, sent, fee, height FROM transaction_details_old;",
44
    "DROP TABLE transaction_details_old;",
45
    "ALTER TABLE utxos ADD COLUMN is_spent;",
46
    // drop all data due to possible inconsistencies with duplicate utxos, re-sync required
47
    "DELETE FROM checksums;",
48
    "DELETE FROM last_derivation_indices;",
49
    "DELETE FROM script_pubkeys;",
50
    "DELETE FROM sync_time;",
51
    "DELETE FROM transaction_details;",
52
    "DELETE FROM transactions;",
53
    "DELETE FROM utxos;",
54
    "DROP INDEX idx_txid_vout;",
55
    "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
56
    "ALTER TABLE utxos RENAME TO utxos_old;",
57
    "CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
58
    "INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
59
    "DROP TABLE utxos_old;",
60
    "CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
61
    // Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
62
    "ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
63
    "DROP INDEX idx_keychain_child;",
64
    "DROP INDEX idx_script;",
65
    "CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
66
    "CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
67
    "CREATE INDEX idx_script ON script_pubkeys(script);",
68
    "CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
69
    "INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
70
    "DROP TABLE script_pubkeys_old;"
71
];
72

73
/// Sqlite database stored on filesystem
74
///
75
/// This is a permanent storage solution for devices and platforms that provide a filesystem.
76
/// [`crate::database`]
77
#[derive(Debug)]
×
78
pub struct SqliteDatabase {
79
    /// Path on the local filesystem to store the sqlite file
80
    pub path: PathBuf,
81
    /// A rusqlite connection object to the sqlite database
82
    pub connection: Connection,
83
}
84

85
impl SqliteDatabase {
86
    /// Instantiate a new SqliteDatabase instance by creating a connection
87
    /// to the database stored at path
88
    pub fn new<T: AsRef<Path>>(path: T) -> Self {
40✔
89
        let connection = get_connection(&path).unwrap();
40✔
90
        SqliteDatabase {
40✔
91
            path: PathBuf::from(path.as_ref()),
40✔
92
            connection,
40✔
93
        }
40✔
94
    }
40✔
95
    fn insert_script_pubkey(
214✔
96
        &self,
214✔
97
        keychain: String,
214✔
98
        child: u32,
214✔
99
        script: &[u8],
214✔
100
    ) -> Result<i64, Error> {
214✔
101
        let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
214✔
102
        statement.execute(named_params! {
214✔
103
            ":keychain": keychain,
214✔
104
            ":child": child,
214✔
105
            ":script": script
214✔
106
        })?;
214✔
107

108
        Ok(self.connection.last_insert_rowid())
214✔
109
    }
214✔
110
    fn insert_utxo(
6✔
111
        &self,
6✔
112
        value: u64,
6✔
113
        keychain: String,
6✔
114
        vout: u32,
6✔
115
        txid: &[u8],
6✔
116
        script: &[u8],
6✔
117
        is_spent: bool,
6✔
118
    ) -> Result<i64, Error> {
6✔
119
        let mut statement = self.connection.prepare_cached("INSERT INTO utxos (value, keychain, vout, txid, script, is_spent) VALUES (:value, :keychain, :vout, :txid, :script, :is_spent) ON CONFLICT(txid, vout) DO UPDATE SET value=:value, keychain=:keychain, script=:script, is_spent=:is_spent")?;
6✔
120
        statement.execute(named_params! {
6✔
121
            ":value": value,
6✔
122
            ":keychain": keychain,
6✔
123
            ":vout": vout,
6✔
124
            ":txid": txid,
6✔
125
            ":script": script,
6✔
126
            ":is_spent": is_spent,
6✔
127
        })?;
6✔
128

129
        Ok(self.connection.last_insert_rowid())
6✔
130
    }
6✔
131
    fn insert_transaction(&self, txid: &[u8], raw_tx: &[u8]) -> Result<i64, Error> {
14✔
132
        let mut statement = self
14✔
133
            .connection
14✔
134
            .prepare_cached("INSERT INTO transactions (txid, raw_tx) VALUES (:txid, :raw_tx)")?;
14✔
135
        statement.execute(named_params! {
14✔
136
            ":txid": txid,
14✔
137
            ":raw_tx": raw_tx,
14✔
138
        })?;
14✔
139

140
        Ok(self.connection.last_insert_rowid())
14✔
141
    }
14✔
142

143
    fn update_transaction(&self, txid: &[u8], raw_tx: &[u8]) -> Result<(), Error> {
2✔
144
        let mut statement = self
2✔
145
            .connection
2✔
146
            .prepare_cached("UPDATE transactions SET raw_tx=:raw_tx WHERE txid=:txid")?;
2✔
147

148
        statement.execute(named_params! {
2✔
149
            ":txid": txid,
2✔
150
            ":raw_tx": raw_tx,
2✔
151
        })?;
2✔
152

153
        Ok(())
2✔
154
    }
2✔
155

156
    fn insert_transaction_details(&self, transaction: &TransactionDetails) -> Result<i64, Error> {
8✔
157
        let (timestamp, height) = match &transaction.confirmation_time {
8✔
158
            Some(confirmation_time) => (
8✔
159
                Some(confirmation_time.timestamp),
8✔
160
                Some(confirmation_time.height),
8✔
161
            ),
8✔
162
            None => (None, None),
×
163
        };
164

165
        let txid: &[u8] = &transaction.txid.as_ref();
8✔
166

167
        let mut statement = self.connection.prepare_cached("INSERT INTO transaction_details (txid, timestamp, received, sent, fee, height) VALUES (:txid, :timestamp, :received, :sent, :fee, :height)")?;
8✔
168

169
        statement.execute(named_params! {
8✔
170
            ":txid": txid,
8✔
171
            ":timestamp": timestamp,
8✔
172
            ":received": transaction.received,
8✔
173
            ":sent": transaction.sent,
8✔
174
            ":fee": transaction.fee,
8✔
175
            ":height": height,
8✔
176
        })?;
8✔
177

178
        Ok(self.connection.last_insert_rowid())
8✔
179
    }
8✔
180

181
    fn update_transaction_details(&self, transaction: &TransactionDetails) -> Result<(), Error> {
×
182
        let (timestamp, height) = match &transaction.confirmation_time {
×
183
            Some(confirmation_time) => (
×
184
                Some(confirmation_time.timestamp),
×
185
                Some(confirmation_time.height),
×
186
            ),
×
187
            None => (None, None),
×
188
        };
189

190
        let txid: &[u8] = &transaction.txid.as_ref();
×
191

192
        let mut statement = self.connection.prepare_cached("UPDATE transaction_details SET timestamp=:timestamp, received=:received, sent=:sent, fee=:fee, height=:height WHERE txid=:txid")?;
×
193

194
        statement.execute(named_params! {
×
195
            ":txid": txid,
×
196
            ":timestamp": timestamp,
×
197
            ":received": transaction.received,
×
198
            ":sent": transaction.sent,
×
199
            ":fee": transaction.fee,
×
200
            ":height": height,
×
201
        })?;
×
202

203
        Ok(())
×
204
    }
×
205

206
    fn insert_last_derivation_index(&self, keychain: String, value: u32) -> Result<i64, Error> {
4✔
207
        let mut statement = self.connection.prepare_cached(
4✔
208
            "INSERT INTO last_derivation_indices (keychain, value) VALUES (:keychain, :value)",
4✔
209
        )?;
4✔
210

211
        statement.execute(named_params! {
4✔
212
            ":keychain": keychain,
4✔
213
            ":value": value,
4✔
214
        })?;
4✔
215

216
        Ok(self.connection.last_insert_rowid())
4✔
217
    }
4✔
218

219
    fn insert_checksum(&self, keychain: String, checksum: &[u8]) -> Result<i64, Error> {
2✔
220
        let mut statement = self.connection.prepare_cached(
2✔
221
            "INSERT INTO checksums (keychain, checksum) VALUES (:keychain, :checksum)",
2✔
222
        )?;
2✔
223
        statement.execute(named_params! {
2✔
224
            ":keychain": keychain,
2✔
225
            ":checksum": checksum,
2✔
226
        })?;
2✔
227

228
        Ok(self.connection.last_insert_rowid())
2✔
229
    }
2✔
230

231
    fn update_last_derivation_index(&self, keychain: String, value: u32) -> Result<(), Error> {
6✔
232
        let mut statement = self.connection.prepare_cached(
6✔
233
            "INSERT INTO last_derivation_indices (keychain, value) VALUES (:keychain, :value) ON CONFLICT(keychain) DO UPDATE SET value=:value WHERE keychain=:keychain",
6✔
234
        )?;
6✔
235

236
        statement.execute(named_params! {
6✔
237
            ":keychain": keychain,
6✔
238
            ":value": value,
6✔
239
        })?;
6✔
240

241
        Ok(())
6✔
242
    }
6✔
243

244
    fn update_sync_time(&self, data: SyncTime) -> Result<i64, Error> {
2✔
245
        let mut statement = self.connection.prepare_cached(
2✔
246
            "INSERT INTO sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0",
2✔
247
        )?;
2✔
248

249
        statement.execute(named_params! {
2✔
250
            ":height": data.block_time.height,
2✔
251
            ":timestamp": data.block_time.timestamp,
2✔
252
        })?;
2✔
253

254
        Ok(self.connection.last_insert_rowid())
2✔
255
    }
2✔
256

257
    fn select_script_pubkeys(&self) -> Result<Vec<ScriptBuf>, Error> {
6✔
258
        let mut statement = self
6✔
259
            .connection
6✔
260
            .prepare_cached("SELECT script FROM script_pubkeys")?;
6✔
261
        let mut scripts: Vec<ScriptBuf> = vec![];
6✔
262
        let mut rows = statement.query([])?;
6✔
263
        while let Some(row) = rows.next()? {
10✔
264
            let raw_script: Vec<u8> = row.get(0)?;
4✔
265
            scripts.push(raw_script.into());
4✔
266
        }
267

268
        Ok(scripts)
6✔
269
    }
6✔
270

271
    fn select_script_pubkeys_by_keychain(&self, keychain: String) -> Result<Vec<ScriptBuf>, Error> {
4✔
272
        let mut statement = self
4✔
273
            .connection
4✔
274
            .prepare_cached("SELECT script FROM script_pubkeys WHERE keychain=:keychain")?;
4✔
275
        let mut scripts: Vec<ScriptBuf> = vec![];
4✔
276
        let mut rows = statement.query(named_params! {":keychain": keychain})?;
4✔
277
        while let Some(row) = rows.next()? {
8✔
278
            let raw_script: Vec<u8> = row.get(0)?;
4✔
279
            scripts.push(raw_script.into());
4✔
280
        }
281

282
        Ok(scripts)
4✔
283
    }
4✔
284

285
    fn select_script_pubkey_by_path(
8✔
286
        &self,
8✔
287
        keychain: String,
8✔
288
        child: u32,
8✔
289
    ) -> Result<Option<ScriptBuf>, Error> {
8✔
290
        let mut statement = self.connection.prepare_cached(
8✔
291
            "SELECT script FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
8✔
292
        )?;
8✔
293
        let mut rows = statement.query(named_params! {":keychain": keychain,":child": child})?;
8✔
294

295
        match rows.next()? {
8✔
296
            Some(row) => {
6✔
297
                let script: Vec<u8> = row.get(0)?;
6✔
298
                let script: ScriptBuf = script.into();
6✔
299
                Ok(Some(script))
6✔
300
            }
301
            None => Ok(None),
2✔
302
        }
303
    }
8✔
304

305
    fn select_script_pubkey_by_script(
12✔
306
        &self,
12✔
307
        script: &[u8],
12✔
308
    ) -> Result<Option<(KeychainKind, u32)>, Error> {
12✔
309
        let mut statement = self
12✔
310
            .connection
12✔
311
            .prepare_cached("SELECT keychain, child FROM script_pubkeys WHERE script=:script")?;
12✔
312
        let mut rows = statement.query(named_params! {":script": script})?;
12✔
313
        match rows.next()? {
12✔
314
            Some(row) => {
6✔
315
                let keychain: String = row.get(0)?;
6✔
316
                let keychain: KeychainKind = serde_json::from_str(&keychain)?;
6✔
317
                let child: u32 = row.get(1)?;
6✔
318
                Ok(Some((keychain, child)))
6✔
319
            }
320
            None => Ok(None),
6✔
321
        }
322
    }
12✔
323

324
    fn select_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
2✔
325
        let mut statement = self
2✔
326
            .connection
2✔
327
            .prepare_cached("SELECT value, keychain, vout, txid, script, is_spent FROM utxos")?;
2✔
328
        let mut utxos: Vec<LocalUtxo> = vec![];
2✔
329
        let mut rows = statement.query([])?;
2✔
330
        while let Some(row) = rows.next()? {
4✔
331
            let value = row.get(0)?;
2✔
332
            let keychain: String = row.get(1)?;
2✔
333
            let vout = row.get(2)?;
2✔
334
            let txid: Vec<u8> = row.get(3)?;
2✔
335
            let script: Vec<u8> = row.get(4)?;
2✔
336
            let is_spent: bool = row.get(5)?;
2✔
337

338
            let keychain: KeychainKind = serde_json::from_str(&keychain)?;
2✔
339

340
            utxos.push(LocalUtxo {
2✔
341
                outpoint: OutPoint::new(deserialize(&txid)?, vout),
2✔
342
                txout: TxOut {
2✔
343
                    value,
2✔
344
                    script_pubkey: script.into(),
2✔
345
                },
2✔
346
                keychain,
2✔
347
                is_spent,
2✔
348
            })
349
        }
350

351
        Ok(utxos)
2✔
352
    }
2✔
353

354
    fn select_utxo_by_outpoint(&self, txid: &[u8], vout: u32) -> Result<Option<LocalUtxo>, Error> {
8✔
355
        let mut statement = self.connection.prepare_cached(
8✔
356
            "SELECT value, keychain, script, is_spent FROM utxos WHERE txid=:txid AND vout=:vout",
8✔
357
        )?;
8✔
358
        let mut rows = statement.query(named_params! {":txid": txid,":vout": vout})?;
8✔
359
        match rows.next()? {
8✔
360
            Some(row) => {
4✔
361
                let value: u64 = row.get(0)?;
4✔
362
                let keychain: String = row.get(1)?;
4✔
363
                let keychain: KeychainKind = serde_json::from_str(&keychain)?;
4✔
364
                let script: Vec<u8> = row.get(2)?;
4✔
365
                let script_pubkey: ScriptBuf = script.into();
4✔
366
                let is_spent: bool = row.get(3)?;
4✔
367

368
                Ok(Some(LocalUtxo {
369
                    outpoint: OutPoint::new(deserialize(txid)?, vout),
4✔
370
                    txout: TxOut {
4✔
371
                        value,
4✔
372
                        script_pubkey,
4✔
373
                    },
4✔
374
                    keychain,
4✔
375
                    is_spent,
4✔
376
                }))
377
            }
378
            None => Ok(None),
4✔
379
        }
380
    }
8✔
381

382
    fn select_transactions(&self) -> Result<Vec<Transaction>, Error> {
4✔
383
        let mut statement = self
4✔
384
            .connection
4✔
385
            .prepare_cached("SELECT raw_tx FROM transactions")?;
4✔
386
        let mut txs: Vec<Transaction> = vec![];
4✔
387
        let mut rows = statement.query([])?;
4✔
388
        while let Some(row) = rows.next()? {
8✔
389
            let raw_tx: Vec<u8> = row.get(0)?;
4✔
390
            let tx: Transaction = deserialize(&raw_tx)?;
4✔
391
            txs.push(tx);
4✔
392
        }
393
        Ok(txs)
4✔
394
    }
4✔
395

396
    fn select_transaction_by_txid(&self, txid: &[u8]) -> Result<Option<Transaction>, Error> {
32✔
397
        let mut statement = self
32✔
398
            .connection
32✔
399
            .prepare_cached("SELECT raw_tx FROM transactions WHERE txid=:txid")?;
32✔
400
        let mut rows = statement.query(named_params! {":txid": txid})?;
32✔
401
        match rows.next()? {
32✔
402
            Some(row) => {
12✔
403
                let raw_tx: Vec<u8> = row.get(0)?;
12✔
404
                let tx: Transaction = deserialize(&raw_tx)?;
12✔
405
                Ok(Some(tx))
12✔
406
            }
407
            None => Ok(None),
20✔
408
        }
409
    }
32✔
410

411
    fn select_transaction_details_with_raw(&self) -> Result<Vec<TransactionDetails>, Error> {
2✔
412
        let mut statement = self.connection.prepare_cached("SELECT transaction_details.txid, transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid = transactions.txid")?;
2✔
413
        let mut transaction_details: Vec<TransactionDetails> = vec![];
2✔
414
        let mut rows = statement.query([])?;
2✔
415
        while let Some(row) = rows.next()? {
4✔
416
            let txid: Vec<u8> = row.get(0)?;
2✔
417
            let txid: Txid = deserialize(&txid)?;
2✔
418
            let timestamp: Option<u64> = row.get(1)?;
2✔
419
            let received: u64 = row.get(2)?;
2✔
420
            let sent: u64 = row.get(3)?;
2✔
421
            let fee: Option<u64> = row.get(4)?;
2✔
422
            let height: Option<u32> = row.get(5)?;
2✔
423
            let raw_tx: Option<Vec<u8>> = row.get(6)?;
2✔
424
            let tx: Option<Transaction> = match raw_tx {
2✔
425
                Some(raw_tx) => {
2✔
426
                    let tx: Transaction = deserialize(&raw_tx)?;
2✔
427
                    Some(tx)
2✔
428
                }
429
                None => None,
×
430
            };
431

432
            let confirmation_time = match (height, timestamp) {
2✔
433
                (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
2✔
434
                _ => None,
×
435
            };
436

437
            transaction_details.push(TransactionDetails {
2✔
438
                transaction: tx,
2✔
439
                txid,
2✔
440
                received,
2✔
441
                sent,
2✔
442
                fee,
2✔
443
                confirmation_time,
2✔
444
            });
2✔
445
        }
446
        Ok(transaction_details)
2✔
447
    }
2✔
448

449
    fn select_transaction_details(&self) -> Result<Vec<TransactionDetails>, Error> {
2✔
450
        let mut statement = self.connection.prepare_cached(
2✔
451
            "SELECT txid, timestamp, received, sent, fee, height FROM transaction_details",
2✔
452
        )?;
2✔
453
        let mut transaction_details: Vec<TransactionDetails> = vec![];
2✔
454
        let mut rows = statement.query([])?;
2✔
455
        while let Some(row) = rows.next()? {
4✔
456
            let txid: Vec<u8> = row.get(0)?;
2✔
457
            let txid: Txid = deserialize(&txid)?;
2✔
458
            let timestamp: Option<u64> = row.get(1)?;
2✔
459
            let received: u64 = row.get(2)?;
2✔
460
            let sent: u64 = row.get(3)?;
2✔
461
            let fee: Option<u64> = row.get(4)?;
2✔
462
            let height: Option<u32> = row.get(5)?;
2✔
463

464
            let confirmation_time = match (height, timestamp) {
2✔
465
                (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
2✔
466
                _ => None,
×
467
            };
468

469
            transaction_details.push(TransactionDetails {
2✔
470
                transaction: None,
2✔
471
                txid,
2✔
472
                received,
2✔
473
                sent,
2✔
474
                fee,
2✔
475
                confirmation_time,
2✔
476
            });
2✔
477
        }
478
        Ok(transaction_details)
2✔
479
    }
2✔
480

481
    fn select_transaction_details_by_txid(
22✔
482
        &self,
22✔
483
        txid: &[u8],
22✔
484
    ) -> Result<Option<TransactionDetails>, Error> {
22✔
485
        let mut statement = self.connection.prepare_cached("SELECT transaction_details.timestamp, transaction_details.received, transaction_details.sent, transaction_details.fee, transaction_details.height, transactions.raw_tx FROM transaction_details, transactions WHERE transaction_details.txid=transactions.txid AND transaction_details.txid=:txid")?;
22✔
486
        let mut rows = statement.query(named_params! { ":txid": txid })?;
22✔
487

488
        match rows.next()? {
22✔
489
            Some(row) => {
8✔
490
                let timestamp: Option<u64> = row.get(0)?;
8✔
491
                let received: u64 = row.get(1)?;
8✔
492
                let sent: u64 = row.get(2)?;
8✔
493
                let fee: Option<u64> = row.get(3)?;
8✔
494
                let height: Option<u32> = row.get(4)?;
8✔
495

496
                let raw_tx: Option<Vec<u8>> = row.get(5)?;
8✔
497
                let tx: Option<Transaction> = match raw_tx {
8✔
498
                    Some(raw_tx) => {
8✔
499
                        let tx: Transaction = deserialize(&raw_tx)?;
8✔
500
                        Some(tx)
8✔
501
                    }
502
                    None => None,
×
503
                };
504

505
                let confirmation_time = match (height, timestamp) {
8✔
506
                    (Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
8✔
507
                    _ => None,
×
508
                };
509

510
                Ok(Some(TransactionDetails {
511
                    transaction: tx,
8✔
512
                    txid: deserialize(txid)?,
8✔
513
                    received,
8✔
514
                    sent,
8✔
515
                    fee,
8✔
516
                    confirmation_time,
8✔
517
                }))
518
            }
519
            None => Ok(None),
14✔
520
        }
521
    }
22✔
522

523
    fn select_last_derivation_index_by_keychain(
22✔
524
        &self,
22✔
525
        keychain: String,
22✔
526
    ) -> Result<Option<u32>, Error> {
22✔
527
        let mut statement = self
22✔
528
            .connection
22✔
529
            .prepare_cached("SELECT value FROM last_derivation_indices WHERE keychain=:keychain")?;
22✔
530
        let mut rows = statement.query(named_params! {":keychain": keychain})?;
22✔
531
        match rows.next()? {
22✔
532
            Some(row) => {
14✔
533
                let value: u32 = row.get(0)?;
14✔
534
                Ok(Some(value))
14✔
535
            }
536
            None => Ok(None),
8✔
537
        }
538
    }
22✔
539

540
    fn select_sync_time(&self) -> Result<Option<SyncTime>, Error> {
8✔
541
        let mut statement = self
8✔
542
            .connection
8✔
543
            .prepare_cached("SELECT height, timestamp FROM sync_time WHERE id = 0")?;
8✔
544
        let mut rows = statement.query([])?;
8✔
545

546
        if let Some(row) = rows.next()? {
8✔
547
            Ok(Some(SyncTime {
548
                block_time: BlockTime {
549
                    height: row.get(0)?,
4✔
550
                    timestamp: row.get(1)?,
4✔
551
                },
552
            }))
553
        } else {
554
            Ok(None)
4✔
555
        }
556
    }
8✔
557

558
    fn select_checksum_by_keychain(&self, keychain: String) -> Result<Option<Vec<u8>>, Error> {
4✔
559
        let mut statement = self
4✔
560
            .connection
4✔
561
            .prepare_cached("SELECT checksum FROM checksums WHERE keychain=:keychain")?;
4✔
562
        let mut rows = statement.query(named_params! {":keychain": keychain})?;
4✔
563

564
        match rows.next()? {
4✔
565
            Some(row) => {
2✔
566
                let checksum: Vec<u8> = row.get(0)?;
2✔
567
                Ok(Some(checksum))
2✔
568
            }
569
            None => Ok(None),
2✔
570
        }
571
    }
4✔
572

573
    fn delete_script_pubkey_by_path(&self, keychain: String, child: u32) -> Result<(), Error> {
2✔
574
        let mut statement = self.connection.prepare_cached(
2✔
575
            "DELETE FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
2✔
576
        )?;
2✔
577
        statement.execute(named_params! {
2✔
578
            ":keychain": keychain,
2✔
579
            ":child": child
2✔
580
        })?;
2✔
581

582
        Ok(())
2✔
583
    }
2✔
584

585
    fn delete_script_pubkey_by_script(&self, script: &[u8]) -> Result<(), Error> {
2✔
586
        let mut statement = self
2✔
587
            .connection
2✔
588
            .prepare_cached("DELETE FROM script_pubkeys WHERE script=:script")?;
2✔
589
        statement.execute(named_params! {
2✔
590
            ":script": script
2✔
591
        })?;
2✔
592

593
        Ok(())
2✔
594
    }
2✔
595

596
    fn delete_utxo_by_outpoint(&self, txid: &[u8], vout: u32) -> Result<(), Error> {
2✔
597
        let mut statement = self
2✔
598
            .connection
2✔
599
            .prepare_cached("DELETE FROM utxos WHERE txid=:txid AND vout=:vout")?;
2✔
600
        statement.execute(named_params! {
2✔
601
            ":txid": txid,
2✔
602
            ":vout": vout
2✔
603
        })?;
2✔
604

605
        Ok(())
2✔
606
    }
2✔
607

608
    fn delete_transaction_by_txid(&self, txid: &[u8]) -> Result<(), Error> {
4✔
609
        let mut statement = self
4✔
610
            .connection
4✔
611
            .prepare_cached("DELETE FROM transactions WHERE txid=:txid")?;
4✔
612
        statement.execute(named_params! {":txid": txid})?;
4✔
613
        Ok(())
4✔
614
    }
4✔
615

616
    fn delete_transaction_details_by_txid(&self, txid: &[u8]) -> Result<(), Error> {
4✔
617
        let mut statement = self
4✔
618
            .connection
4✔
619
            .prepare_cached("DELETE FROM transaction_details WHERE txid=:txid")?;
4✔
620
        statement.execute(named_params! {":txid": txid})?;
4✔
621
        Ok(())
4✔
622
    }
4✔
623

624
    fn delete_last_derivation_index_by_keychain(&self, keychain: String) -> Result<(), Error> {
2✔
625
        let mut statement = self
2✔
626
            .connection
2✔
627
            .prepare_cached("DELETE FROM last_derivation_indices WHERE keychain=:keychain")?;
2✔
628
        statement.execute(named_params! {
2✔
629
            ":keychain": &keychain
2✔
630
        })?;
2✔
631

632
        Ok(())
2✔
633
    }
2✔
634

635
    fn delete_sync_time(&self) -> Result<(), Error> {
2✔
636
        let mut statement = self
2✔
637
            .connection
2✔
638
            .prepare_cached("DELETE FROM sync_time WHERE id = 0")?;
2✔
639
        statement.execute([])?;
2✔
640
        Ok(())
2✔
641
    }
2✔
642
}
643

644
impl BatchOperations for SqliteDatabase {
645
    fn set_script_pubkey(
214✔
646
        &mut self,
214✔
647
        script: &Script,
214✔
648
        keychain: KeychainKind,
214✔
649
        child: u32,
214✔
650
    ) -> Result<(), Error> {
214✔
651
        let keychain = serde_json::to_string(&keychain)?;
214✔
652
        self.insert_script_pubkey(keychain, child, script.as_bytes())?;
214✔
653
        Ok(())
214✔
654
    }
214✔
655

656
    fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> {
6✔
657
        self.insert_utxo(
6✔
658
            utxo.txout.value,
6✔
659
            serde_json::to_string(&utxo.keychain)?,
6✔
660
            utxo.outpoint.vout,
6✔
661
            &utxo.outpoint.txid.as_ref(),
6✔
662
            utxo.txout.script_pubkey.as_bytes(),
6✔
663
            utxo.is_spent,
6✔
664
        )?;
×
665
        Ok(())
6✔
666
    }
6✔
667

668
    fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
669
        match self.select_transaction_by_txid(&transaction.txid().as_ref())? {
16✔
670
            Some(_) => {
671
                self.update_transaction(&transaction.txid().as_ref(), &serialize(transaction))?;
2✔
672
            }
673
            None => {
674
                self.insert_transaction(&transaction.txid().as_ref(), &serialize(transaction))?;
14✔
675
            }
676
        }
677
        Ok(())
16✔
678
    }
16✔
679

680
    fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
681
        match self.select_transaction_details_by_txid(&transaction.txid.as_ref())? {
8✔
682
            Some(_) => {
683
                self.update_transaction_details(transaction)?;
×
684
            }
685
            None => {
686
                self.insert_transaction_details(transaction)?;
8✔
687
            }
688
        }
689

690
        if let Some(tx) = &transaction.transaction {
8✔
691
            self.set_raw_tx(tx)?;
6✔
692
        }
2✔
693

694
        Ok(())
8✔
695
    }
8✔
696

697
    fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
2✔
698
        self.update_last_derivation_index(serde_json::to_string(&keychain)?, value)?;
2✔
699
        Ok(())
2✔
700
    }
2✔
701

702
    fn set_sync_time(&mut self, ct: SyncTime) -> Result<(), Error> {
703
        self.update_sync_time(ct)?;
2✔
704
        Ok(())
2✔
705
    }
2✔
706

707
    fn del_script_pubkey_from_path(
2✔
708
        &mut self,
2✔
709
        keychain: KeychainKind,
2✔
710
        child: u32,
2✔
711
    ) -> Result<Option<ScriptBuf>, Error> {
2✔
712
        let keychain = serde_json::to_string(&keychain)?;
2✔
713
        let script = self.select_script_pubkey_by_path(keychain.clone(), child)?;
2✔
714
        match script {
2✔
715
            Some(script) => {
2✔
716
                self.delete_script_pubkey_by_path(keychain, child)?;
2✔
717
                Ok(Some(script))
2✔
718
            }
719
            None => Ok(None),
×
720
        }
721
    }
2✔
722

723
    fn del_path_from_script_pubkey(
724
        &mut self,
725
        script: &Script,
726
    ) -> Result<Option<(KeychainKind, u32)>, Error> {
727
        match self.select_script_pubkey_by_script(script.as_bytes())? {
4✔
728
            Some((keychain, child)) => {
2✔
729
                self.delete_script_pubkey_by_script(script.as_bytes())?;
2✔
730
                Ok(Some((keychain, child)))
2✔
731
            }
732
            None => Ok(None),
2✔
733
        }
734
    }
4✔
735

736
    fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
737
        match self.select_utxo_by_outpoint(&outpoint.txid.as_ref(), outpoint.vout)? {
4✔
738
            Some(local_utxo) => {
2✔
739
                self.delete_utxo_by_outpoint(&outpoint.txid.as_ref(), outpoint.vout)?;
2✔
740
                Ok(Some(local_utxo))
2✔
741
            }
742
            None => Ok(None),
2✔
743
        }
744
    }
4✔
745

746
    fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
747
        match self.select_transaction_by_txid(txid.as_ref())? {
4✔
748
            Some(tx) => {
2✔
749
                self.delete_transaction_by_txid(txid.as_ref())?;
2✔
750
                Ok(Some(tx))
2✔
751
            }
752
            None => Ok(None),
2✔
753
        }
754
    }
4✔
755

756
    fn del_tx(
757
        &mut self,
758
        txid: &Txid,
759
        include_raw: bool,
760
    ) -> Result<Option<TransactionDetails>, Error> {
761
        match self.select_transaction_details_by_txid(txid.as_ref())? {
6✔
762
            Some(mut transaction_details) => {
4✔
763
                self.delete_transaction_details_by_txid(txid.as_ref())?;
4✔
764

765
                if include_raw {
4✔
766
                    self.delete_transaction_by_txid(txid.as_ref())?;
2✔
767
                } else {
2✔
768
                    transaction_details.transaction = None;
2✔
769
                }
2✔
770
                Ok(Some(transaction_details))
4✔
771
            }
772
            None => Ok(None),
2✔
773
        }
774
    }
6✔
775

776
    fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
2✔
777
        let keychain = serde_json::to_string(&keychain)?;
2✔
778
        match self.select_last_derivation_index_by_keychain(keychain.clone())? {
2✔
779
            Some(value) => {
2✔
780
                self.delete_last_derivation_index_by_keychain(keychain)?;
2✔
781

782
                Ok(Some(value))
2✔
783
            }
784
            None => Ok(None),
×
785
        }
786
    }
2✔
787

788
    fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
789
        match self.select_sync_time()? {
2✔
790
            Some(value) => {
2✔
791
                self.delete_sync_time()?;
2✔
792

793
                Ok(Some(value))
2✔
794
            }
795
            None => Ok(None),
×
796
        }
797
    }
2✔
798
}
799

800
impl Database for SqliteDatabase {
801
    fn check_descriptor_checksum<B: AsRef<[u8]>>(
4✔
802
        &mut self,
4✔
803
        keychain: KeychainKind,
4✔
804
        bytes: B,
4✔
805
    ) -> Result<(), Error> {
4✔
806
        let keychain = serde_json::to_string(&keychain)?;
4✔
807

808
        match self.select_checksum_by_keychain(keychain.clone())? {
4✔
809
            Some(checksum) => {
2✔
810
                if checksum == bytes.as_ref().to_vec() {
2✔
811
                    Ok(())
×
812
                } else {
813
                    Err(Error::ChecksumMismatch)
2✔
814
                }
815
            }
816
            None => {
817
                self.insert_checksum(keychain, bytes.as_ref())?;
2✔
818
                Ok(())
2✔
819
            }
820
        }
821
    }
4✔
822

823
    fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error> {
10✔
824
        match keychain {
10✔
825
            Some(keychain) => {
4✔
826
                let keychain = serde_json::to_string(&keychain)?;
4✔
827
                self.select_script_pubkeys_by_keychain(keychain)
4✔
828
            }
829
            None => self.select_script_pubkeys(),
6✔
830
        }
831
    }
10✔
832

833
    fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
2✔
834
        self.select_utxos()
2✔
835
    }
2✔
836

837
    fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
4✔
838
        self.select_transactions()
4✔
839
    }
4✔
840

841
    fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
4✔
842
        match include_raw {
4✔
843
            true => self.select_transaction_details_with_raw(),
2✔
844
            false => self.select_transaction_details(),
2✔
845
        }
846
    }
4✔
847

848
    fn get_script_pubkey_from_path(
6✔
849
        &self,
6✔
850
        keychain: KeychainKind,
6✔
851
        child: u32,
6✔
852
    ) -> Result<Option<ScriptBuf>, Error> {
6✔
853
        let keychain = serde_json::to_string(&keychain)?;
6✔
854
        match self.select_script_pubkey_by_path(keychain, child)? {
6✔
855
            Some(script) => Ok(Some(script)),
4✔
856
            None => Ok(None),
2✔
857
        }
858
    }
6✔
859

860
    fn get_path_from_script_pubkey(
861
        &self,
862
        script: &Script,
863
    ) -> Result<Option<(KeychainKind, u32)>, Error> {
864
        match self.select_script_pubkey_by_script(script.as_bytes())? {
8✔
865
            Some((keychain, child)) => Ok(Some((keychain, child))),
4✔
866
            None => Ok(None),
4✔
867
        }
868
    }
8✔
869

870
    fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
4✔
871
        self.select_utxo_by_outpoint(&outpoint.txid.as_ref(), outpoint.vout)
4✔
872
    }
4✔
873

874
    fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
875
        match self.select_transaction_by_txid(txid.as_ref())? {
12✔
876
            Some(tx) => Ok(Some(tx)),
8✔
877
            None => Ok(None),
4✔
878
        }
879
    }
12✔
880

881
    fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
882
        match self.select_transaction_details_by_txid(txid.as_ref())? {
8✔
883
            Some(mut transaction_details) => {
4✔
884
                if !include_raw {
4✔
885
                    transaction_details.transaction = None;
2✔
886
                }
2✔
887
                Ok(Some(transaction_details))
4✔
888
            }
889
            None => Ok(None),
4✔
890
        }
891
    }
8✔
892

893
    fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
20✔
894
        let keychain = serde_json::to_string(&keychain)?;
20✔
895
        let value = self.select_last_derivation_index_by_keychain(keychain)?;
20✔
896
        Ok(value)
20✔
897
    }
20✔
898

899
    fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
6✔
900
        self.select_sync_time()
6✔
901
    }
6✔
902

903
    fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
8✔
904
        let keychain_string = serde_json::to_string(&keychain)?;
8✔
905
        match self.get_last_index(keychain)? {
8✔
906
            Some(value) => {
4✔
907
                self.update_last_derivation_index(keychain_string, value + 1)?;
4✔
908
                Ok(value + 1)
4✔
909
            }
910
            None => {
911
                self.insert_last_derivation_index(keychain_string, 0)?;
4✔
912
                Ok(0)
4✔
913
            }
914
        }
915
    }
8✔
916
}
917

918
impl BatchDatabase for SqliteDatabase {
919
    type Batch = SqliteDatabase;
920

921
    fn begin_batch(&self) -> Self::Batch {
2✔
922
        let db = SqliteDatabase::new(self.path.clone());
2✔
923
        db.connection.execute("BEGIN TRANSACTION", []).unwrap();
2✔
924
        db
2✔
925
    }
2✔
926

927
    fn commit_batch(&mut self, batch: Self::Batch) -> Result<(), Error> {
928
        batch.connection.execute("COMMIT TRANSACTION", [])?;
2✔
929
        Ok(())
2✔
930
    }
2✔
931
}
932

933
pub fn get_connection<T: AsRef<Path>>(path: &T) -> Result<Connection, Error> {
40✔
934
    let mut connection = Connection::open(path)?;
40✔
935
    migrate(&mut connection)?;
40✔
936
    Ok(connection)
40✔
937
}
40✔
938

939
pub fn get_schema_version(conn: &Connection) -> rusqlite::Result<i32> {
40✔
940
    let statement = conn.prepare_cached("SELECT version FROM version");
40✔
941
    match statement {
38✔
942
        Err(rusqlite::Error::SqliteFailure(e, Some(msg))) => {
38✔
943
            if msg == "no such table: version" {
38✔
944
                Ok(0)
38✔
945
            } else {
946
                Err(rusqlite::Error::SqliteFailure(e, Some(msg)))
×
947
            }
948
        }
949
        Ok(mut stmt) => {
2✔
950
            let mut rows = stmt.query([])?;
2✔
951
            match rows.next()? {
2✔
952
                Some(row) => {
2✔
953
                    let version: i32 = row.get(0)?;
2✔
954
                    Ok(version)
2✔
955
                }
956
                None => Ok(0),
×
957
            }
958
        }
959
        _ => Ok(0),
×
960
    }
961
}
40✔
962

963
pub fn set_schema_version(conn: &Connection, version: i32) -> rusqlite::Result<usize> {
38✔
964
    conn.execute(
38✔
965
        "UPDATE version SET version=:version",
38✔
966
        named_params! {":version": version},
38✔
967
    )
38✔
968
}
38✔
969

970
pub fn migrate(conn: &mut Connection) -> Result<(), Error> {
40✔
971
    let version = get_schema_version(conn)?;
40✔
972
    let stmts = &MIGRATIONS[(version as usize)..];
40✔
973

974
    // begin transaction, all migration statements and new schema version commit or rollback
975
    let tx = conn.transaction()?;
40✔
976

977
    // execute every statement and return `Some` new schema version
978
    // if execution fails, return `Error::Rusqlite`
979
    // if no statements executed returns `None`
980
    let new_version = stmts
40✔
981
        .iter()
40✔
982
        .enumerate()
40✔
983
        .map(|version_stmt| {
1,692✔
984
            log::info!(
1,672✔
985
                "executing db migration {}: `{}`",
×
986
                version + version_stmt.0 as i32 + 1,
×
987
                version_stmt.1
988
            );
989
            tx.execute(version_stmt.1, [])
1,672✔
990
                // map result value to next migration version
1,672✔
991
                .map(|_| version_stmt.0 as i32 + version + 1)
1,672✔
992
        })
1,692✔
993
        .last()
40✔
994
        .transpose()?;
40✔
995

996
    // if `Some` new statement version, set new schema version
997
    if let Some(version) = new_version {
40✔
998
        set_schema_version(&tx, version)?;
38✔
999
    } else {
1000
        log::info!("db up to date, no migration needed");
2✔
1001
    }
1002

1003
    // commit transaction
1004
    tx.commit()?;
40✔
1005
    Ok(())
40✔
1006
}
40✔
1007

1008
#[cfg(test)]
1009
pub mod test {
1010
    use crate::database::SqliteDatabase;
1011
    use std::time::{SystemTime, UNIX_EPOCH};
1012

1013
    fn get_database() -> SqliteDatabase {
38✔
1014
        let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
38✔
1015
        let mut dir = std::env::temp_dir();
38✔
1016
        dir.push(format!("bdk_{}", time.as_nanos()));
38✔
1017
        SqliteDatabase::new(String::from(dir.to_str().unwrap()))
38✔
1018
    }
38✔
1019

1020
    #[test]
2✔
1021
    fn test_script_pubkey() {
2✔
1022
        crate::database::test::test_script_pubkey(get_database());
2✔
1023
    }
2✔
1024

1025
    #[test]
2✔
1026
    fn test_batch_script_pubkey() {
2✔
1027
        crate::database::test::test_batch_script_pubkey(get_database());
2✔
1028
    }
2✔
1029

1030
    #[test]
2✔
1031
    fn test_iter_script_pubkey() {
2✔
1032
        crate::database::test::test_iter_script_pubkey(get_database());
2✔
1033
    }
2✔
1034

1035
    #[test]
2✔
1036
    fn test_del_script_pubkey() {
2✔
1037
        crate::database::test::test_del_script_pubkey(get_database());
2✔
1038
    }
2✔
1039

1040
    #[test]
2✔
1041
    fn test_utxo() {
2✔
1042
        crate::database::test::test_utxo(get_database());
2✔
1043
    }
2✔
1044

1045
    #[test]
2✔
1046
    fn test_raw_tx() {
2✔
1047
        crate::database::test::test_raw_tx(get_database());
2✔
1048
    }
2✔
1049

1050
    #[test]
2✔
1051
    fn test_tx() {
2✔
1052
        crate::database::test::test_tx(get_database());
2✔
1053
    }
2✔
1054

1055
    #[test]
2✔
1056
    fn test_last_index() {
2✔
1057
        crate::database::test::test_last_index(get_database());
2✔
1058
    }
2✔
1059

1060
    #[test]
2✔
1061
    fn test_sync_time() {
2✔
1062
        crate::database::test::test_sync_time(get_database());
2✔
1063
    }
2✔
1064

1065
    #[test]
2✔
1066
    fn test_txs() {
2✔
1067
        crate::database::test::test_list_transaction(get_database());
2✔
1068
    }
2✔
1069

1070
    #[test]
2✔
1071
    fn test_iter_raw_txs() {
2✔
1072
        crate::database::test::test_iter_raw_txs(get_database());
2✔
1073
    }
2✔
1074

1075
    #[test]
2✔
1076
    fn test_del_path_from_script_pubkey() {
2✔
1077
        crate::database::test::test_del_path_from_script_pubkey(get_database());
2✔
1078
    }
2✔
1079

1080
    #[test]
2✔
1081
    fn test_iter_script_pubkeys() {
2✔
1082
        crate::database::test::test_iter_script_pubkeys(get_database());
2✔
1083
    }
2✔
1084

1085
    #[test]
2✔
1086
    fn test_del_utxo() {
2✔
1087
        crate::database::test::test_del_utxo(get_database());
2✔
1088
    }
2✔
1089

1090
    #[test]
2✔
1091
    fn test_del_raw_tx() {
2✔
1092
        crate::database::test::test_del_raw_tx(get_database());
2✔
1093
    }
2✔
1094

1095
    #[test]
2✔
1096
    fn test_del_tx() {
2✔
1097
        crate::database::test::test_del_tx(get_database());
2✔
1098
    }
2✔
1099

1100
    #[test]
2✔
1101
    fn test_del_last_index() {
2✔
1102
        crate::database::test::test_del_last_index(get_database());
2✔
1103
    }
2✔
1104

1105
    #[test]
2✔
1106
    fn test_check_descriptor_checksum() {
2✔
1107
        crate::database::test::test_check_descriptor_checksum(get_database());
2✔
1108
    }
2✔
1109

1110
    // Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
1111
    #[test]
2✔
1112
    fn test_unique_spks() {
2✔
1113
        use crate::bitcoin::hashes::hex::FromHex;
2✔
1114
        use crate::database::*;
2✔
1115

2✔
1116
        let mut db = get_database();
2✔
1117

2✔
1118
        let script = ScriptBuf::from(
2✔
1119
            Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
2✔
1120
        );
2✔
1121
        let path = 42;
2✔
1122
        let keychain = KeychainKind::External;
2✔
1123

1124
        for _ in 0..100 {
202✔
1125
            db.set_script_pubkey(&script, keychain, path).unwrap();
200✔
1126
        }
200✔
1127

1128
        let mut statement = db
2✔
1129
            .connection
2✔
1130
            .prepare_cached(
2✔
1131
                "select keychain,child,count(child) from script_pubkeys group by keychain,child;",
2✔
1132
            )
2✔
1133
            .unwrap();
2✔
1134
        let mut rows = statement.query([]).unwrap();
2✔
1135
        while let Some(row) = rows.next().unwrap() {
4✔
1136
            let keychain: String = row.get(0).unwrap();
2✔
1137
            let child: u32 = row.get(1).unwrap();
2✔
1138
            let count: usize = row.get(2).unwrap();
2✔
1139

2✔
1140
            assert_eq!(
2✔
1141
                count, 1,
1142
                "keychain={}, child={}, count={}",
×
1143
                keychain, child, count
1144
            );
1145
        }
1146
    }
2✔
1147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc