fix(wallet): remove generic from wallet

This commit is contained in:
Rob N 2024-04-13 10:21:24 -10:00
parent ee21ffeee0
commit e51af49ffa
No known key found for this signature in database
GPG Key ID: F4DD8F8486EC0F1F
10 changed files with 157 additions and 234 deletions

View File

@ -13,6 +13,7 @@ edition = "2021"
rust-version = "1.63" rust-version = "1.63"
[dependencies] [dependencies]
anyhow = { version = "1", default-features = false }
rand = "^0.8" rand = "^0.8"
miniscript = { version = "11.0.0", features = ["serde"], default-features = false } miniscript = { version = "11.0.0", features = ["serde"], default-features = false }
bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false } bitcoin = { version = "0.31.0", features = ["serde", "base64", "rand-std"], default-features = false }

View File

@ -47,11 +47,11 @@ impl std::error::Error for MiniscriptPsbtError {}
/// Error returned from [`TxBuilder::finish`] /// Error returned from [`TxBuilder::finish`]
/// ///
/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish /// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish
pub enum CreateTxError<P> { pub enum CreateTxError {
/// There was a problem with the descriptors passed in /// There was a problem with the descriptors passed in
Descriptor(DescriptorError), Descriptor(DescriptorError),
/// We were unable to write wallet data to the persistence backend /// We were unable to load wallet data from or write wallet data to the persistence backend
Persist(P), Persist(anyhow::Error),
/// There was a problem while extracting and manipulating policies /// There was a problem while extracting and manipulating policies
Policy(PolicyError), Policy(PolicyError),
/// Spending policy is not compatible with this [`KeychainKind`] /// Spending policy is not compatible with this [`KeychainKind`]
@ -119,17 +119,14 @@ pub enum CreateTxError<P> {
MiniscriptPsbt(MiniscriptPsbtError), MiniscriptPsbt(MiniscriptPsbtError),
} }
impl<P> fmt::Display for CreateTxError<P> impl fmt::Display for CreateTxError {
where
P: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Descriptor(e) => e.fmt(f), Self::Descriptor(e) => e.fmt(f),
Self::Persist(e) => { Self::Persist(e) => {
write!( write!(
f, f,
"failed to write wallet data to persistence backend: {}", "failed to load wallet data from or write wallet data to persistence backend: {}",
e e
) )
} }
@ -214,38 +211,38 @@ where
} }
} }
impl<P> From<descriptor::error::Error> for CreateTxError<P> { impl From<descriptor::error::Error> for CreateTxError {
fn from(err: descriptor::error::Error) -> Self { fn from(err: descriptor::error::Error) -> Self {
CreateTxError::Descriptor(err) CreateTxError::Descriptor(err)
} }
} }
impl<P> From<PolicyError> for CreateTxError<P> { impl From<PolicyError> for CreateTxError {
fn from(err: PolicyError) -> Self { fn from(err: PolicyError) -> Self {
CreateTxError::Policy(err) CreateTxError::Policy(err)
} }
} }
impl<P> From<MiniscriptPsbtError> for CreateTxError<P> { impl From<MiniscriptPsbtError> for CreateTxError {
fn from(err: MiniscriptPsbtError) -> Self { fn from(err: MiniscriptPsbtError) -> Self {
CreateTxError::MiniscriptPsbt(err) CreateTxError::MiniscriptPsbt(err)
} }
} }
impl<P> From<psbt::Error> for CreateTxError<P> { impl From<psbt::Error> for CreateTxError {
fn from(err: psbt::Error) -> Self { fn from(err: psbt::Error) -> Self {
CreateTxError::Psbt(err) CreateTxError::Psbt(err)
} }
} }
impl<P> From<coin_selection::Error> for CreateTxError<P> { impl From<coin_selection::Error> for CreateTxError {
fn from(err: coin_selection::Error) -> Self { fn from(err: coin_selection::Error) -> Self {
CreateTxError::CoinSelection(err) CreateTxError::CoinSelection(err)
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for CreateTxError<P> {} impl std::error::Error for CreateTxError {}
#[derive(Debug)] #[derive(Debug)]
/// Error returned from [`Wallet::build_fee_bump`] /// Error returned from [`Wallet::build_fee_bump`]

View File

@ -53,9 +53,8 @@
//! # Ok::<_, Box<dyn std::error::Error>>(()) //! # Ok::<_, Box<dyn std::error::Error>>(())
//! ``` //! ```
use core::str::FromStr;
use alloc::string::{String, ToString}; use alloc::string::{String, ToString};
use core::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use miniscript::descriptor::{ShInner, WshInner}; use miniscript::descriptor::{ShInner, WshInner};
@ -110,8 +109,8 @@ impl FullyNodedExport {
/// ///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field /// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`. /// returned will be `0`.
pub fn export_wallet<D>( pub fn export_wallet(
wallet: &Wallet<D>, wallet: &Wallet,
label: &str, label: &str,
include_blockheight: bool, include_blockheight: bool,
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
@ -225,7 +224,7 @@ mod test {
descriptor: &str, descriptor: &str,
change_descriptor: Option<&str>, change_descriptor: Option<&str>,
network: Network, network: Network,
) -> Wallet<()> { ) -> Wallet {
let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
let transaction = Transaction { let transaction = Transaction {
input: vec![], input: vec![],

View File

@ -82,12 +82,12 @@ const COINBASE_MATURITY: u32 = 100;
/// ///
/// [`signer`]: crate::signer /// [`signer`]: crate::signer
#[derive(Debug)] #[derive(Debug)]
pub struct Wallet<D = ()> { pub struct Wallet {
signers: Arc<SignersContainer>, signers: Arc<SignersContainer>,
change_signers: Arc<SignersContainer>, change_signers: Arc<SignersContainer>,
chain: LocalChain, chain: LocalChain,
indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>, indexed_graph: IndexedTxGraph<ConfirmationTimeHeightAnchor, KeychainTxOutIndex<KeychainKind>>,
persist: Persist<D, ChangeSet>, persist: Persist<ChangeSet>,
network: Network, network: Network,
secp: SecpCtx, secp: SecpCtx,
} }
@ -236,7 +236,7 @@ impl Wallet {
Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e { Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"), NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
NewError::Descriptor(e) => e, NewError::Descriptor(e) => e,
NewError::Write(_) => unreachable!("mock-write must always succeed"), NewError::Persist(_) => unreachable!("mock-write must always succeed"),
}) })
} }
@ -251,15 +251,12 @@ impl Wallet {
.map_err(|e| match e { .map_err(|e| match e {
NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"), NewError::NonEmptyDatabase => unreachable!("mock-database cannot have data"),
NewError::Descriptor(e) => e, NewError::Descriptor(e) => e,
NewError::Write(_) => unreachable!("mock-write must always succeed"), NewError::Persist(_) => unreachable!("mock-write must always succeed"),
}) })
} }
} }
impl<D> Wallet<D> impl Wallet {
where
D: PersistBackend<ChangeSet, WriteError = core::convert::Infallible>,
{
/// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for /// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for
/// available address index selection strategies. If none of the keys in the descriptor are derivable /// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`]. /// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
@ -296,19 +293,16 @@ where
/// [`new`]: Wallet::new /// [`new`]: Wallet::new
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash /// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
#[derive(Debug)] #[derive(Debug)]
pub enum NewError<W> { pub enum NewError {
/// Database already has data. /// Database already has data.
NonEmptyDatabase, NonEmptyDatabase,
/// There was problem with the passed-in descriptor(s). /// There was problem with the passed-in descriptor(s).
Descriptor(crate::descriptor::DescriptorError), Descriptor(crate::descriptor::DescriptorError),
/// We were unable to write the wallet's data to the persistence backend. /// We were unable to write the wallet's data to the persistence backend.
Write(W), Persist(anyhow::Error),
} }
impl<W> fmt::Display for NewError<W> impl fmt::Display for NewError {
where
W: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
NewError::NonEmptyDatabase => write!( NewError::NonEmptyDatabase => write!(
@ -316,13 +310,13 @@ where
"database already has data - use `load` or `new_or_load` methods instead" "database already has data - use `load` or `new_or_load` methods instead"
), ),
NewError::Descriptor(e) => e.fmt(f), NewError::Descriptor(e) => e.fmt(f),
NewError::Write(e) => e.fmt(f), NewError::Persist(e) => e.fmt(f),
} }
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<W> std::error::Error for NewError<W> where W: core::fmt::Display + core::fmt::Debug {} impl std::error::Error for NewError {}
/// The error type when loading a [`Wallet`] from persistence. /// The error type when loading a [`Wallet`] from persistence.
/// ///
@ -330,11 +324,11 @@ impl<W> std::error::Error for NewError<W> where W: core::fmt::Display + core::fm
/// ///
/// [`load`]: Wallet::load /// [`load`]: Wallet::load
#[derive(Debug)] #[derive(Debug)]
pub enum LoadError<L> { pub enum LoadError {
/// There was a problem with the passed-in descriptor(s). /// There was a problem with the passed-in descriptor(s).
Descriptor(crate::descriptor::DescriptorError), Descriptor(crate::descriptor::DescriptorError),
/// Loading data from the persistence backend failed. /// Loading data from the persistence backend failed.
Load(L), Persist(anyhow::Error),
/// Wallet not initialized, persistence backend is empty. /// Wallet not initialized, persistence backend is empty.
NotInitialized, NotInitialized,
/// Data loaded from persistence is missing network type. /// Data loaded from persistence is missing network type.
@ -343,14 +337,11 @@ pub enum LoadError<L> {
MissingGenesis, MissingGenesis,
} }
impl<L> fmt::Display for LoadError<L> impl fmt::Display for LoadError {
where
L: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
LoadError::Descriptor(e) => e.fmt(f), LoadError::Descriptor(e) => e.fmt(f),
LoadError::Load(e) => e.fmt(f), LoadError::Persist(e) => e.fmt(f),
LoadError::NotInitialized => { LoadError::NotInitialized => {
write!(f, "wallet is not initialized, persistence backend is empty") write!(f, "wallet is not initialized, persistence backend is empty")
} }
@ -361,7 +352,7 @@ where
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<L> std::error::Error for LoadError<L> where L: core::fmt::Display + core::fmt::Debug {} impl std::error::Error for LoadError {}
/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent. /// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
/// ///
@ -370,13 +361,11 @@ impl<L> std::error::Error for LoadError<L> where L: core::fmt::Display + core::f
/// [`new_or_load`]: Wallet::new_or_load /// [`new_or_load`]: Wallet::new_or_load
/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash /// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
#[derive(Debug)] #[derive(Debug)]
pub enum NewOrLoadError<W, L> { pub enum NewOrLoadError {
/// There is a problem with the passed-in descriptor. /// There is a problem with the passed-in descriptor.
Descriptor(crate::descriptor::DescriptorError), Descriptor(crate::descriptor::DescriptorError),
/// Writing to the persistence backend failed. /// Either writing to or loading from the persistence backend failed.
Write(W), Persist(anyhow::Error),
/// Loading from the persistence backend failed.
Load(L),
/// Wallet is not initialized, persistence backend is empty. /// Wallet is not initialized, persistence backend is empty.
NotInitialized, NotInitialized,
/// The loaded genesis hash does not match what was provided. /// The loaded genesis hash does not match what was provided.
@ -395,16 +384,15 @@ pub enum NewOrLoadError<W, L> {
}, },
} }
impl<W, L> fmt::Display for NewOrLoadError<W, L> impl fmt::Display for NewOrLoadError {
where
W: fmt::Display,
L: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
NewOrLoadError::Descriptor(e) => e.fmt(f), NewOrLoadError::Descriptor(e) => e.fmt(f),
NewOrLoadError::Write(e) => write!(f, "failed to write to persistence: {}", e), NewOrLoadError::Persist(e) => write!(
NewOrLoadError::Load(e) => write!(f, "failed to load from persistence: {}", e), f,
"failed to either write to or load from persistence, {}",
e
),
NewOrLoadError::NotInitialized => { NewOrLoadError::NotInitialized => {
write!(f, "wallet is not initialized, persistence backend is empty") write!(f, "wallet is not initialized, persistence backend is empty")
} }
@ -419,12 +407,7 @@ where
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<W, L> std::error::Error for NewOrLoadError<W, L> impl std::error::Error for NewOrLoadError {}
where
W: core::fmt::Display + core::fmt::Debug,
L: core::fmt::Display + core::fmt::Debug,
{
}
/// An error that may occur when inserting a transaction into [`Wallet`]. /// An error that may occur when inserting a transaction into [`Wallet`].
#[derive(Debug)] #[derive(Debug)]
@ -488,17 +471,14 @@ impl fmt::Display for ApplyBlockError {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for ApplyBlockError {} impl std::error::Error for ApplyBlockError {}
impl<D> Wallet<D> { impl Wallet {
/// Initialize an empty [`Wallet`]. /// Initialize an empty [`Wallet`].
pub fn new<E: IntoWalletDescriptor>( pub fn new<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
db: D, db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
) -> Result<Self, NewError<D::WriteError>> ) -> Result<Self, NewError> {
where
D: PersistBackend<ChangeSet>,
{
let genesis_hash = genesis_block(network).block_hash(); let genesis_hash = genesis_block(network).block_hash();
Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash) Self::new_with_genesis_hash(descriptor, change_descriptor, db, network, genesis_hash)
} }
@ -510,13 +490,10 @@ impl<D> Wallet<D> {
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>( pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
mut db: D, mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
genesis_hash: BlockHash, genesis_hash: BlockHash,
) -> Result<Self, NewError<D::WriteError>> ) -> Result<Self, NewError> {
where
D: PersistBackend<ChangeSet>,
{
if let Ok(changeset) = db.load_from_persistence() { if let Ok(changeset) = db.load_from_persistence() {
if changeset.is_some() { if changeset.is_some() {
return Err(NewError::NonEmptyDatabase); return Err(NewError::NonEmptyDatabase);
@ -538,7 +515,7 @@ impl<D> Wallet<D> {
indexed_tx_graph: indexed_graph.initial_changeset(), indexed_tx_graph: indexed_graph.initial_changeset(),
network: Some(network), network: Some(network),
}); });
persist.commit().map_err(NewError::Write)?; persist.commit().map_err(NewError::Persist)?;
Ok(Wallet { Ok(Wallet {
signers, signers,
@ -555,14 +532,11 @@ impl<D> Wallet<D> {
pub fn load<E: IntoWalletDescriptor>( pub fn load<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
mut db: D, mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
) -> Result<Self, LoadError<D::LoadError>> ) -> Result<Self, LoadError> {
where
D: PersistBackend<ChangeSet>,
{
let changeset = db let changeset = db
.load_from_persistence() .load_from_persistence()
.map_err(LoadError::Load)? .map_err(LoadError::Persist)?
.ok_or(LoadError::NotInitialized)?; .ok_or(LoadError::NotInitialized)?;
Self::load_from_changeset(descriptor, change_descriptor, db, changeset) Self::load_from_changeset(descriptor, change_descriptor, db, changeset)
} }
@ -570,12 +544,9 @@ impl<D> Wallet<D> {
fn load_from_changeset<E: IntoWalletDescriptor>( fn load_from_changeset<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
db: D, db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
changeset: ChangeSet, changeset: ChangeSet,
) -> Result<Self, LoadError<D::LoadError>> ) -> Result<Self, LoadError> {
where
D: PersistBackend<ChangeSet>,
{
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let network = changeset.network.ok_or(LoadError::MissingNetwork)?; let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
let chain = let chain =
@ -608,12 +579,9 @@ impl<D> Wallet<D> {
pub fn new_or_load<E: IntoWalletDescriptor>( pub fn new_or_load<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
db: D, db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
) -> Result<Self, NewOrLoadError<D::WriteError, D::LoadError>> ) -> Result<Self, NewOrLoadError> {
where
D: PersistBackend<ChangeSet>,
{
let genesis_hash = genesis_block(network).block_hash(); let genesis_hash = genesis_block(network).block_hash();
Self::new_or_load_with_genesis_hash( Self::new_or_load_with_genesis_hash(
descriptor, descriptor,
@ -633,21 +601,20 @@ impl<D> Wallet<D> {
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>( pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: Option<E>,
mut db: D, mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
genesis_hash: BlockHash, genesis_hash: BlockHash,
) -> Result<Self, NewOrLoadError<D::WriteError, D::LoadError>> ) -> Result<Self, NewOrLoadError> {
where let changeset = db
D: PersistBackend<ChangeSet>, .load_from_persistence()
{ .map_err(NewOrLoadError::Persist)?;
let changeset = db.load_from_persistence().map_err(NewOrLoadError::Load)?;
match changeset { match changeset {
Some(changeset) => { Some(changeset) => {
let wallet = let wallet =
Self::load_from_changeset(descriptor, change_descriptor, db, changeset) Self::load_from_changeset(descriptor, change_descriptor, db, changeset)
.map_err(|e| match e { .map_err(|e| match e {
LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e), LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
LoadError::Load(e) => NewOrLoadError::Load(e), LoadError::Persist(e) => NewOrLoadError::Persist(e),
LoadError::NotInitialized => NewOrLoadError::NotInitialized, LoadError::NotInitialized => NewOrLoadError::NotInitialized,
LoadError::MissingNetwork => { LoadError::MissingNetwork => {
NewOrLoadError::LoadedNetworkDoesNotMatch { NewOrLoadError::LoadedNetworkDoesNotMatch {
@ -688,7 +655,7 @@ impl<D> Wallet<D> {
unreachable!("database is already checked to have no data") unreachable!("database is already checked to have no data")
} }
NewError::Descriptor(e) => NewOrLoadError::Descriptor(e), NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
NewError::Write(e) => NewOrLoadError::Write(e), NewError::Persist(e) => NewOrLoadError::Persist(e),
}), }),
} }
} }
@ -714,13 +681,7 @@ impl<D> Wallet<D> {
/// ///
/// This panics when the caller requests for an address of derivation index greater than the /// This panics when the caller requests for an address of derivation index greater than the
/// BIP32 max index. /// BIP32 max index.
pub fn try_get_address( pub fn try_get_address(&mut self, address_index: AddressIndex) -> anyhow::Result<AddressInfo> {
&mut self,
address_index: AddressIndex,
) -> Result<AddressInfo, D::WriteError>
where
D: PersistBackend<ChangeSet>,
{
self._get_address(KeychainKind::External, address_index) self._get_address(KeychainKind::External, address_index)
} }
@ -742,10 +703,7 @@ impl<D> Wallet<D> {
pub fn try_get_internal_address( pub fn try_get_internal_address(
&mut self, &mut self,
address_index: AddressIndex, address_index: AddressIndex,
) -> Result<AddressInfo, D::WriteError> ) -> anyhow::Result<AddressInfo> {
where
D: PersistBackend<ChangeSet>,
{
self._get_address(KeychainKind::Internal, address_index) self._get_address(KeychainKind::Internal, address_index)
} }
@ -770,10 +728,7 @@ impl<D> Wallet<D> {
&mut self, &mut self,
keychain: KeychainKind, keychain: KeychainKind,
address_index: AddressIndex, address_index: AddressIndex,
) -> Result<AddressInfo, D::WriteError> ) -> anyhow::Result<AddressInfo> {
where
D: PersistBackend<ChangeSet>,
{
let keychain = self.map_keychain(keychain); let keychain = self.map_keychain(keychain);
let txout_index = &mut self.indexed_graph.index; let txout_index = &mut self.indexed_graph.index;
let (index, spk, changeset) = match address_index { let (index, spk, changeset) = match address_index {
@ -918,10 +873,7 @@ impl<D> Wallet<D> {
/// [`list_unspent`]: Self::list_unspent /// [`list_unspent`]: Self::list_unspent
/// [`list_output`]: Self::list_output /// [`list_output`]: Self::list_output
/// [`commit`]: Self::commit /// [`commit`]: Self::commit
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) {
where
D: PersistBackend<ChangeSet>,
{
let additions = self.indexed_graph.insert_txout(outpoint, txout); let additions = self.indexed_graph.insert_txout(outpoint, txout);
self.persist.stage(ChangeSet::from(additions)); self.persist.stage(ChangeSet::from(additions));
} }
@ -938,7 +890,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Txid; /// # use bitcoin::Txid;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
/// let fee = wallet.calculate_fee(&tx).expect("fee"); /// let fee = wallet.calculate_fee(&tx).expect("fee");
@ -947,7 +899,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Psbt; /// # use bitcoin::Psbt;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let mut psbt: Psbt = todo!(); /// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx"); /// let tx = &psbt.clone().extract_tx().expect("tx");
/// let fee = wallet.calculate_fee(tx).expect("fee"); /// let fee = wallet.calculate_fee(tx).expect("fee");
@ -969,7 +921,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Txid; /// # use bitcoin::Txid;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
/// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate");
@ -978,7 +930,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Psbt; /// # use bitcoin::Psbt;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let mut psbt: Psbt = todo!(); /// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx"); /// let tx = &psbt.clone().extract_tx().expect("tx");
/// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate");
@ -1000,7 +952,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Txid; /// # use bitcoin::Txid;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let txid:Txid = todo!(); /// # let txid:Txid = todo!();
/// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx; /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx;
/// let (sent, received) = wallet.sent_and_received(&tx); /// let (sent, received) = wallet.sent_and_received(&tx);
@ -1009,7 +961,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// # use bitcoin::Psbt; /// # use bitcoin::Psbt;
/// # use bdk::Wallet; /// # use bdk::Wallet;
/// # let mut wallet: Wallet<()> = todo!(); /// # let mut wallet: Wallet = todo!();
/// # let mut psbt: Psbt = todo!(); /// # let mut psbt: Psbt = todo!();
/// let tx = &psbt.clone().extract_tx().expect("tx"); /// let tx = &psbt.clone().extract_tx().expect("tx");
/// let (sent, received) = wallet.sent_and_received(tx); /// let (sent, received) = wallet.sent_and_received(tx);
@ -1031,7 +983,7 @@ impl<D> Wallet<D> {
/// ```rust, no_run /// ```rust, no_run
/// use bdk::{chain::ChainPosition, Wallet}; /// use bdk::{chain::ChainPosition, Wallet};
/// use bdk_chain::Anchor; /// use bdk_chain::Anchor;
/// # let wallet: Wallet<()> = todo!(); /// # let wallet: Wallet = todo!();
/// # let my_txid: bitcoin::Txid = todo!(); /// # let my_txid: bitcoin::Txid = todo!();
/// ///
/// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); /// let canonical_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist");
@ -1087,10 +1039,7 @@ impl<D> Wallet<D> {
pub fn insert_checkpoint( pub fn insert_checkpoint(
&mut self, &mut self,
block_id: BlockId, block_id: BlockId,
) -> Result<bool, local_chain::AlterCheckPointError> ) -> Result<bool, local_chain::AlterCheckPointError> {
where
D: PersistBackend<ChangeSet>,
{
let changeset = self.chain.insert_block(block_id)?; let changeset = self.chain.insert_block(block_id)?;
let changed = !changeset.is_empty(); let changed = !changeset.is_empty();
self.persist.stage(changeset.into()); self.persist.stage(changeset.into());
@ -1118,10 +1067,7 @@ impl<D> Wallet<D> {
&mut self, &mut self,
tx: Transaction, tx: Transaction,
position: ConfirmationTime, position: ConfirmationTime,
) -> Result<bool, InsertTxError> ) -> Result<bool, InsertTxError> {
where
D: PersistBackend<ChangeSet>,
{
let (anchor, last_seen) = match position { let (anchor, last_seen) = match position {
ConfirmationTime::Confirmed { height, time } => { ConfirmationTime::Confirmed { height, time } => {
// anchor tx to checkpoint with lowest height that is >= position's height // anchor tx to checkpoint with lowest height that is >= position's height
@ -1248,7 +1194,7 @@ impl<D> Wallet<D> {
/// ``` /// ```
/// ///
/// [`TxBuilder`]: crate::TxBuilder /// [`TxBuilder`]: crate::TxBuilder
pub fn build_tx(&mut self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> { pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder { TxBuilder {
wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)), wallet: alloc::rc::Rc::new(core::cell::RefCell::new(self)),
params: TxParams::default(), params: TxParams::default(),
@ -1261,10 +1207,7 @@ impl<D> Wallet<D> {
&mut self, &mut self,
coin_selection: Cs, coin_selection: Cs,
params: TxParams, params: TxParams,
) -> Result<Psbt, CreateTxError<D::WriteError>> ) -> Result<Psbt, CreateTxError> {
where
D: PersistBackend<ChangeSet>,
{
let external_descriptor = self let external_descriptor = self
.indexed_graph .indexed_graph
.index .index
@ -1283,7 +1226,7 @@ impl<D> Wallet<D> {
let internal_policy = internal_descriptor let internal_policy = internal_descriptor
.as_ref() .as_ref()
.map(|desc| { .map(|desc| {
Ok::<_, CreateTxError<D::WriteError>>( Ok::<_, CreateTxError>(
desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)? desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
.unwrap(), .unwrap(),
) )
@ -1320,7 +1263,7 @@ impl<D> Wallet<D> {
)?; )?;
let internal_requirements = internal_policy let internal_requirements = internal_policy
.map(|policy| { .map(|policy| {
Ok::<_, CreateTxError<D::WriteError>>( Ok::<_, CreateTxError>(
policy.get_condition( policy.get_condition(
params params
.internal_policy_path .internal_policy_path
@ -1647,7 +1590,7 @@ impl<D> Wallet<D> {
pub fn build_fee_bump( pub fn build_fee_bump(
&mut self, &mut self,
txid: Txid, txid: Txid,
) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, BuildFeeBumpError> { ) -> Result<TxBuilder<'_, DefaultCoinSelectionAlgorithm, BumpFee>, BuildFeeBumpError> {
let graph = self.indexed_graph.graph(); let graph = self.indexed_graph.graph();
let txout_index = &self.indexed_graph.index; let txout_index = &self.indexed_graph.index;
let chain_tip = self.chain.tip().block_id(); let chain_tip = self.chain.tip().block_id();
@ -2158,10 +2101,7 @@ impl<D> Wallet<D> {
tx: Transaction, tx: Transaction,
selected: Vec<Utxo>, selected: Vec<Utxo>,
params: TxParams, params: TxParams,
) -> Result<Psbt, CreateTxError<D::WriteError>> ) -> Result<Psbt, CreateTxError> {
where
D: PersistBackend<ChangeSet>,
{
let mut psbt = Psbt::from_unsigned_tx(tx)?; let mut psbt = Psbt::from_unsigned_tx(tx)?;
if params.add_global_xpubs { if params.add_global_xpubs {
@ -2242,10 +2182,7 @@ impl<D> Wallet<D> {
utxo: LocalOutput, utxo: LocalOutput,
sighash_type: Option<psbt::PsbtSighashType>, sighash_type: Option<psbt::PsbtSighashType>,
only_witness_utxo: bool, only_witness_utxo: bool,
) -> Result<psbt::Input, CreateTxError<D::WriteError>> ) -> Result<psbt::Input, CreateTxError> {
where
D: PersistBackend<ChangeSet>,
{
// Try to find the prev_script in our db to figure out if this is internal or external, // Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index // and the derivation index
let (keychain, child) = self let (keychain, child) = self
@ -2335,10 +2272,7 @@ impl<D> Wallet<D> {
/// transactions related to your wallet into it. /// transactions related to your wallet into it.
/// ///
/// [`commit`]: Self::commit /// [`commit`]: Self::commit
pub fn apply_update(&mut self, update: Update) -> Result<(), CannotConnectError> pub fn apply_update(&mut self, update: Update) -> Result<(), CannotConnectError> {
where
D: PersistBackend<ChangeSet>,
{
let mut changeset = match update.chain { let mut changeset = match update.chain {
Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?), Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?),
None => ChangeSet::default(), None => ChangeSet::default(),
@ -2354,7 +2288,6 @@ impl<D> Wallet<D> {
changeset.append(ChangeSet::from( changeset.append(ChangeSet::from(
self.indexed_graph.apply_update(update.graph), self.indexed_graph.apply_update(update.graph),
)); ));
self.persist.stage(changeset); self.persist.stage(changeset);
Ok(()) Ok(())
} }
@ -2365,20 +2298,14 @@ impl<D> Wallet<D> {
/// This returns whether the `update` resulted in any changes. /// This returns whether the `update` resulted in any changes.
/// ///
/// [`staged`]: Self::staged /// [`staged`]: Self::staged
pub fn commit(&mut self) -> Result<bool, D::WriteError> pub fn commit(&mut self) -> anyhow::Result<bool> {
where
D: PersistBackend<ChangeSet>,
{
self.persist.commit().map(|c| c.is_some()) self.persist.commit().map(|c| c.is_some())
} }
/// Returns the changes that will be committed with the next call to [`commit`]. /// Returns the changes that will be committed with the next call to [`commit`].
/// ///
/// [`commit`]: Self::commit /// [`commit`]: Self::commit
pub fn staged(&self) -> &ChangeSet pub fn staged(&self) -> &ChangeSet {
where
D: PersistBackend<ChangeSet>,
{
self.persist.staged() self.persist.staged()
} }
@ -2404,10 +2331,7 @@ impl<D> Wallet<D> {
/// with `prev_blockhash` and `height-1` as the `connected_to` parameter. /// with `prev_blockhash` and `height-1` as the `connected_to` parameter.
/// ///
/// [`apply_block_connected_to`]: Self::apply_block_connected_to /// [`apply_block_connected_to`]: Self::apply_block_connected_to
pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> {
where
D: PersistBackend<ChangeSet>,
{
let connected_to = match height.checked_sub(1) { let connected_to = match height.checked_sub(1) {
Some(prev_height) => BlockId { Some(prev_height) => BlockId {
height: prev_height, height: prev_height,
@ -2438,10 +2362,7 @@ impl<D> Wallet<D> {
block: &Block, block: &Block,
height: u32, height: u32,
connected_to: BlockId, connected_to: BlockId,
) -> Result<(), ApplyHeaderError> ) -> Result<(), ApplyHeaderError> {
where
D: PersistBackend<ChangeSet>,
{
let mut changeset = ChangeSet::default(); let mut changeset = ChangeSet::default();
changeset.append( changeset.append(
self.chain self.chain
@ -2468,9 +2389,7 @@ impl<D> Wallet<D> {
pub fn apply_unconfirmed_txs<'t>( pub fn apply_unconfirmed_txs<'t>(
&mut self, &mut self,
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>, unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
) where ) {
D: PersistBackend<ChangeSet>,
{
let indexed_graph_changeset = self let indexed_graph_changeset = self
.indexed_graph .indexed_graph
.batch_insert_relevant_unconfirmed(unconfirmed_txs); .batch_insert_relevant_unconfirmed(unconfirmed_txs);
@ -2478,7 +2397,7 @@ impl<D> Wallet<D> {
} }
} }
impl<D> AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor>> for Wallet<D> { impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor>> for Wallet {
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor> { fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationTimeHeightAnchor> {
self.indexed_graph.graph() self.indexed_graph.graph()
} }

View File

@ -45,13 +45,12 @@ use core::cell::RefCell;
use core::fmt; use core::fmt;
use core::marker::PhantomData; use core::marker::PhantomData;
use bdk_chain::PersistBackend;
use bitcoin::psbt::{self, Psbt}; use bitcoin::psbt::{self, Psbt};
use bitcoin::script::PushBytes; use bitcoin::script::PushBytes;
use bitcoin::{absolute, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, Txid}; use bitcoin::{absolute, FeeRate, OutPoint, ScriptBuf, Sequence, Transaction, Txid};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use super::{ChangeSet, CreateTxError, Wallet}; use super::{CreateTxError, Wallet};
use crate::collections::{BTreeMap, HashSet}; use crate::collections::{BTreeMap, HashSet};
use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo}; use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo};
@ -124,8 +123,8 @@ impl TxBuilderContext for BumpFee {}
/// [`finish`]: Self::finish /// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection /// [`coin_selection`]: Self::coin_selection
#[derive(Debug)] #[derive(Debug)]
pub struct TxBuilder<'a, D, Cs, Ctx> { pub struct TxBuilder<'a, Cs, Ctx> {
pub(crate) wallet: Rc<RefCell<&'a mut Wallet<D>>>, pub(crate) wallet: Rc<RefCell<&'a mut Wallet>>,
pub(crate) params: TxParams, pub(crate) params: TxParams,
pub(crate) coin_selection: Cs, pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>, pub(crate) phantom: PhantomData<Ctx>,
@ -176,7 +175,7 @@ impl Default for FeePolicy {
} }
} }
impl<'a, D, Cs: Clone, Ctx> Clone for TxBuilder<'a, D, Cs, Ctx> { impl<'a, Cs: Clone, Ctx> Clone for TxBuilder<'a, Cs, Ctx> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
TxBuilder { TxBuilder {
wallet: self.wallet.clone(), wallet: self.wallet.clone(),
@ -188,7 +187,7 @@ impl<'a, D, Cs: Clone, Ctx> Clone for TxBuilder<'a, D, Cs, Ctx> {
} }
// methods supported by both contexts, for any CoinSelectionAlgorithm // methods supported by both contexts, for any CoinSelectionAlgorithm
impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> { impl<'a, Cs, Ctx> TxBuilder<'a, Cs, Ctx> {
/// Set a custom fee rate. /// Set a custom fee rate.
/// ///
/// This method sets the mining fee paid by the transaction as a rate on its size. /// This method sets the mining fee paid by the transaction as a rate on its size.
@ -560,7 +559,7 @@ impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
pub fn coin_selection<P: CoinSelectionAlgorithm>( pub fn coin_selection<P: CoinSelectionAlgorithm>(
self, self,
coin_selection: P, coin_selection: P,
) -> TxBuilder<'a, D, P, Ctx> { ) -> TxBuilder<'a, P, Ctx> {
TxBuilder { TxBuilder {
wallet: self.wallet, wallet: self.wallet,
params: self.params, params: self.params,
@ -615,16 +614,13 @@ impl<'a, D, Cs, Ctx> TxBuilder<'a, D, Cs, Ctx> {
} }
} }
impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx> TxBuilder<'a, D, Cs, Ctx> { impl<'a, Cs: CoinSelectionAlgorithm, Ctx> TxBuilder<'a, Cs, Ctx> {
/// Finish building the transaction. /// Finish building the transaction.
/// ///
/// Returns a new [`Psbt`] per [`BIP174`]. /// Returns a new [`Psbt`] per [`BIP174`].
/// ///
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
pub fn finish(self) -> Result<Psbt, CreateTxError<D::WriteError>> pub fn finish(self) -> Result<Psbt, CreateTxError> {
where
D: PersistBackend<ChangeSet>,
{
self.wallet self.wallet
.borrow_mut() .borrow_mut()
.create_tx(self.coin_selection, self.params) .create_tx(self.coin_selection, self.params)
@ -715,7 +711,7 @@ impl fmt::Display for AllowShrinkingError {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for AllowShrinkingError {} impl std::error::Error for AllowShrinkingError {}
impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { impl<'a, Cs: CoinSelectionAlgorithm> TxBuilder<'a, Cs, CreateTx> {
/// Replace the recipients already added with a new list /// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, u64)>) -> &mut Self { pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, u64)>) -> &mut Self {
self.params.recipients = recipients; self.params.recipients = recipients;
@ -793,7 +789,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> {
} }
// methods supported only by bump_fee // methods supported only by bump_fee
impl<'a, D> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> { impl<'a> TxBuilder<'a, DefaultCoinSelectionAlgorithm, BumpFee> {
/// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet /// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead. /// will attempt to find a change output to shrink instead.

View File

@ -14,6 +14,7 @@ readme = "README.md"
[dependencies] [dependencies]
# For no-std, remember to enable the bitcoin/no-std feature # For no-std, remember to enable the bitcoin/no-std feature
anyhow = { version = "1", default-features = false }
bitcoin = { version = "0.31.0", default-features = false } bitcoin = { version = "0.31.0", default-features = false }
serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] } serde_crate = { package = "serde", version = "1", optional = true, features = ["derive", "rc"] }

View File

@ -1,26 +1,32 @@
use core::convert::Infallible;
use crate::Append; use crate::Append;
use alloc::boxed::Box;
use core::fmt;
/// `Persist` wraps a [`PersistBackend`] (`B`) to create a convenient staging area for changes (`C`) /// `Persist` wraps a [`PersistBackend`] to create a convenient staging area for changes (`C`)
/// before they are persisted. /// before they are persisted.
/// ///
/// Not all changes to the in-memory representation needs to be written to disk right away, so /// Not all changes to the in-memory representation needs to be written to disk right away, so
/// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used /// [`Persist::stage`] can be used to *stage* changes first and then [`Persist::commit`] can be used
/// to write changes to disk. /// to write changes to disk.
#[derive(Debug)] pub struct Persist<C> {
pub struct Persist<B, C> { backend: Box<dyn PersistBackend<C> + Send + Sync>,
backend: B,
stage: C, stage: C,
} }
impl<B, C> Persist<B, C> impl<C: fmt::Debug> fmt::Debug for Persist<C> {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(fmt, "{:?}", self.stage)?;
Ok(())
}
}
impl<C> Persist<C>
where where
B: PersistBackend<C>,
C: Default + Append, C: Default + Append,
{ {
/// Create a new [`Persist`] from [`PersistBackend`]. /// Create a new [`Persist`] from [`PersistBackend`].
pub fn new(backend: B) -> Self { pub fn new(backend: impl PersistBackend<C> + Send + Sync + 'static) -> Self {
let backend = Box::new(backend);
Self { Self {
backend, backend,
stage: Default::default(), stage: Default::default(),
@ -46,7 +52,7 @@ where
/// # Error /// # Error
/// ///
/// Returns a backend-defined error if this fails. /// Returns a backend-defined error if this fails.
pub fn commit(&mut self) -> Result<Option<C>, B::WriteError> { pub fn commit(&mut self) -> anyhow::Result<Option<C>> {
if self.stage.is_empty() { if self.stage.is_empty() {
return Ok(None); return Ok(None);
} }
@ -63,7 +69,7 @@ where
/// ///
/// [`stage`]: Self::stage /// [`stage`]: Self::stage
/// [`commit`]: Self::commit /// [`commit`]: Self::commit
pub fn stage_and_commit(&mut self, changeset: C) -> Result<Option<C>, B::WriteError> { pub fn stage_and_commit(&mut self, changeset: C) -> anyhow::Result<Option<C>> {
self.stage(changeset); self.stage(changeset);
self.commit() self.commit()
} }
@ -74,12 +80,6 @@ where
/// `C` represents the changeset; a datatype that records changes made to in-memory data structures /// `C` represents the changeset; a datatype that records changes made to in-memory data structures
/// that are to be persisted, or retrieved from persistence. /// that are to be persisted, or retrieved from persistence.
pub trait PersistBackend<C> { pub trait PersistBackend<C> {
/// The error the backend returns when it fails to write.
type WriteError: core::fmt::Debug;
/// The error the backend returns when it fails to load changesets `C`.
type LoadError: core::fmt::Debug;
/// Writes a changeset to the persistence backend. /// Writes a changeset to the persistence backend.
/// ///
/// It is up to the backend what it does with this. It could store every changeset in a list or /// It is up to the backend what it does with this. It could store every changeset in a list or
@ -88,22 +88,18 @@ pub trait PersistBackend<C> {
/// changesets had been applied sequentially. /// changesets had been applied sequentially.
/// ///
/// [`load_from_persistence`]: Self::load_from_persistence /// [`load_from_persistence`]: Self::load_from_persistence
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError>; fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()>;
/// Return the aggregate changeset `C` from persistence. /// Return the aggregate changeset `C` from persistence.
fn load_from_persistence(&mut self) -> Result<Option<C>, Self::LoadError>; fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>>;
} }
impl<C> PersistBackend<C> for () { impl<C> PersistBackend<C> for () {
type WriteError = Infallible; fn write_changes(&mut self, _changeset: &C) -> anyhow::Result<()> {
type LoadError = Infallible;
fn write_changes(&mut self, _changeset: &C) -> Result<(), Self::WriteError> {
Ok(()) Ok(())
} }
fn load_from_persistence(&mut self) -> Result<Option<C>, Self::LoadError> { fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
Ok(None) Ok(None)
} }
} }

View File

@ -11,6 +11,7 @@ authors = ["Bitcoin Dev Kit Developers"]
readme = "README.md" readme = "README.md"
[dependencies] [dependencies]
anyhow = { version = "1", default-features = false }
bdk_chain = { path = "../chain", version = "0.12.0", features = [ "serde", "miniscript" ] } bdk_chain = { path = "../chain", version = "0.12.0", features = [ "serde", "miniscript" ] }
bincode = { version = "1" } bincode = { version = "1" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@ -1,21 +1,23 @@
use crate::{bincode_options, EntryIter, FileError, IterError};
use anyhow::anyhow;
use bdk_chain::{Append, PersistBackend};
use bincode::Options;
use std::{ use std::{
fmt::Debug, fmt::{self, Debug},
fs::{File, OpenOptions}, fs::{File, OpenOptions},
io::{self, Read, Seek, Write}, io::{self, Read, Seek, Write},
marker::PhantomData, marker::PhantomData,
path::Path, path::Path,
}; };
use bdk_chain::{Append, PersistBackend};
use bincode::Options;
use crate::{bincode_options, EntryIter, FileError, IterError};
/// Persists an append-only list of changesets (`C`) to a single file. /// Persists an append-only list of changesets (`C`) to a single file.
/// ///
/// The changesets are the results of altering a tracker implementation (`T`). /// The changesets are the results of altering a tracker implementation (`T`).
#[derive(Debug)] #[derive(Debug)]
pub struct Store<C> { pub struct Store<C>
where
C: Sync + Send,
{
magic_len: usize, magic_len: usize,
db_file: File, db_file: File,
marker: PhantomData<C>, marker: PhantomData<C>,
@ -23,24 +25,30 @@ pub struct Store<C> {
impl<C> PersistBackend<C> for Store<C> impl<C> PersistBackend<C> for Store<C>
where where
C: Append + serde::Serialize + serde::de::DeserializeOwned, C: Append
+ serde::Serialize
+ serde::de::DeserializeOwned
+ core::marker::Send
+ core::marker::Sync,
{ {
type WriteError = std::io::Error; fn write_changes(&mut self, changeset: &C) -> anyhow::Result<()> {
type LoadError = IterError;
fn write_changes(&mut self, changeset: &C) -> Result<(), Self::WriteError> {
self.append_changeset(changeset) self.append_changeset(changeset)
.map_err(|e| anyhow!(e).context("failed to write changes to persistence backend"))
} }
fn load_from_persistence(&mut self) -> Result<Option<C>, Self::LoadError> { fn load_from_persistence(&mut self) -> anyhow::Result<Option<C>> {
self.aggregate_changesets().map_err(|e| e.iter_error) self.aggregate_changesets()
.map_err(|e| anyhow!(e.iter_error).context("error loading from persistence backend"))
} }
} }
impl<C> Store<C> impl<C> Store<C>
where where
C: Append + serde::Serialize + serde::de::DeserializeOwned, C: Append
+ serde::Serialize
+ serde::de::DeserializeOwned
+ core::marker::Send
+ core::marker::Sync,
{ {
/// Create a new [`Store`] file in write-only mode; error if the file exists. /// Create a new [`Store`] file in write-only mode; error if the file exists.
/// ///
@ -182,7 +190,7 @@ where
bincode_options() bincode_options()
.serialize_into(&mut self.db_file, changeset) .serialize_into(&mut self.db_file, changeset)
.map_err(|e| match *e { .map_err(|e| match *e {
bincode::ErrorKind::Io(inner) => inner, bincode::ErrorKind::Io(error) => error,
unexpected_err => panic!("unexpected bincode error: {}", unexpected_err), unexpected_err => panic!("unexpected bincode error: {}", unexpected_err),
})?; })?;
@ -212,7 +220,7 @@ impl<C> std::fmt::Display for AggregateChangesetsError<C> {
} }
} }
impl<C: std::fmt::Debug> std::error::Error for AggregateChangesetsError<C> {} impl<C: fmt::Debug> std::error::Error for AggregateChangesetsError<C> {}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -31,7 +31,6 @@ pub type KeychainChangeSet<A> = (
local_chain::ChangeSet, local_chain::ChangeSet,
indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>>, indexed_tx_graph::ChangeSet<A, keychain::ChangeSet<Keychain>>,
); );
pub type Database<C> = Persist<Store<C>, C>;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
@ -440,7 +439,7 @@ pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDeri
pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>( pub fn handle_commands<CS: clap::Subcommand, S: clap::Args, A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>, graph: &Mutex<KeychainTxGraph<A>>,
db: &Mutex<Database<C>>, db: &Mutex<Persist<C>>,
chain: &Mutex<O>, chain: &Mutex<O>,
keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>, keymap: &BTreeMap<DescriptorPublicKey, DescriptorSecretKey>,
network: Network, network: Network,
@ -667,7 +666,7 @@ pub struct Init<CS: clap::Subcommand, S: clap::Args, C> {
/// Keychain-txout index. /// Keychain-txout index.
pub index: KeychainTxOutIndex<Keychain>, pub index: KeychainTxOutIndex<Keychain>,
/// Persistence backend. /// Persistence backend.
pub db: Mutex<Database<C>>, pub db: Mutex<Persist<C>>,
/// Initial changeset. /// Initial changeset.
pub init_changeset: C, pub init_changeset: C,
} }
@ -679,7 +678,13 @@ pub fn init<CS: clap::Subcommand, S: clap::Args, C>(
db_default_path: &str, db_default_path: &str,
) -> anyhow::Result<Init<CS, S, C>> ) -> anyhow::Result<Init<CS, S, C>>
where where
C: Default + Append + Serialize + DeserializeOwned, C: Default
+ Append
+ Serialize
+ DeserializeOwned
+ core::marker::Send
+ core::marker::Sync
+ 'static,
{ {
if std::env::var("BDK_DB_PATH").is_err() { if std::env::var("BDK_DB_PATH").is_err() {
std::env::set_var("BDK_DB_PATH", db_default_path); std::env::set_var("BDK_DB_PATH", db_default_path);
@ -715,7 +720,7 @@ where
args, args,
keymap, keymap,
index, index,
db: Mutex::new(Database::new(db_backend)), db: Mutex::new(Persist::new(db_backend)),
init_changeset, init_changeset,
}) })
} }