Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bdk_sqlite crate implementing PersistBackend #1128

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/nightly_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2
- name: Set default toolchain
run: rustup default nightly-2022-12-14
run: rustup default nightly-2024-05-12
- name: Set profile
run: rustup set profile minimal
- name: Update toolchain
run: rustup update
- name: Rust Cache
uses: Swatinem/rust-cache@v2.2.1
- name: Pin dependencies for MSRV
run: cargo update -p home --precise "0.5.5"
- name: Build docs
run: cargo doc --no-deps
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Cargo.lock

# Example persisted files.
*.db
*.sqlite*
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"crates/wallet",
"crates/chain",
"crates/file_store",
"crates/sqlite",
"crates/electrum",
"crates/esplora",
"crates/bitcoind_rpc",
Expand Down
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,9 @@ This library should compile with any combination of features with Rust 1.63.0.
To build with the MSRV you will need to pin dependencies as follows:

```shell
# zip 0.6.3 has MSRV 1.64.0
cargo update -p zip --precise "0.6.2"
# time 0.3.21 has MSRV 1.65.0
cargo update -p zstd-sys --precise "2.0.8+zstd.1.5.5"
cargo update -p time --precise "0.3.20"
# jobserver 0.1.27 has MSRV 1.66.0
cargo update -p jobserver --precise "0.1.26"
# home 0.5.9 has MSRV 1.70.0
cargo update -p home --precise "0.5.5"
# proptest 1.4.0 has MSRV 1.65.0
cargo update -p proptest --precise "1.2.0"
```

Expand Down
2 changes: 1 addition & 1 deletion crates/bitcoind_rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ bitcoincore-rpc = { version = "0.18" }
bdk_chain = { path = "../chain", version = "0.14", default-features = false }

[dev-dependencies]
bdk_testenv = { path = "../testenv", default_features = false }
bdk_testenv = { path = "../testenv", default-features = false }

[features]
default = ["std"]
Expand Down
3 changes: 0 additions & 3 deletions crates/chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ extern crate alloc;
#[cfg(feature = "serde")]
pub extern crate serde_crate as serde;

#[cfg(feature = "bincode")]
extern crate bincode;

#[cfg(feature = "std")]
#[macro_use]
extern crate std;
Expand Down
2 changes: 1 addition & 1 deletion crates/esplora/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ bitcoin = { version = "0.31.0", optional = true, default-features = false }
miniscript = { version = "11.0.0", optional = true, default-features = false }

[dev-dependencies]
bdk_testenv = { path = "../testenv", default_features = false }
bdk_testenv = { path = "../testenv", default-features = false }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }

[features]
Expand Down
9 changes: 3 additions & 6 deletions crates/file_store/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# BDK File Store

This is a simple append-only flat file implementation of
[`PersistBackend`](bdk_persist::PersistBackend).
This is a simple append-only flat file implementation of [`PersistBackend`](bdk_persist::PersistBackend).

The main structure is [`Store`](crate::Store), which can be used with [`bdk`]'s
`Wallet` to persist wallet data into a flat file.
The main structure is [`Store`] which works with any [`bdk_chain`] based changesets to persist data into a flat file.

[`bdk`]: https://docs.rs/bdk/latest
[`bdk_persist`]: https://docs.rs/bdk_persist/latest
[`bdk_chain`]:https://docs.rs/bdk_chain/latest/bdk_chain/
6 changes: 3 additions & 3 deletions crates/persist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.14.0", default-features = false }

[features]
default = ["bdk_chain/std"]


default = ["bdk_chain/std", "miniscript"]
serde = ["bdk_chain/serde"]
miniscript = ["bdk_chain/miniscript"]
4 changes: 3 additions & 1 deletion crates/persist/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# BDK Persist

This crate is home to the [`PersistBackend`](crate::PersistBackend) trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures. The [`Persist`](crate::Persist) type provides a convenient wrapper around a `PersistBackend` that allows staging changes before committing them.
This crate is home to the [`PersistBackend`] trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures.

The [`Persist`] type provides a convenient wrapper around a [`PersistBackend`] that allows staging changes before committing them.
73 changes: 73 additions & 0 deletions crates/persist/src/changeset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#![cfg(feature = "miniscript")]

use bdk_chain::{bitcoin::Network, indexed_tx_graph, keychain, local_chain, Anchor, Append};

/// Changes from a combination of [`bdk_chain`] structures.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(bdk_chain::serde::Deserialize, bdk_chain::serde::Serialize),
serde(
crate = "bdk_chain::serde",
bound(
deserialize = "A: Ord + bdk_chain::serde::Deserialize<'de>, K: Ord + bdk_chain::serde::Deserialize<'de>",
serialize = "A: Ord + bdk_chain::serde::Serialize, K: Ord + bdk_chain::serde::Serialize",
),
)
)]
pub struct CombinedChangeSet<K, A> {
/// Changes to the [`LocalChain`](local_chain::LocalChain).
pub chain: local_chain::ChangeSet,
/// Changes to [`IndexedTxGraph`](indexed_tx_graph::IndexedTxGraph).
pub indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>,
/// Stores the network type of the transaction data.
pub network: Option<Network>,
}

