Skip to content

Commit d614e9e

Browse files
evanlinjinnotmandatory
authored andcommitted
fix(sqlite)!: some anchor/keychain types paniced on decode
This was caused by the use of `json_extract()` SQL function. SQLite had funky behavior where not all types returned is encoded in JSON. This made `serde_json` fail. We fixed this by using `json()` instead. Tests: updated `bdk_wallet` tests to test with both `bdk_file_store` and `bdk_sqlite_store`. Additionally, change constructor of `bdk_sqlite_store::Store` to take in a `rusqlite::Connection` directly.
1 parent 2b7e69f commit d614e9e

File tree

8 files changed

+209
-186
lines changed

8 files changed

+209
-186
lines changed

crates/sqlite_store/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This is a simple [SQLite] relational database schema backed implementation of [`PersistBackend`](bdk_persist::PersistBackend).
44

5-
The main structure is [`Store`](store::Store) which works with any [`bdk_chain`] based changesets to persist data into a SQLite database file.
5+
The main structure is `Store` which works with any [`bdk_chain`] based changesets to persist data into a SQLite database file.
66

77
To use `Store` with [`Wallet`](bdk_wallet::wallet::Wallet) enable the `wallet` feature.
88

crates/sqlite_store/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
pub mod persist;
66
mod schema;
7-
pub mod store;
7+
mod store;
88
#[cfg(feature = "wallet")]
99
#[cfg_attr(docsrs, doc(cfg(feature = "wallet")))]
1010
pub mod wallet;
@@ -13,6 +13,7 @@ use bdk_chain::bitcoin::Network;
1313
use bdk_chain::{indexed_tx_graph, keychain, local_chain, Anchor, Append};
1414
pub use rusqlite;
1515
use serde::{Deserialize, Serialize};
16+
pub use store::Store;
1617

1718
/// Change set representing changes to [`local_chain::ChangeSet`] and [`indexed_tx_graph::ChangeSet`].
1819
///

crates/sqlite_store/src/store.rs

+20-42
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use rusqlite::{named_params, Connection};
77
use serde::{Deserialize, Serialize};
88
use std::collections::{BTreeMap, BTreeSet};
99
use std::marker::PhantomData;
10-
use std::path::Path;
1110
use std::str::FromStr;
1211
use std::sync::{Arc, Mutex};
1312

