refactor(chain,wallet)!: move rusqlite things into it's own file
Also fix imports and rename `sqlite` module to `rusqlite_impl`.
This commit is contained in:
parent
93f9b83e27
commit
2cf07d686b
@ -21,7 +21,7 @@ hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
|
|||||||
miniscript = { version = "12.0.0", optional = true, default-features = false }
|
miniscript = { version = "12.0.0", optional = true, default-features = false }
|
||||||
|
|
||||||
# Feature dependencies
|
# Feature dependencies
|
||||||
rusqlite = { version = "0.31.0", features = ["bundled"], optional = true }
|
rusqlite_crate = { package = "rusqlite", version = "0.31.0", features = ["bundled"], optional = true }
|
||||||
serde_json = {version = "1", optional = true }
|
serde_json = {version = "1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
@ -32,4 +32,4 @@ proptest = "1.2.0"
|
|||||||
default = ["std", "miniscript"]
|
default = ["std", "miniscript"]
|
||||||
std = ["bitcoin/std", "miniscript?/std"]
|
std = ["bitcoin/std", "miniscript?/std"]
|
||||||
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
|
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
|
||||||
sqlite = ["std", "rusqlite", "serde", "serde_json"]
|
rusqlite = ["std", "rusqlite_crate", "serde", "serde_json"]
|
||||||
|
@ -783,74 +783,6 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
impl ChangeSet {
|
|
||||||
/// Schema name for the changeset.
|
|
||||||
pub const SCHEMA_NAME: &'static str = "bdk_keychaintxout";
|
|
||||||
/// Name for table that stores last revealed indices per descriptor id.
|
|
||||||
pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
|
|
||||||
|
|
||||||
/// Initialize sqlite tables for persisting [`KeychainTxOutIndex`].
|
|
||||||
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
let schema_v0: &[&str] = &[
|
|
||||||
// last revealed
|
|
||||||
&format!(
|
|
||||||
"CREATE TABLE {} ( \
|
|
||||||
descriptor_id TEXT PRIMARY KEY NOT NULL, \
|
|
||||||
last_revealed INTEGER NOT NULL \
|
|
||||||
) STRICT",
|
|
||||||
Self::LAST_REVEALED_TABLE_NAME,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
crate::sqlite::migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct [`KeychainTxOutIndex`] from sqlite database and given parameters.
|
|
||||||
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
|
|
||||||
let mut changeset = Self::default();
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare(&format!(
|
|
||||||
"SELECT descriptor_id, last_revealed FROM {}",
|
|
||||||
Self::LAST_REVEALED_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let row_iter = statement.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, Sql<DescriptorId>>("descriptor_id")?,
|
|
||||||
row.get::<_, u32>("last_revealed")?,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
for row in row_iter {
|
|
||||||
let (Sql(descriptor_id), last_revealed) = row?;
|
|
||||||
changeset.last_revealed.insert(descriptor_id, last_revealed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(changeset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist `changeset` to the sqlite database.
|
|
||||||
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::rusqlite::named_params;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare_cached(&format!(
|
|
||||||
"REPLACE INTO {}(descriptor_id, last_revealed) VALUES(:descriptor_id, :last_revealed)",
|
|
||||||
Self::LAST_REVEALED_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for (&descriptor_id, &last_revealed) in &self.last_revealed {
|
|
||||||
statement.execute(named_params! {
|
|
||||||
":descriptor_id": Sql(descriptor_id),
|
|
||||||
":last_revealed": last_revealed,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
|
/// Error returned from [`KeychainTxOutIndex::insert_descriptor`]
|
||||||
pub enum InsertDescriptorError<K> {
|
pub enum InsertDescriptorError<K> {
|
||||||
|
@ -55,16 +55,15 @@ mod spk_iter;
|
|||||||
pub use indexer::keychain_txout;
|
pub use indexer::keychain_txout;
|
||||||
#[cfg(feature = "miniscript")]
|
#[cfg(feature = "miniscript")]
|
||||||
pub use spk_iter::*;
|
pub use spk_iter::*;
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
pub mod sqlite;
|
pub mod rusqlite_impl;
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
pub use rusqlite;
|
|
||||||
pub mod spk_client;
|
pub mod spk_client;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
#[cfg(feature = "rusqlite")]
|
||||||
|
pub extern crate rusqlite_crate as rusqlite;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub extern crate serde_crate as serde;
|
pub extern crate serde_crate as serde;
|
||||||
|
|
||||||
@ -110,3 +109,20 @@ pub const COINBASE_MATURITY: u32 = 100;
|
|||||||
pub type Indexed<T> = (u32, T);
|
pub type Indexed<T> = (u32, T);
|
||||||
/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
|
/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
|
||||||
pub type KeychainIndexed<K, T> = ((K, u32), T);
|
pub type KeychainIndexed<K, T> = ((K, u32), T);
|
||||||
|
|
||||||
|
/// A wrapper that we use to impl remote traits for types in our crate or dependency crates.
|
||||||
|
pub struct Impl<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> From<T> for Impl<T> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::Deref for Impl<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -681,83 +681,6 @@ impl FromIterator<(u32, BlockHash)> for ChangeSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
impl ChangeSet {
|
|
||||||
/// Schema name for the changeset.
|
|
||||||
pub const SCHEMA_NAME: &'static str = "bdk_localchain";
|
|
||||||
/// Name of sqlite table that stores blocks of [`LocalChain`].
|
|
||||||
pub const BLOCKS_TABLE_NAME: &'static str = "bdk_blocks";
|
|
||||||
|
|
||||||
/// Initialize sqlite tables for persisting [`LocalChain`].
|
|
||||||
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
let schema_v0: &[&str] = &[
|
|
||||||
// blocks
|
|
||||||
&format!(
|
|
||||||
"CREATE TABLE {} ( \
|
|
||||||
block_height INTEGER PRIMARY KEY NOT NULL, \
|
|
||||||
block_hash TEXT NOT NULL \
|
|
||||||
) STRICT",
|
|
||||||
Self::BLOCKS_TABLE_NAME,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
crate::sqlite::migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a [`LocalChain`] from sqlite database.
|
|
||||||
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
|
|
||||||
let mut changeset = Self::default();
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare(&format!(
|
|
||||||
"SELECT block_height, block_hash FROM {}",
|
|
||||||
Self::BLOCKS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let row_iter = statement.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, u32>("block_height")?,
|
|
||||||
row.get::<_, Sql<BlockHash>>("block_hash")?,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
for row in row_iter {
|
|
||||||
let (height, Sql(hash)) = row?;
|
|
||||||
changeset.blocks.insert(height, Some(hash));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(changeset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist `changeset` to the sqlite database.
|
|
||||||
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
use rusqlite::named_params;
|
|
||||||
|
|
||||||
let mut replace_statement = db_tx.prepare_cached(&format!(
|
|
||||||
"REPLACE INTO {}(block_height, block_hash) VALUES(:block_height, :block_hash)",
|
|
||||||
Self::BLOCKS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let mut delete_statement = db_tx.prepare_cached(&format!(
|
|
||||||
"DELETE FROM {} WHERE block_height=:block_height",
|
|
||||||
Self::BLOCKS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for (&height, &hash) in &self.blocks {
|
|
||||||
match hash {
|
|
||||||
Some(hash) => replace_statement.execute(named_params! {
|
|
||||||
":block_height": height,
|
|
||||||
":block_hash": Sql(hash),
|
|
||||||
})?,
|
|
||||||
None => delete_statement.execute(named_params! {
|
|
||||||
":block_height": height,
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error which occurs when a [`LocalChain`] is constructed without a genesis checkpoint.
|
/// An error which occurs when a [`LocalChain`] is constructed without a genesis checkpoint.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct MissingGenesisError;
|
pub struct MissingGenesisError;
|
||||||
|
530
crates/chain/src/rusqlite_impl.rs
Normal file
530
crates/chain/src/rusqlite_impl.rs
Normal file
@ -0,0 +1,530 @@
|
|||||||
|
//! Module for stuff
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
use core::str::FromStr;
|
||||||
|
|
||||||
|
use alloc::{borrow::ToOwned, boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||||
|
use bitcoin::consensus::{Decodable, Encodable};
|
||||||
|
use rusqlite;
|
||||||
|
use rusqlite::named_params;
|
||||||
|
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
|
||||||
|
use rusqlite::OptionalExtension;
|
||||||
|
use rusqlite::Transaction;
|
||||||
|
|
||||||
|
/// Table name for schemas.
|
||||||
|
pub const SCHEMAS_TABLE_NAME: &str = "bdk_schemas";
|
||||||
|
|
||||||
|
/// Initialize the schema table.
|
||||||
|
fn init_schemas_table(db_tx: &Transaction) -> rusqlite::Result<()> {
|
||||||
|
let sql = format!("CREATE TABLE IF NOT EXISTS {}( name TEXT PRIMARY KEY NOT NULL, version INTEGER NOT NULL ) STRICT", SCHEMAS_TABLE_NAME);
|
||||||
|
db_tx.execute(&sql, ())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get schema version of `schema_name`.
|
||||||
|
fn schema_version(db_tx: &Transaction, schema_name: &str) -> rusqlite::Result<Option<u32>> {
|
||||||
|
let sql = format!(
|
||||||
|
"SELECT version FROM {} WHERE name=:name",
|
||||||
|
SCHEMAS_TABLE_NAME
|
||||||
|
);
|
||||||
|
db_tx
|
||||||
|
.query_row(&sql, named_params! { ":name": schema_name }, |row| {
|
||||||
|
row.get::<_, u32>("version")
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the `schema_version` of `schema_name`.
|
||||||
|
fn set_schema_version(
|
||||||
|
db_tx: &Transaction,
|
||||||
|
schema_name: &str,
|
||||||
|
schema_version: u32,
|
||||||
|
) -> rusqlite::Result<()> {
|
||||||
|
let sql = format!(
|
||||||
|
"REPLACE INTO {}(name, version) VALUES(:name, :version)",
|
||||||
|
SCHEMAS_TABLE_NAME,
|
||||||
|
);
|
||||||
|
db_tx.execute(
|
||||||
|
&sql,
|
||||||
|
named_params! { ":name": schema_name, ":version": schema_version },
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs logic that initializes/migrates the table schemas.
|
||||||
|
pub fn migrate_schema(
|
||||||
|
db_tx: &Transaction,
|
||||||
|
schema_name: &str,
|
||||||
|
versioned_scripts: &[&[&str]],
|
||||||
|
) -> rusqlite::Result<()> {
|
||||||
|
init_schemas_table(db_tx)?;
|
||||||
|
let current_version = schema_version(db_tx, schema_name)?;
|
||||||
|
let exec_from = current_version.map_or(0_usize, |v| v as usize + 1);
|
||||||
|
let scripts_to_exec = versioned_scripts.iter().enumerate().skip(exec_from);
|
||||||
|
for (version, &script) in scripts_to_exec {
|
||||||
|
set_schema_version(db_tx, schema_name, version as u32)?;
|
||||||
|
for statement in script {
|
||||||
|
db_tx.execute(statement, ())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::Txid> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
bitcoin::Txid::from_str(value.as_str()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::Txid> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::BlockHash> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
bitcoin::BlockHash::from_str(value.as_str()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::BlockHash> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl FromSql for Impl<DescriptorId> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
DescriptorId::from_str(value.as_str()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl ToSql for Impl<DescriptorId> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::Transaction> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
bitcoin::Transaction::consensus_decode_from_finite_reader(&mut value.as_bytes()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::Transaction> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
let mut bytes = Vec::<u8>::new();
|
||||||
|
self.consensus_encode(&mut bytes).map_err(to_sql_error)?;
|
||||||
|
Ok(bytes.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::ScriptBuf> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
Ok(bitcoin::Script::from_bytes(value.as_bytes()?)
|
||||||
|
.to_owned()
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::ScriptBuf> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.as_bytes().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::Amount> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
Ok(bitcoin::Amount::from_sat(value.as_i64()?.try_into().map_err(from_sql_error)?).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::Amount> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
let amount: i64 = self.to_sat().try_into().map_err(to_sql_error)?;
|
||||||
|
Ok(amount.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Anchor + serde_crate::de::DeserializeOwned> FromSql for Impl<A> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
serde_json::from_str(value.as_str()?)
|
||||||
|
.map(Impl)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Anchor + serde_crate::Serialize> ToSql for Impl<A> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
serde_json::to_string(&self.0)
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(to_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl FromSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
miniscript::Descriptor::from_str(value.as_str()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl ToSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromSql for Impl<bitcoin::Network> {
|
||||||
|
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||||
|
bitcoin::Network::from_str(value.as_str()?)
|
||||||
|
.map(Self)
|
||||||
|
.map_err(from_sql_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql for Impl<bitcoin::Network> {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
||||||
|
Ok(self.to_string().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> FromSqlError {
|
||||||
|
FromSqlError::Other(Box::new(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> rusqlite::Error {
|
||||||
|
rusqlite::Error::ToSqlConversionFailure(Box::new(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> tx_graph::ChangeSet<A>
|
||||||
|
where
|
||||||
|
A: Anchor + Clone + Ord + serde::Serialize + serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
/// Schema name for [`tx_graph::ChangeSet`].
|
||||||
|
pub const SCHEMA_NAME: &'static str = "bdk_txgraph";
|
||||||
|
/// Name of table that stores full transactions and `last_seen` timestamps.
|
||||||
|
pub const TXS_TABLE_NAME: &'static str = "bdk_txs";
|
||||||
|
/// Name of table that stores floating txouts.
|
||||||
|
pub const TXOUTS_TABLE_NAME: &'static str = "bdk_txouts";
|
||||||
|
/// Name of table that stores [`Anchor`]s.
|
||||||
|
pub const ANCHORS_TABLE_NAME: &'static str = "bdk_anchors";
|
||||||
|
|
||||||
|
/// Initialize sqlite tables.
|
||||||
|
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
let schema_v0: &[&str] = &[
|
||||||
|
// full transactions
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} ( \
|
||||||
|
txid TEXT PRIMARY KEY NOT NULL, \
|
||||||
|
raw_tx BLOB, \
|
||||||
|
last_seen INTEGER \
|
||||||
|
) STRICT",
|
||||||
|
Self::TXS_TABLE_NAME,
|
||||||
|
),
|
||||||
|
// floating txouts
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} ( \
|
||||||
|
txid TEXT NOT NULL, \
|
||||||
|
vout INTEGER NOT NULL, \
|
||||||
|
value INTEGER NOT NULL, \
|
||||||
|
script BLOB NOT NULL, \
|
||||||
|
PRIMARY KEY (txid, vout) \
|
||||||
|
) STRICT",
|
||||||
|
Self::TXOUTS_TABLE_NAME,
|
||||||
|
),
|
||||||
|
// anchors
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} ( \
|
||||||
|
txid TEXT NOT NULL REFERENCES {} (txid), \
|
||||||
|
block_height INTEGER NOT NULL, \
|
||||||
|
block_hash TEXT NOT NULL, \
|
||||||
|
anchor BLOB NOT NULL, \
|
||||||
|
PRIMARY KEY (txid, block_height, block_hash) \
|
||||||
|
) STRICT",
|
||||||
|
Self::ANCHORS_TABLE_NAME,
|
||||||
|
Self::TXS_TABLE_NAME,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`TxGraph`] from an sqlite database.
|
||||||
|
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut changeset = Self::default();
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare(&format!(
|
||||||
|
"SELECT txid, raw_tx, last_seen FROM {}",
|
||||||
|
Self::TXS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let row_iter = statement.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, Impl<bitcoin::Txid>>("txid")?,
|
||||||
|
row.get::<_, Option<Impl<bitcoin::Transaction>>>("raw_tx")?,
|
||||||
|
row.get::<_, Option<u64>>("last_seen")?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
for row in row_iter {
|
||||||
|
let (Impl(txid), tx, last_seen) = row?;
|
||||||
|
if let Some(Impl(tx)) = tx {
|
||||||
|
changeset.txs.insert(Arc::new(tx));
|
||||||
|
}
|
||||||
|
if let Some(last_seen) = last_seen {
|
||||||
|
changeset.last_seen.insert(txid, last_seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare(&format!(
|
||||||
|
"SELECT txid, vout, value, script FROM {}",
|
||||||
|
Self::TXOUTS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let row_iter = statement.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, Impl<bitcoin::Txid>>("txid")?,
|
||||||
|
row.get::<_, u32>("vout")?,
|
||||||
|
row.get::<_, Impl<bitcoin::Amount>>("value")?,
|
||||||
|
row.get::<_, Impl<bitcoin::ScriptBuf>>("script")?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
for row in row_iter {
|
||||||
|
let (Impl(txid), vout, Impl(value), Impl(script_pubkey)) = row?;
|
||||||
|
changeset.txouts.insert(
|
||||||
|
bitcoin::OutPoint { txid, vout },
|
||||||
|
bitcoin::TxOut {
|
||||||
|
value,
|
||||||
|
script_pubkey,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare(&format!(
|
||||||
|
"SELECT json(anchor), txid FROM {}",
|
||||||
|
Self::ANCHORS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let row_iter = statement.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, Impl<A>>("json(anchor)")?,
|
||||||
|
row.get::<_, Impl<bitcoin::Txid>>("txid")?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
for row in row_iter {
|
||||||
|
let (Impl(anchor), Impl(txid)) = row?;
|
||||||
|
changeset.anchors.insert((anchor, txid));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(changeset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist `changeset` to the sqlite database.
|
||||||
|
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare_cached(&format!(
|
||||||
|
"INSERT INTO {}(txid, raw_tx) VALUES(:txid, :raw_tx) ON CONFLICT(txid) DO UPDATE SET raw_tx=:raw_tx",
|
||||||
|
Self::TXS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for tx in &self.txs {
|
||||||
|
statement.execute(named_params! {
|
||||||
|
":txid": Impl(tx.compute_txid()),
|
||||||
|
":raw_tx": Impl(tx.as_ref().clone()),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db_tx
|
||||||
|
.prepare_cached(&format!(
|
||||||
|
"INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen",
|
||||||
|
Self::TXS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for (&txid, &last_seen) in &self.last_seen {
|
||||||
|
statement.execute(named_params! {
|
||||||
|
":txid": Impl(txid),
|
||||||
|
":last_seen": Some(last_seen),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare_cached(&format!(
|
||||||
|
"REPLACE INTO {}(txid, vout, value, script) VALUES(:txid, :vout, :value, :script)",
|
||||||
|
Self::TXOUTS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for (op, txo) in &self.txouts {
|
||||||
|
statement.execute(named_params! {
|
||||||
|
":txid": Impl(op.txid),
|
||||||
|
":vout": op.vout,
|
||||||
|
":value": Impl(txo.value),
|
||||||
|
":script": Impl(txo.script_pubkey.clone()),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare_cached(&format!(
|
||||||
|
"REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
|
||||||
|
Self::ANCHORS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for (anchor, txid) in &self.anchors {
|
||||||
|
let anchor_block = anchor.anchor_block();
|
||||||
|
statement.execute(named_params! {
|
||||||
|
":txid": Impl(*txid),
|
||||||
|
":block_height": anchor_block.height,
|
||||||
|
":block_hash": Impl(anchor_block.hash),
|
||||||
|
":anchor": Impl(anchor.clone()),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl local_chain::ChangeSet {
|
||||||
|
/// Schema name for the changeset.
|
||||||
|
pub const SCHEMA_NAME: &'static str = "bdk_localchain";
|
||||||
|
/// Name of sqlite table that stores blocks of [`LocalChain`](local_chain::LocalChain).
|
||||||
|
pub const BLOCKS_TABLE_NAME: &'static str = "bdk_blocks";
|
||||||
|
|
||||||
|
/// Initialize sqlite tables for persisting [`local_chain::LocalChain`].
|
||||||
|
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
let schema_v0: &[&str] = &[
|
||||||
|
// blocks
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} ( \
|
||||||
|
block_height INTEGER PRIMARY KEY NOT NULL, \
|
||||||
|
block_hash TEXT NOT NULL \
|
||||||
|
) STRICT",
|
||||||
|
Self::BLOCKS_TABLE_NAME,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a [`LocalChain`](local_chain::LocalChain) from sqlite database.
|
||||||
|
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut changeset = Self::default();
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare(&format!(
|
||||||
|
"SELECT block_height, block_hash FROM {}",
|
||||||
|
Self::BLOCKS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let row_iter = statement.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, u32>("block_height")?,
|
||||||
|
row.get::<_, Impl<bitcoin::BlockHash>>("block_hash")?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
for row in row_iter {
|
||||||
|
let (height, Impl(hash)) = row?;
|
||||||
|
changeset.blocks.insert(height, Some(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(changeset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist `changeset` to the sqlite database.
|
||||||
|
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut replace_statement = db_tx.prepare_cached(&format!(
|
||||||
|
"REPLACE INTO {}(block_height, block_hash) VALUES(:block_height, :block_hash)",
|
||||||
|
Self::BLOCKS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let mut delete_statement = db_tx.prepare_cached(&format!(
|
||||||
|
"DELETE FROM {} WHERE block_height=:block_height",
|
||||||
|
Self::BLOCKS_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for (&height, &hash) in &self.blocks {
|
||||||
|
match hash {
|
||||||
|
Some(hash) => replace_statement.execute(named_params! {
|
||||||
|
":block_height": height,
|
||||||
|
":block_hash": Impl(hash),
|
||||||
|
})?,
|
||||||
|
None => delete_statement.execute(named_params! {
|
||||||
|
":block_height": height,
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "miniscript")]
|
||||||
|
impl keychain_txout::ChangeSet {
|
||||||
|
/// Schema name for the changeset.
|
||||||
|
pub const SCHEMA_NAME: &'static str = "bdk_keychaintxout";
|
||||||
|
/// Name for table that stores last revealed indices per descriptor id.
|
||||||
|
pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
|
||||||
|
|
||||||
|
/// Initialize sqlite tables for persisting
|
||||||
|
/// [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
|
||||||
|
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
let schema_v0: &[&str] = &[
|
||||||
|
// last revealed
|
||||||
|
&format!(
|
||||||
|
"CREATE TABLE {} ( \
|
||||||
|
descriptor_id TEXT PRIMARY KEY NOT NULL, \
|
||||||
|
last_revealed INTEGER NOT NULL \
|
||||||
|
) STRICT",
|
||||||
|
Self::LAST_REVEALED_TABLE_NAME,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database
|
||||||
|
/// and given parameters.
|
||||||
|
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut changeset = Self::default();
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare(&format!(
|
||||||
|
"SELECT descriptor_id, last_revealed FROM {}",
|
||||||
|
Self::LAST_REVEALED_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
let row_iter = statement.query_map([], |row| {
|
||||||
|
Ok((
|
||||||
|
row.get::<_, Impl<DescriptorId>>("descriptor_id")?,
|
||||||
|
row.get::<_, u32>("last_revealed")?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
for row in row_iter {
|
||||||
|
let (Impl(descriptor_id), last_revealed) = row?;
|
||||||
|
changeset.last_revealed.insert(descriptor_id, last_revealed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(changeset)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist `changeset` to the sqlite database.
|
||||||
|
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
||||||
|
Self::init_sqlite_tables(db_tx)?;
|
||||||
|
|
||||||
|
let mut statement = db_tx.prepare_cached(&format!(
|
||||||
|
"REPLACE INTO {}(descriptor_id, last_revealed) VALUES(:descriptor_id, :last_revealed)",
|
||||||
|
Self::LAST_REVEALED_TABLE_NAME,
|
||||||
|
))?;
|
||||||
|
for (&descriptor_id, &last_revealed) in &self.last_revealed {
|
||||||
|
statement.execute(named_params! {
|
||||||
|
":descriptor_id": Impl(descriptor_id),
|
||||||
|
":last_revealed": last_revealed,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,234 +0,0 @@
|
|||||||
//! Module for stuff
|
|
||||||
|
|
||||||
use core::{ops::Deref, str::FromStr};
|
|
||||||
|
|
||||||
use alloc::{borrow::ToOwned, boxed::Box, string::ToString, vec::Vec};
|
|
||||||
use bitcoin::consensus::{Decodable, Encodable};
|
|
||||||
pub use rusqlite;
|
|
||||||
pub use rusqlite::Connection;
|
|
||||||
use rusqlite::OptionalExtension;
|
|
||||||
pub use rusqlite::Transaction;
|
|
||||||
use rusqlite::{
|
|
||||||
named_params,
|
|
||||||
types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef},
|
|
||||||
ToSql,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::Anchor;
|
|
||||||
|
|
||||||
/// Table name for schemas.
|
|
||||||
pub const SCHEMAS_TABLE_NAME: &str = "bdk_schemas";
|
|
||||||
|
|
||||||
/// Initialize the schema table.
|
|
||||||
fn init_schemas_table(db_tx: &Transaction) -> rusqlite::Result<()> {
|
|
||||||
let sql = format!("CREATE TABLE IF NOT EXISTS {}( name TEXT PRIMARY KEY NOT NULL, version INTEGER NOT NULL ) STRICT", SCHEMAS_TABLE_NAME);
|
|
||||||
db_tx.execute(&sql, ())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get schema version of `schema_name`.
|
|
||||||
fn schema_version(db_tx: &Transaction, schema_name: &str) -> rusqlite::Result<Option<u32>> {
|
|
||||||
let sql = format!(
|
|
||||||
"SELECT version FROM {} WHERE name=:name",
|
|
||||||
SCHEMAS_TABLE_NAME
|
|
||||||
);
|
|
||||||
db_tx
|
|
||||||
.query_row(&sql, named_params! { ":name": schema_name }, |row| {
|
|
||||||
row.get::<_, u32>("version")
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the `schema_version` of `schema_name`.
|
|
||||||
fn set_schema_version(
|
|
||||||
db_tx: &Transaction,
|
|
||||||
schema_name: &str,
|
|
||||||
schema_version: u32,
|
|
||||||
) -> rusqlite::Result<()> {
|
|
||||||
let sql = format!(
|
|
||||||
"REPLACE INTO {}(name, version) VALUES(:name, :version)",
|
|
||||||
SCHEMAS_TABLE_NAME,
|
|
||||||
);
|
|
||||||
db_tx.execute(
|
|
||||||
&sql,
|
|
||||||
named_params! { ":name": schema_name, ":version": schema_version },
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs logic that initializes/migrates the table schemas.
|
|
||||||
pub fn migrate_schema(
|
|
||||||
db_tx: &Transaction,
|
|
||||||
schema_name: &str,
|
|
||||||
versioned_scripts: &[&[&str]],
|
|
||||||
) -> rusqlite::Result<()> {
|
|
||||||
init_schemas_table(db_tx)?;
|
|
||||||
let current_version = schema_version(db_tx, schema_name)?;
|
|
||||||
let exec_from = current_version.map_or(0_usize, |v| v as usize + 1);
|
|
||||||
let scripts_to_exec = versioned_scripts.iter().enumerate().skip(exec_from);
|
|
||||||
for (version, &script) in scripts_to_exec {
|
|
||||||
set_schema_version(db_tx, schema_name, version as u32)?;
|
|
||||||
for statement in script {
|
|
||||||
db_tx.execute(statement, ())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper so that we can impl [FromSql] and [ToSql] for multiple types.
|
|
||||||
pub struct Sql<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> From<T> for Sql<T> {
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for Sql<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::Txid> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
bitcoin::Txid::from_str(value.as_str()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::Txid> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::BlockHash> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
bitcoin::BlockHash::from_str(value.as_str()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::BlockHash> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl FromSql for Sql<crate::DescriptorId> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
crate::DescriptorId::from_str(value.as_str()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl ToSql for Sql<crate::DescriptorId> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::Transaction> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
bitcoin::Transaction::consensus_decode_from_finite_reader(&mut value.as_bytes()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::Transaction> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
let mut bytes = Vec::<u8>::new();
|
|
||||||
self.consensus_encode(&mut bytes).map_err(to_sql_error)?;
|
|
||||||
Ok(bytes.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::ScriptBuf> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
Ok(bitcoin::Script::from_bytes(value.as_bytes()?)
|
|
||||||
.to_owned()
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::ScriptBuf> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.as_bytes().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::Amount> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
Ok(bitcoin::Amount::from_sat(value.as_i64()?.try_into().map_err(from_sql_error)?).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::Amount> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
let amount: i64 = self.to_sat().try_into().map_err(to_sql_error)?;
|
|
||||||
Ok(amount.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Anchor + serde_crate::de::DeserializeOwned> FromSql for Sql<A> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
serde_json::from_str(value.as_str()?)
|
|
||||||
.map(Sql)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Anchor + serde_crate::Serialize> ToSql for Sql<A> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
serde_json::to_string(&self.0)
|
|
||||||
.map(Into::into)
|
|
||||||
.map_err(to_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl FromSql for Sql<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
miniscript::Descriptor::from_str(value.as_str()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "miniscript")]
|
|
||||||
impl ToSql for Sql<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromSql for Sql<bitcoin::Network> {
|
|
||||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
|
||||||
bitcoin::Network::from_str(value.as_str()?)
|
|
||||||
.map(Self)
|
|
||||||
.map_err(from_sql_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToSql for Sql<bitcoin::Network> {
|
|
||||||
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
|
|
||||||
Ok(self.to_string().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> FromSqlError {
|
|
||||||
FromSqlError::Other(Box::new(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> rusqlite::Error {
|
|
||||||
rusqlite::Error::ToSqlConversionFailure(Box::new(err))
|
|
||||||
}
|
|
@ -1293,188 +1293,6 @@ impl<A> ChangeSet<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
|
||||||
impl<A> ChangeSet<A>
|
|
||||||
where
|
|
||||||
A: Anchor + Clone + Ord + serde::Serialize + serde::de::DeserializeOwned,
|
|
||||||
{
|
|
||||||
/// Schema name for the [`ChangeSet`].
|
|
||||||
pub const SCHEMA_NAME: &'static str = "bdk_txgraph";
|
|
||||||
/// Name of table that stores full transactions and `last_seen` timestamps.
|
|
||||||
pub const TXS_TABLE_NAME: &'static str = "bdk_txs";
|
|
||||||
/// Name of table that stores floating txouts.
|
|
||||||
pub const TXOUTS_TABLE_NAME: &'static str = "bdk_txouts";
|
|
||||||
/// Name of table that stores [`Anchor`]s.
|
|
||||||
pub const ANCHORS_TABLE_NAME: &'static str = "bdk_anchors";
|
|
||||||
|
|
||||||
/// Initialize sqlite tables.
|
|
||||||
fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
let schema_v0: &[&str] = &[
|
|
||||||
// full transactions
|
|
||||||
&format!(
|
|
||||||
"CREATE TABLE {} ( \
|
|
||||||
txid TEXT PRIMARY KEY NOT NULL, \
|
|
||||||
raw_tx BLOB, \
|
|
||||||
last_seen INTEGER \
|
|
||||||
) STRICT",
|
|
||||||
Self::TXS_TABLE_NAME,
|
|
||||||
),
|
|
||||||
// floating txouts
|
|
||||||
&format!(
|
|
||||||
"CREATE TABLE {} ( \
|
|
||||||
txid TEXT NOT NULL, \
|
|
||||||
vout INTEGER NOT NULL, \
|
|
||||||
value INTEGER NOT NULL, \
|
|
||||||
script BLOB NOT NULL, \
|
|
||||||
PRIMARY KEY (txid, vout) \
|
|
||||||
) STRICT",
|
|
||||||
Self::TXOUTS_TABLE_NAME,
|
|
||||||
),
|
|
||||||
// anchors
|
|
||||||
&format!(
|
|
||||||
"CREATE TABLE {} ( \
|
|
||||||
txid TEXT NOT NULL REFERENCES {} (txid), \
|
|
||||||
block_height INTEGER NOT NULL, \
|
|
||||||
block_hash TEXT NOT NULL, \
|
|
||||||
anchor BLOB NOT NULL, \
|
|
||||||
PRIMARY KEY (txid, block_height, block_hash) \
|
|
||||||
) STRICT",
|
|
||||||
Self::ANCHORS_TABLE_NAME,
|
|
||||||
Self::TXS_TABLE_NAME,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
crate::sqlite::migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a [`TxGraph`] from an sqlite database.
|
|
||||||
pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
|
|
||||||
let mut changeset = Self::default();
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare(&format!(
|
|
||||||
"SELECT txid, raw_tx, last_seen FROM {}",
|
|
||||||
Self::TXS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let row_iter = statement.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, Sql<Txid>>("txid")?,
|
|
||||||
row.get::<_, Option<Sql<Transaction>>>("raw_tx")?,
|
|
||||||
row.get::<_, Option<u64>>("last_seen")?,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
for row in row_iter {
|
|
||||||
let (Sql(txid), tx, last_seen) = row?;
|
|
||||||
if let Some(Sql(tx)) = tx {
|
|
||||||
changeset.txs.insert(Arc::new(tx));
|
|
||||||
}
|
|
||||||
if let Some(last_seen) = last_seen {
|
|
||||||
changeset.last_seen.insert(txid, last_seen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare(&format!(
|
|
||||||
"SELECT txid, vout, value, script FROM {}",
|
|
||||||
Self::TXOUTS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let row_iter = statement.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, Sql<Txid>>("txid")?,
|
|
||||||
row.get::<_, u32>("vout")?,
|
|
||||||
row.get::<_, Sql<Amount>>("value")?,
|
|
||||||
row.get::<_, Sql<bitcoin::ScriptBuf>>("script")?,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
for row in row_iter {
|
|
||||||
let (Sql(txid), vout, Sql(value), Sql(script_pubkey)) = row?;
|
|
||||||
changeset.txouts.insert(
|
|
||||||
OutPoint { txid, vout },
|
|
||||||
TxOut {
|
|
||||||
value,
|
|
||||||
script_pubkey,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare(&format!(
|
|
||||||
"SELECT json(anchor), txid FROM {}",
|
|
||||||
Self::ANCHORS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
let row_iter = statement.query_map([], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, Sql<A>>("json(anchor)")?,
|
|
||||||
row.get::<_, Sql<Txid>>("txid")?,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
for row in row_iter {
|
|
||||||
let (Sql(anchor), Sql(txid)) = row?;
|
|
||||||
changeset.anchors.insert((anchor, txid));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(changeset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persist `changeset` to the sqlite database.
|
|
||||||
pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
|
|
||||||
Self::init_sqlite_tables(db_tx)?;
|
|
||||||
use crate::rusqlite::named_params;
|
|
||||||
use crate::sqlite::Sql;
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare_cached(&format!(
|
|
||||||
"INSERT INTO {}(txid, raw_tx) VALUES(:txid, :raw_tx) ON CONFLICT(txid) DO UPDATE SET raw_tx=:raw_tx",
|
|
||||||
Self::TXS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for tx in &self.txs {
|
|
||||||
statement.execute(named_params! {
|
|
||||||
":txid": Sql(tx.compute_txid()),
|
|
||||||
":raw_tx": Sql(tx.as_ref().clone()),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = db_tx
|
|
||||||
.prepare_cached(&format!(
|
|
||||||
"INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen",
|
|
||||||
Self::TXS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for (&txid, &last_seen) in &self.last_seen {
|
|
||||||
statement.execute(named_params! {
|
|
||||||
":txid": Sql(txid),
|
|
||||||
":last_seen": Some(last_seen),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare_cached(&format!(
|
|
||||||
"REPLACE INTO {}(txid, vout, value, script) VALUES(:txid, :vout, :value, :script)",
|
|
||||||
Self::TXOUTS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for (op, txo) in &self.txouts {
|
|
||||||
statement.execute(named_params! {
|
|
||||||
":txid": Sql(op.txid),
|
|
||||||
":vout": op.vout,
|
|
||||||
":value": Sql(txo.value),
|
|
||||||
":script": Sql(txo.script_pubkey.clone()),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut statement = db_tx.prepare_cached(&format!(
|
|
||||||
"REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
|
|
||||||
Self::ANCHORS_TABLE_NAME,
|
|
||||||
))?;
|
|
||||||
for (anchor, txid) in &self.anchors {
|
|
||||||
let anchor_block = anchor.anchor_block();
|
|
||||||
statement.execute(named_params! {
|
|
||||||
":txid": Sql(*txid),
|
|
||||||
":block_height": anchor_block.height,
|
|
||||||
":block_hash": Sql(anchor_block.hash),
|
|
||||||
":anchor": Sql(anchor.clone()),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<A: Ord> Merge for ChangeSet<A> {
|
impl<A: Ord> Merge for ChangeSet<A> {
|
||||||
fn merge(&mut self, other: Self) {
|
fn merge(&mut self, other: Self) {
|
||||||
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
|
// We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
|
||||||
|
@ -30,15 +30,15 @@ std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"]
|
|||||||
compiler = ["miniscript/compiler"]
|
compiler = ["miniscript/compiler"]
|
||||||
all-keys = ["keys-bip39"]
|
all-keys = ["keys-bip39"]
|
||||||
keys-bip39 = ["bip39"]
|
keys-bip39 = ["bip39"]
|
||||||
sqlite = ["bdk_chain/sqlite"]
|
rusqlite = ["bdk_chain/rusqlite"]
|
||||||
file_store = ["bdk_file_store"]
|
file_store = ["bdk_file_store"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
bdk_chain = { path = "../chain", features = ["sqlite"] }
|
bdk_chain = { path = "../chain", features = ["rusqlite"] }
|
||||||
bdk_wallet = { path = ".", features = ["sqlite", "file_store"] }
|
bdk_wallet = { path = ".", features = ["rusqlite", "file_store"] }
|
||||||
bdk_file_store = { path = "../file_store" }
|
bdk_file_store = { path = "../file_store" }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
rand = "^0.8"
|
rand = "^0.8"
|
||||||
|
@ -32,10 +32,10 @@ mod types;
|
|||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
pub(crate) use bdk_chain::collections;
|
pub(crate) use bdk_chain::collections;
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
pub use bdk_chain::rusqlite;
|
pub use bdk_chain::rusqlite;
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
pub use bdk_chain::sqlite;
|
pub use bdk_chain::rusqlite_impl;
|
||||||
pub use descriptor::template;
|
pub use descriptor::template;
|
||||||
pub use descriptor::HdKeyPaths;
|
pub use descriptor::HdKeyPaths;
|
||||||
pub use signer;
|
pub use signer;
|
||||||
|
@ -64,7 +64,7 @@ impl Merge for ChangeSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
impl ChangeSet {
|
impl ChangeSet {
|
||||||
/// Schema name for wallet.
|
/// Schema name for wallet.
|
||||||
pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
|
pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
|
||||||
@ -84,14 +84,14 @@ impl ChangeSet {
|
|||||||
) STRICT;",
|
) STRICT;",
|
||||||
Self::WALLET_TABLE_NAME,
|
Self::WALLET_TABLE_NAME,
|
||||||
)];
|
)];
|
||||||
crate::sqlite::migrate_schema(db_tx, Self::WALLET_SCHEMA_NAME, &[schema_v0])
|
crate::rusqlite_impl::migrate_schema(db_tx, Self::WALLET_SCHEMA_NAME, &[schema_v0])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recover a [`ChangeSet`] from sqlite database.
|
/// Recover a [`ChangeSet`] from sqlite database.
|
||||||
pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<Self> {
|
pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<Self> {
|
||||||
Self::init_wallet_sqlite_tables(db_tx)?;
|
Self::init_wallet_sqlite_tables(db_tx)?;
|
||||||
use crate::sqlite::Sql;
|
|
||||||
use chain::rusqlite::OptionalExtension;
|
use chain::rusqlite::OptionalExtension;
|
||||||
|
use chain::Impl;
|
||||||
use miniscript::{Descriptor, DescriptorPublicKey};
|
use miniscript::{Descriptor, DescriptorPublicKey};
|
||||||
|
|
||||||
let mut changeset = Self::default();
|
let mut changeset = Self::default();
|
||||||
@ -103,13 +103,13 @@ impl ChangeSet {
|
|||||||
let row = wallet_statement
|
let row = wallet_statement
|
||||||
.query_row([], |row| {
|
.query_row([], |row| {
|
||||||
Ok((
|
Ok((
|
||||||
row.get::<_, Sql<Descriptor<DescriptorPublicKey>>>("descriptor")?,
|
row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("descriptor")?,
|
||||||
row.get::<_, Sql<Descriptor<DescriptorPublicKey>>>("change_descriptor")?,
|
row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("change_descriptor")?,
|
||||||
row.get::<_, Sql<bitcoin::Network>>("network")?,
|
row.get::<_, Impl<bitcoin::Network>>("network")?,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.optional()?;
|
.optional()?;
|
||||||
if let Some((Sql(desc), Sql(change_desc), Sql(network))) = row {
|
if let Some((Impl(desc), Impl(change_desc), Impl(network))) = row {
|
||||||
changeset.descriptor = Some(desc);
|
changeset.descriptor = Some(desc);
|
||||||
changeset.change_descriptor = Some(change_desc);
|
changeset.change_descriptor = Some(change_desc);
|
||||||
changeset.network = Some(network);
|
changeset.network = Some(network);
|
||||||
@ -129,7 +129,7 @@ impl ChangeSet {
|
|||||||
) -> chain::rusqlite::Result<()> {
|
) -> chain::rusqlite::Result<()> {
|
||||||
Self::init_wallet_sqlite_tables(db_tx)?;
|
Self::init_wallet_sqlite_tables(db_tx)?;
|
||||||
use chain::rusqlite::named_params;
|
use chain::rusqlite::named_params;
|
||||||
use chain::sqlite::Sql;
|
use chain::Impl;
|
||||||
|
|
||||||
let mut descriptor_statement = db_tx.prepare_cached(&format!(
|
let mut descriptor_statement = db_tx.prepare_cached(&format!(
|
||||||
"INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor",
|
"INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor",
|
||||||
@ -138,7 +138,7 @@ impl ChangeSet {
|
|||||||
if let Some(descriptor) = &self.descriptor {
|
if let Some(descriptor) = &self.descriptor {
|
||||||
descriptor_statement.execute(named_params! {
|
descriptor_statement.execute(named_params! {
|
||||||
":id": 0,
|
":id": 0,
|
||||||
":descriptor": Sql(descriptor.clone()),
|
":descriptor": Impl(descriptor.clone()),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ impl ChangeSet {
|
|||||||
if let Some(change_descriptor) = &self.change_descriptor {
|
if let Some(change_descriptor) = &self.change_descriptor {
|
||||||
change_descriptor_statement.execute(named_params! {
|
change_descriptor_statement.execute(named_params! {
|
||||||
":id": 0,
|
":id": 0,
|
||||||
":change_descriptor": Sql(change_descriptor.clone()),
|
":change_descriptor": Impl(change_descriptor.clone()),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ impl ChangeSet {
|
|||||||
if let Some(network) = self.network {
|
if let Some(network) = self.network {
|
||||||
network_statement.execute(named_params! {
|
network_statement.execute(named_params! {
|
||||||
":id": 0,
|
":id": 0,
|
||||||
":network": Sql(network),
|
":network": Impl(network),
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@ impl Wallet {
|
|||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind};
|
/// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind};
|
||||||
/// use bdk_chain::sqlite::Connection;
|
/// use bdk_chain::rusqlite::Connection;
|
||||||
/// let mut conn = Connection::open_in_memory().expect("must open connection");
|
/// let mut conn = Connection::open_in_memory().expect("must open connection");
|
||||||
/// let mut wallet = LoadParams::new()
|
/// let mut wallet = LoadParams::new()
|
||||||
/// .load_wallet(&mut conn)
|
/// .load_wallet(&mut conn)
|
||||||
|
@ -5,8 +5,8 @@ use crate::{descriptor::DescriptorError, Wallet};
|
|||||||
/// Represents a persisted wallet.
|
/// Represents a persisted wallet.
|
||||||
pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
|
pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
impl<'c> chain::PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
impl<'c> chain::PersistWith<bdk_chain::rusqlite::Transaction<'c>> for Wallet {
|
||||||
type CreateParams = crate::CreateParams;
|
type CreateParams = crate::CreateParams;
|
||||||
type LoadParams = crate::LoadParams;
|
type LoadParams = crate::LoadParams;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ impl<'c> chain::PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
|||||||
type PersistError = bdk_chain::rusqlite::Error;
|
type PersistError = bdk_chain::rusqlite::Error;
|
||||||
|
|
||||||
fn create(
|
fn create(
|
||||||
db: &mut bdk_chain::sqlite::Transaction<'c>,
|
db: &mut bdk_chain::rusqlite::Transaction<'c>,
|
||||||
params: Self::CreateParams,
|
params: Self::CreateParams,
|
||||||
) -> Result<Self, Self::CreateError> {
|
) -> Result<Self, Self::CreateError> {
|
||||||
let mut wallet =
|
let mut wallet =
|
||||||
@ -29,7 +29,7 @@ impl<'c> chain::PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
conn: &mut bdk_chain::sqlite::Transaction<'c>,
|
conn: &mut bdk_chain::rusqlite::Transaction<'c>,
|
||||||
params: Self::LoadParams,
|
params: Self::LoadParams,
|
||||||
) -> Result<Option<Self>, Self::LoadError> {
|
) -> Result<Option<Self>, Self::LoadError> {
|
||||||
let changeset =
|
let changeset =
|
||||||
@ -41,15 +41,15 @@ impl<'c> chain::PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn persist(
|
fn persist(
|
||||||
db: &mut bdk_chain::sqlite::Transaction<'c>,
|
db: &mut bdk_chain::rusqlite::Transaction<'c>,
|
||||||
changeset: &<Self as chain::Staged>::ChangeSet,
|
changeset: &<Self as chain::Staged>::ChangeSet,
|
||||||
) -> Result<(), Self::PersistError> {
|
) -> Result<(), Self::PersistError> {
|
||||||
changeset.persist_to_sqlite(db)
|
changeset.persist_to_sqlite(db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sqlite")]
|
#[cfg(feature = "rusqlite")]
|
||||||
impl chain::PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
impl chain::PersistWith<bdk_chain::rusqlite::Connection> for Wallet {
|
||||||
type CreateParams = crate::CreateParams;
|
type CreateParams = crate::CreateParams;
|
||||||
type LoadParams = crate::LoadParams;
|
type LoadParams = crate::LoadParams;
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ impl chain::PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
|||||||
type PersistError = bdk_chain::rusqlite::Error;
|
type PersistError = bdk_chain::rusqlite::Error;
|
||||||
|
|
||||||
fn create(
|
fn create(
|
||||||
db: &mut bdk_chain::sqlite::Connection,
|
db: &mut bdk_chain::rusqlite::Connection,
|
||||||
params: Self::CreateParams,
|
params: Self::CreateParams,
|
||||||
) -> Result<Self, Self::CreateError> {
|
) -> Result<Self, Self::CreateError> {
|
||||||
let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
|
let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
|
||||||
@ -68,7 +68,7 @@ impl chain::PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
db: &mut bdk_chain::sqlite::Connection,
|
db: &mut bdk_chain::rusqlite::Connection,
|
||||||
params: Self::LoadParams,
|
params: Self::LoadParams,
|
||||||
) -> Result<Option<Self>, Self::LoadError> {
|
) -> Result<Option<Self>, Self::LoadError> {
|
||||||
let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
|
let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
|
||||||
@ -78,7 +78,7 @@ impl chain::PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn persist(
|
fn persist(
|
||||||
db: &mut bdk_chain::sqlite::Connection,
|
db: &mut bdk_chain::rusqlite::Connection,
|
||||||
changeset: &<Self as chain::Staged>::ChangeSet,
|
changeset: &<Self as chain::Staged>::ChangeSet,
|
||||||
) -> Result<(), Self::PersistError> {
|
) -> Result<(), Self::PersistError> {
|
||||||
let db_tx = db.transaction()?;
|
let db_tx = db.transaction()?;
|
||||||
|
@ -169,10 +169,10 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
|
|||||||
|path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
|
|path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
|
||||||
|path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
|
|path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
|
||||||
)?;
|
)?;
|
||||||
run::<bdk_chain::sqlite::Connection, _, _>(
|
run::<bdk_chain::rusqlite::Connection, _, _>(
|
||||||
"store.sqlite",
|
"store.sqlite",
|
||||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
|path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
|
||||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
|path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -258,8 +258,8 @@ fn wallet_load_checks() -> anyhow::Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
run(
|
run(
|
||||||
"store.sqlite",
|
"store.sqlite",
|
||||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
|path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
|
||||||
|path| Ok(bdk_chain::sqlite::Connection::open(path)?),
|
|path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -6,7 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_wallet = { path = "../../crates/wallet", features = ["sqlite"] }
|
bdk_wallet = { path = "../../crates/wallet", features = ["rusqlite"] }
|
||||||
bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] }
|
bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] }
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user