impl<K, A> Default for CombinedChangeSet<K, A> {
fn default() -> Self {
Self {
chain: Default::default(),
indexed_tx_graph: Default::default(),
network: None,
}
}
}

impl<K: Ord, A: Anchor> Append for CombinedChangeSet<K, A> {
fn append(&mut self, other: Self) {
Append::append(&mut self.chain, other.chain);
Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph);
if other.network.is_some() {
debug_assert!(
self.network.is_none() || self.network == other.network,
"network type must either be just introduced or remain the same"
);
self.network = other.network;
}
}

fn is_empty(&self) -> bool {
self.chain.is_empty() && self.indexed_tx_graph.is_empty() && self.network.is_none()
}
}

impl<K, A> From<local_chain::ChangeSet> for CombinedChangeSet<K, A> {
fn from(chain: local_chain::ChangeSet) -> Self {
Self {
chain,
..Default::default()
}
}
}

impl<K, A> From<indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>>
for CombinedChangeSet<K, A>
{
fn from(indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<K>>) -> Self {
Self {
indexed_tx_graph,
..Default::default()
}
}
}
3 changes: 3 additions & 0 deletions crates/persist/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![doc = include_str!("../README.md")]
#![no_std]
#![warn(missing_docs)]

mod changeset;
mod persist;
pub use changeset::*;
pub use persist::*;
19 changes: 19 additions & 0 deletions crates/sqlite/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "bdk_sqlite"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bitcoindevkit/bdk"
documentation = "https://docs.rs/bdk_sqlite"
description = "A simple SQLite based implementation of Persist for Bitcoin Dev Kit."
keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"]
authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md"

[dependencies]
anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.14.0", features = ["serde", "miniscript"] }
bdk_persist = { path = "../persist", version = "0.2.0", features = ["serde"] }
rusqlite = { version = "0.31.0", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
8 changes: 8 additions & 0 deletions crates/sqlite/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# BDK SQLite

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

The main structure is `Store` which persists [`bdk_persist`] `CombinedChangeSet` data into a SQLite database file.

[`bdk_persist`]:https://docs.rs/bdk_persist/latest/bdk_persist/
[SQLite]: https://www.sqlite.org/index.html
69 changes: 69 additions & 0 deletions crates/sqlite/schema/schema_0.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
-- schema version control
CREATE TABLE version
(
version INTEGER
) STRICT;
INSERT INTO version
VALUES (1);

-- network is the valid network for all other table data
CREATE TABLE network
(
name TEXT UNIQUE NOT NULL
) STRICT;

-- keychain is the json serialized keychain structure as JSONB,
-- descriptor is the complete descriptor string,
-- descriptor_id is a sha256::Hash id of the descriptor string w/o the checksum,
-- last revealed index is a u32
CREATE TABLE keychain
(
keychain BLOB PRIMARY KEY NOT NULL,
descriptor TEXT NOT NULL,
descriptor_id BLOB NOT NULL,
last_revealed INTEGER
) STRICT;

-- hash is block hash hex string,
-- block height is a u32,
CREATE TABLE block
(
hash TEXT PRIMARY KEY NOT NULL,
height INTEGER NOT NULL
) STRICT;

-- txid is transaction hash hex string (reversed)
-- whole_tx is a consensus encoded transaction,
-- last seen is a u64 unix epoch seconds
CREATE TABLE tx
(
txid TEXT PRIMARY KEY NOT NULL,
whole_tx BLOB,
last_seen INTEGER
) STRICT;

-- Outpoint txid hash hex string (reversed)
-- Outpoint vout
-- TxOut value as SATs
-- TxOut script consensus encoded
CREATE TABLE txout
(
txid TEXT NOT NULL,
vout INTEGER NOT NULL,
value INTEGER NOT NULL,
script BLOB NOT NULL,
PRIMARY KEY (txid, vout)
) STRICT;

-- join table between anchor and tx
-- block hash hex string
-- anchor is a json serialized Anchor structure as JSONB,
-- txid is transaction hash hex string (reversed)
CREATE TABLE anchor_tx
(
block_hash TEXT NOT NULL,
anchor BLOB NOT NULL,
txid TEXT NOT NULL REFERENCES tx (txid),
UNIQUE (anchor, txid),
FOREIGN KEY (block_hash) REFERENCES block(hash)
) STRICT;
34 changes: 34 additions & 0 deletions crates/sqlite/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![doc = include_str!("../README.md")]
// only enables the `doc_cfg` feature when the `docsrs` configuration attribute is defined
#![cfg_attr(docsrs, feature(doc_cfg))]

mod schema;
mod store;

use bdk_chain::bitcoin::Network;
pub use rusqlite;
pub use store::Store;

/// Error that occurs while reading or writing change sets with the SQLite database.
#[derive(Debug)]
pub enum Error {
/// Invalid network, cannot change the one already stored in the database.
Network { expected: Network, given: Network },
/// SQLite error.
Sqlite(rusqlite::Error),
}

impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Network { expected, given } => write!(
f,
"network error trying to read or write change set, expected {}, given {}",
expected, given
),
Self::Sqlite(e) => write!(f, "sqlite error reading or writing changeset: {}", e),
}
}
}

impl std::error::Error for Error {}
Loading
Loading