@@ -19,9 +18,7 @@ use bdk_chain::{
1918

2019
/// Persists [`ChangeSet`] data in to a relational schema based SQLite database file.
2120
///
22-
/// The changesets loaded or stored represent changes to keychain and blockchain data. If the
23-
/// keychain (K) is a simple enum without variant fields you must enable the
24-
/// [serde internal tag](https://serde.rs/enum-representations.html#internally-tagged) feature.
21+
/// The changesets loaded or stored represent changes to keychain and blockchain data.
2522
#[derive(Debug)]
2623
pub struct Store<K, A> {
2724
// A rusqlite connection to the SQLite database. Uses a Mutex for thread safety.
@@ -35,27 +32,9 @@ where
3532
K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
3633
A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
3734
{
38-
/// Creates a new store from a [`Path`].
39-
///
40-
/// The file must be able to be opened with read and write permissions.
41-
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, rusqlite::Error> {
42-
let mut conn = Connection::open(path)?;
35+
/// Creates a new store from a [`Connection`].
36+
pub fn new(mut conn: Connection) -> Result<Self, rusqlite::Error> {
4337
Self::migrate(&mut conn)?;
44-
45-
Ok(Self {
46-
conn: Mutex::new(conn),
47-
keychain_marker: Default::default(),
48-
anchor_marker: Default::default(),
49-
})
50-
}
51-
52-
/// Creates a new in-memory, not persisted database store.
53-
///
54-
/// This is primarily used for testing.
55-
pub fn new_memory() -> Result<Self, rusqlite::Error> {
56-
let mut conn = Connection::open_in_memory()?;
57-
Self::migrate(&mut conn)?;
58-
5938
Ok(Self {
6039
conn: Mutex::new(conn),
6140
keychain_marker: Default::default(),
@@ -261,13 +240,13 @@ where
261240
db_transaction: &rusqlite::Transaction,
262241
) -> Result<BTreeMap<K, Descriptor<DescriptorPublicKey>>, Error> {
263242
let mut select_keychains_added_stmt = db_transaction
264-
.prepare_cached("SELECT json_extract(keychain, '$'), descriptor FROM keychain")
243+
.prepare_cached("SELECT json(keychain), descriptor FROM keychain")
265244
.expect("select keychains statement");
266245

267246
let keychains = select_keychains_added_stmt
268247
.query_map([], |row| {
269248
let keychain = row.get_unwrap::<usize, String>(0);
270-
let keychain: K = serde_json::from_str(keychain.as_str()).expect("keychain");
249+
let keychain = serde_json::from_str::<K>(keychain.as_str()).expect("keychain");
271250
let descriptor = row.get_unwrap::<usize, String>(1);
272251
let descriptor = Descriptor::from_str(descriptor.as_str()).expect("descriptor");
273252
Ok((keychain, descriptor))
@@ -487,7 +466,7 @@ where
487466
) -> Result<BTreeSet<(A, Txid)>, Error> {
488467
// serde_json::from_str
489468
let mut select_anchor_stmt = db_transaction
490-
.prepare_cached("SELECT block_hash, json_extract(anchor, '$'), txid FROM anchor_tx")
469+
.prepare_cached("SELECT block_hash, json(anchor), txid FROM anchor_tx")
491470
.expect("select anchor statement");
492471
let anchors = select_anchor_stmt
493472
.query_map([], |row| {
@@ -626,19 +605,18 @@ mod test {
626605
}
627606

628607
#[test]
629-
fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor() {
608+
fn insert_and_load_aggregate_changesets_with_confirmation_time_height_anchor(
609+
) -> anyhow::Result<()> {
630610
let (test_changesets, agg_test_changesets) =
631611
create_test_changesets(&|height, time, hash| ConfirmationTimeHeightAnchor {
632612
confirmation_height: height,
633613
confirmation_time: time,
634614
anchor_block: (height, hash).into(),
635615
});
636616

637-
let mut store = Store::<Keychain, ConfirmationTimeHeightAnchor>::new_memory()
617+
let conn = Connection::open_in_memory().expect("in memory connection");
618+
let mut store = Store::<Keychain, ConfirmationTimeHeightAnchor>::new(conn)
638619
.expect("create new memory db store");
639-
// let mut store =
640-
// Store::<Keychain, ConfirmationTimeHeightAnchor>::new(Path::new("test_agg.sqlite"))
641-
// .expect("create new file db store");
642620

643621
test_changesets.iter().for_each(|changeset| {
644622
store.write_changes(changeset).expect("write changeset");
@@ -647,21 +625,21 @@ mod test {
647625
let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
648626

649627
assert_eq!(agg_changeset, Some(agg_test_changesets));
628+
Ok(())
650629
}
651630

652631
#[test]
653-
fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() {
632+
fn insert_and_load_aggregate_changesets_with_confirmation_height_anchor() -> anyhow::Result<()>
633+
{
654634
let (test_changesets, agg_test_changesets) =
655635
create_test_changesets(&|height, _time, hash| ConfirmationHeightAnchor {
656636
confirmation_height: height,
657637
anchor_block: (height, hash).into(),
658638
});
659639

660-
let mut store = Store::<Keychain, ConfirmationHeightAnchor>::new_memory()
640+
let conn = Connection::open_in_memory().expect("in memory connection");
641+
let mut store = Store::<Keychain, ConfirmationHeightAnchor>::new(conn)
661642
.expect("create new memory db store");
662-
// let mut store =
663-
// Store::<Keychain, ConfirmationHeightAnchor>::new(Path::new("test_agg.sqlite"))
664-
// .expect("create new file db store");
665643

666644
test_changesets.iter().for_each(|changeset| {
667645
store.write_changes(changeset).expect("write changeset");
@@ -670,17 +648,16 @@ mod test {
670648
let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
671649

672650
assert_eq!(agg_changeset, Some(agg_test_changesets));
651+
Ok(())
673652
}
674653

675654
#[test]
676-
fn insert_and_load_aggregate_changesets_with_blockid_anchor() {
655+
fn insert_and_load_aggregate_changesets_with_blockid_anchor() -> anyhow::Result<()> {
677656
let (test_changesets, agg_test_changesets) =
678657
create_test_changesets(&|height, _time, hash| BlockId { height, hash });
679658

680-
let mut store =
681-
Store::<Keychain, BlockId>::new_memory().expect("create new memory db store");
682-
// let mut store = Store::<Keychain, BlockId>::new(Path::new("test_agg.sqlite"))
683-
// .expect("create new file db store");
659+
let conn = Connection::open_in_memory().expect("in memory connection");
660+
let mut store = Store::<Keychain, BlockId>::new(conn).expect("create new memory db store");
684661

685662
test_changesets.iter().for_each(|changeset| {
686663
store.write_changes(changeset).expect("write changeset");
@@ -689,6 +666,7 @@ mod test {
689666
let agg_changeset = store.load_from_persistence().expect("aggregated changeset");
690667

691668
assert_eq!(agg_changeset, Some(agg_test_changesets));
669+
Ok(())
692670
}
693671

694672
fn create_test_changesets<A: Anchor + Copy>(

crates/wallet/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ lazy_static = "1.4"
4646
assert_matches = "1.5.0"
4747
tempfile = "3"
4848
bdk_sqlite_store = { path = "../sqlite_store", features = ["wallet"] }
49+
bdk_file_store = { path = "../file_store" }
4950
anyhow = "1"
5051

5152
[package.metadata.docs.rs]

crates/wallet/src/types.rs

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use serde::{Deserialize, Serialize};
2020

2121
/// Types of keychains
2222
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
23-
#[serde(tag = "type")]
2423
pub enum KeychainKind {
2524
/// External keychain, used for deriving recipient addresses.
2625
External = 0,

crates/wallet/src/wallet/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -531,12 +531,13 @@ impl Wallet {
531531
/// # use bdk_wallet::descriptor::Descriptor;
532532
/// # use bitcoin::key::Secp256k1;
533533
/// # use bdk_wallet::KeychainKind;
534-
/// # use bdk_sqlite_store::store::Store;
534+
/// # use bdk_sqlite_store::{Store, rusqlite::Connection};
535535
/// #
536536
/// # fn main() -> Result<(), anyhow::Error> {
537537
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
538538
/// # let file_path = temp_dir.path().join("store.db");
539-
/// # let db = Store::new(&file_path).expect("must create db");
539+
/// # let conn = Connection::open(file_path).expect("must open connection");
540+
/// # let db = Store::new(conn).expect("must create db");
540541
/// let secp = Secp256k1::new();
541542
///
542543
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();

0 commit comments

Comments
 (0)