pub use bdk_chain::keychain::Balance;
use bdk_chain::{
indexed_tx_graph::{IndexedAdditions, IndexedTxGraph},
- keychain::{DerivationAdditions, KeychainTxOutIndex, LocalUpdate},
+ keychain::{KeychainTxOutIndex, LocalChangeSet, LocalUpdate},
local_chain::{self, LocalChain, UpdateNotConnectedError},
tx_graph::{CanonicalTx, TxGraph},
- Anchor, Append, BlockId, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, ObservedAs,
- Persist, PersistBackend,
+ Append, BlockId, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, ObservedAs, Persist,
+ PersistBackend,
};
use bitcoin::consensus::encode::serialize;
use bitcoin::secp256k1::Secp256k1;
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
pub type Update = LocalUpdate<KeychainKind, ConfirmationTimeAnchor>;
-/// The changeset produced internally by applying an update.
-#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
-#[serde(bound(
- deserialize = "A: Ord + serde::Deserialize<'de>, K: Ord + serde::Deserialize<'de>",
- serialize = "A: Ord + serde::Serialize, K: Ord + serde::Serialize"
-))]
-pub struct ChangeSet<K = KeychainKind, A = ConfirmationTimeAnchor> {
- pub chain_changeset: local_chain::ChangeSet,
- pub indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>,
-}
-
-impl<K, A> Default for ChangeSet<K, A> {
- fn default() -> Self {
- Self {
- chain_changeset: Default::default(),
- indexed_additions: Default::default(),
- }
- }
-}
-
-impl<K: Ord, A: Anchor> Append for ChangeSet<K, A> {
- fn append(&mut self, other: Self) {
- Append::append(&mut self.chain_changeset, other.chain_changeset);
- Append::append(&mut self.indexed_additions, other.indexed_additions);
- }
-
- fn is_empty(&self) -> bool {
- self.chain_changeset.is_empty() && self.indexed_additions.is_empty()
- }
-}
-
-impl<K, A> From<IndexedAdditions<A, DerivationAdditions<K>>> for ChangeSet<K, A> {
- fn from(indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>) -> Self {
- Self {
- indexed_additions,
- ..Default::default()
- }
- }
-}
-
-impl<K, A> From<DerivationAdditions<K>> for ChangeSet<K, A> {
- fn from(index_additions: DerivationAdditions<K>) -> Self {
- Self {
- indexed_additions: IndexedAdditions {
- index_additions,
- ..Default::default()
- },
- ..Default::default()
- }
- }
-}
-
-impl<K, A> From<local_chain::ChangeSet> for ChangeSet<K, A> {
- fn from(chain_changeset: local_chain::ChangeSet) -> Self {
- Self {
- chain_changeset,
- ..Default::default()
- }
- }
-}
-
+// /// The changeset produced internally by applying an update.
+pub(crate) type ChangeSet = LocalChangeSet<KeychainKind, ConfirmationTimeAnchor>;
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
let txout_index = &mut self.indexed_graph.index;
let (index, spk) = match address_index {
AddressIndex::New => {
- let ((index, spk), changeset) = txout_index.reveal_next_spk(&keychain);
+ let ((index, spk), index_additions) = txout_index.reveal_next_spk(&keychain);
let spk = spk.clone();
- self.persist.stage(changeset.into());
+ self.persist
+ .stage(ChangeSet::from(IndexedAdditions::from(index_additions)));
self.persist.commit().expect("TODO");
(index, spk)
}
Some(ref drain_recipient) => drain_recipient.clone(),
None => {
let change_keychain = self.map_keychain(KeychainKind::Internal);
- let ((index, spk), changeset) =
+ let ((index, spk), index_additions) =
self.indexed_graph.index.next_unused_spk(&change_keychain);
let spk = spk.clone();
self.indexed_graph.index.mark_used(&change_keychain, index);
- self.persist.stage(changeset.into());
+ self.persist
+ .stage(ChangeSet::from(IndexedAdditions::from(index_additions)));
self.persist.commit().expect("TODO");
spk
}
D: PersistBackend<ChangeSet>,
{
let mut changeset: ChangeSet = self.chain.apply_update(update.chain)?.into();
- let (_, derivation_additions) = self
+ let (_, index_additions) = self
.indexed_graph
.index
.reveal_to_target_multi(&update.keychain);
- changeset.append(derivation_additions.into());
+ changeset.append(ChangeSet::from(IndexedAdditions::from(index_additions)));
changeset.append(self.indexed_graph.apply_update(update.graph).into());
let changed = !changeset.is_empty();
use bitcoin::{OutPoint, Transaction, TxOut};
use crate::{
+ keychain::DerivationAdditions,
tx_graph::{Additions, TxGraph},
Anchor, Append,
};
}
}
+impl<A, K> From<DerivationAdditions<K>> for IndexedAdditions<A, DerivationAdditions<K>> {
+ fn from(index_additions: DerivationAdditions<K>) -> Self {
+ Self {
+ graph_additions: Default::default(),
+ index_additions,
+ }
+ }
+}
+
/// Represents a structure that can index transaction data.
pub trait Indexer {
/// The resultant "additions" when new transaction data is indexed.
use crate::{
chain_graph::{self, ChainGraph},
collections::BTreeMap,
- local_chain::LocalChain,
+ indexed_tx_graph::IndexedAdditions,
+ local_chain::{self, LocalChain},
sparse_chain::ChainPosition,
tx_graph::TxGraph,
- Append, ForEachTxOut,
+ Anchor, Append, ForEachTxOut,
};
#[cfg(feature = "miniscript")]
}
}
+/// A structure that records the corresponding changes as result of applying an [`LocalUpdate`].
+#[derive(Debug, Clone, PartialEq)]
+#[cfg_attr(
+ feature = "serde",
+ derive(serde::Deserialize, serde::Serialize),
+ serde(
+ crate = "serde_crate",
+ bound(
+ deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>",
+ serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize",
+ )
+ )
+)]
+pub struct LocalChangeSet<K, A> {
+ /// Changes to the [`LocalChain`].
+ pub chain_changeset: local_chain::ChangeSet,
+
+ /// Additions to [`IndexedTxGraph`].
+ ///
+ /// [`IndexedTxGraph`]: crate::indexed_tx_graph::IndexedTxGraph
+ pub indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>,
+}
+
+impl<K, A> Default for LocalChangeSet<K, A> {
+ fn default() -> Self {
+ Self {
+ chain_changeset: Default::default(),
+ indexed_additions: Default::default(),
+ }
+ }
+}
+
+impl<K: Ord, A: Anchor> Append for LocalChangeSet<K, A> {
+ fn append(&mut self, other: Self) {
+ Append::append(&mut self.chain_changeset, other.chain_changeset);
+ Append::append(&mut self.indexed_additions, other.indexed_additions);
+ }
+
+ fn is_empty(&self) -> bool {
+ self.chain_changeset.is_empty() && self.indexed_additions.is_empty()
+ }
+}
+
+impl<K, A> From<local_chain::ChangeSet> for LocalChangeSet<K, A> {
+ fn from(chain_changeset: local_chain::ChangeSet) -> Self {
+ Self {
+ chain_changeset,
+ ..Default::default()
+ }
+ }
+}
+
+impl<K, A> From<IndexedAdditions<A, DerivationAdditions<K>>> for LocalChangeSet<K, A> {
+ fn from(indexed_additions: IndexedAdditions<A, DerivationAdditions<K>>) -> Self {
+ Self {
+ indexed_additions,
+ ..Default::default()
+ }
+ }
+}
+
#[derive(Clone, Debug, PartialEq)]
/// An update that includes the last active indexes of each keychain.
pub struct KeychainScan<K, P> {
use clap::{Parser, Subcommand};
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
-pub type Database<'m, A, X> = Persist<Store<'m, ChangeSet<A, X>>, ChangeSet<A, X>>;
-
-#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
-#[serde(bound(
- deserialize = "A: Ord + serde::Deserialize<'de>, X: serde::Deserialize<'de>",
- serialize = "A: Ord + serde::Serialize, X: serde::Serialize",
-))]
-pub struct ChangeSet<A, X> {
- pub indexed_additions: IndexedAdditions<A, DerivationAdditions<Keychain>>,
- pub extension: X,
-}
-
-impl<A, X: Default> Default for ChangeSet<A, X> {
- fn default() -> Self {
- Self {
- indexed_additions: Default::default(),
- extension: Default::default(),
- }
- }
-}
-
-impl<A: Anchor, X: Append> Append for ChangeSet<A, X> {
- fn append(&mut self, other: Self) {
- Append::append(&mut self.indexed_additions, other.indexed_additions);
- Append::append(&mut self.extension, other.extension)
- }
-
- fn is_empty(&self) -> bool {
- self.indexed_additions.is_empty() && self.extension.is_empty()
- }
-}
+pub type KeychainAdditions<A> = IndexedAdditions<A, DerivationAdditions<Keychain>>;
+pub type Database<'m, C> = Persist<Store<'m, C>, C>;
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
-pub struct Args<C: clap::Subcommand> {
+pub struct Args<S: clap::Subcommand> {
#[clap(env = "DESCRIPTOR")]
pub descriptor: String,
#[clap(env = "CHANGE_DESCRIPTOR")]
pub cp_limit: usize,
#[clap(subcommand)]
- pub command: Commands<C>,
+ pub command: Commands<S>,
}
#[allow(clippy::almost_swapped)]
#[derive(Subcommand, Debug, Clone)]
-pub enum Commands<C: clap::Subcommand> {
+pub enum Commands<S: clap::Subcommand> {
#[clap(flatten)]
- ChainSpecific(C),
+ ChainSpecific(S),
/// Address generation and inspection.
Address {
#[clap(subcommand)]
}
}
-pub fn run_address_cmd<A, X>(
+pub fn run_address_cmd<A, C>(
graph: &mut KeychainTxGraph<A>,
- db: &Mutex<Database<'_, A, X>>,
+ db: &Mutex<Database<C>>,
network: Network,
cmd: AddressCmd,
) -> anyhow::Result<()>
where
- ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+ C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
{
let index = &mut graph.index;
let ((spk_i, spk), index_additions) = spk_chooser(index, &Keychain::External);
let db = &mut *db.lock().unwrap();
- db.stage(ChangeSet {
- indexed_additions: IndexedAdditions {
- index_additions,
- ..Default::default()
- },
- ..Default::default()
- });
+ db.stage(C::from(KeychainAdditions::from(index_additions)));
db.commit()?;
let addr = Address::from_script(spk, network).context("failed to derive address")?;
println!("[address @ {}] {}", spk_i, addr);
}
#[allow(clippy::too_many_arguments)]
-pub fn run_send_cmd<A: Anchor, O: ChainOracle, X>(
+pub fn run_send_cmd<A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>,
- db: &Mutex<Database<'_, A, X>>,
+ db: &Mutex<Database<'_, C>>,
chain: &O,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
cs_algorithm: CoinSelectionAlgo,
) -> anyhow::Result<()>
where
O::Error: std::error::Error + Send + Sync + 'static,
- ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+ C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
{
let (transaction, change_index) = {
let graph = &mut *graph.lock().unwrap();
// We must first persist to disk the fact that we've got a new address from the
// change keychain so future scans will find the tx we're about to broadcast.
// If we're unable to persist this, then we don't want to broadcast.
- db.lock().unwrap().stage(ChangeSet {
- indexed_additions: IndexedAdditions {
- index_additions,
- ..Default::default()
- },
- ..Default::default()
- });
+ db.lock()
+ .unwrap()
+ .stage(C::from(KeychainAdditions::from(index_additions)));
// We don't want other callers/threads to use this address while we're using it
// but we also don't want to scan the tx we just created because it's not
Ok(_) => {
println!("Broadcasted Tx : {}", transaction.txid());
- let indexed_additions = graph.lock().unwrap().insert_tx(&transaction, None, None);
+ let keychain_additions = graph.lock().unwrap().insert_tx(&transaction, None, None);
// We know the tx is at least unconfirmed now. Note if persisting here fails,
// it's not a big deal since we can always find it again form
// blockchain.
- db.lock().unwrap().stage(ChangeSet {
- indexed_additions,
- ..Default::default()
- });
+ db.lock().unwrap().stage(C::from(keychain_additions));
Ok(())
}
Err(e) => {
.collect()
}
-pub fn handle_commands<C: clap::Subcommand, A: Anchor, O: ChainOracle, X>(
+pub fn handle_commands<S: clap::Subcommand, A: Anchor, O: ChainOracle, C>(
graph: &Mutex<KeychainTxGraph<A>>,
- db: &Mutex<Database<A, X>>,
+ db: &Mutex<Database<C>>,
chain: &Mutex<O>,
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
network: Network,
broadcast: impl FnOnce(&Transaction) -> anyhow::Result<()>,
- cmd: Commands<C>,
+ cmd: Commands<S>,
) -> anyhow::Result<()>
where
O::Error: std::error::Error + Send + Sync + 'static,
- ChangeSet<A, X>: Default + Append + DeserializeOwned + Serialize,
+ C: Default + Append + DeserializeOwned + Serialize + From<KeychainAdditions<A>>,
{
match cmd {
Commands::ChainSpecific(_) => unreachable!("example code should handle this!"),
}
}
-pub fn prepare_index<C: clap::Subcommand, SC: secp256k1::Signing>(
- args: &Args<C>,
- secp: &Secp256k1<SC>,
+pub fn prepare_index<S: clap::Subcommand, CTX: secp256k1::Signing>(
+ args: &Args<S>,
+ secp: &Secp256k1<CTX>,
) -> anyhow::Result<(KeychainTxOutIndex<Keychain>, KeyMap)> {
let mut index = KeychainTxOutIndex::<Keychain>::default();
}
#[allow(clippy::type_complexity)]
-pub fn init<'m, S: clap::Subcommand, A: Anchor, X>(
+pub fn init<'m, S: clap::Subcommand, C>(
db_magic: &'m [u8],
db_default_path: &str,
) -> anyhow::Result<(
Args<S>,
KeyMap,
- Mutex<KeychainTxGraph<A>>,
- Mutex<Database<'m, A, X>>,
- X,
+ KeychainTxOutIndex<Keychain>,
+ Mutex<Database<'m, C>>,
+ C,
)>
where
- ChangeSet<A, X>: Default + Append + Serialize + DeserializeOwned,
+ C: Default + Append + Serialize + DeserializeOwned,
{
if std::env::var("BDK_DB_PATH").is_err() {
std::env::set_var("BDK_DB_PATH", db_default_path);
let secp = Secp256k1::default();
let (index, keymap) = prepare_index(&args, &secp)?;
- let mut indexed_graph = IndexedTxGraph::<A, KeychainTxOutIndex<Keychain>>::new(index);
-
- let mut db_backend =
- match Store::<'m, ChangeSet<A, X>>::new_from_path(db_magic, args.db_path.as_path()) {
- Ok(db_backend) => db_backend,
- Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)),
- };
+ let mut db_backend = match Store::<'m, C>::new_from_path(db_magic, &args.db_path) {
+ Ok(db_backend) => db_backend,
+ // we cannot return `err` directly as it has lifetime `'m`
+ Err(err) => return Err(anyhow::anyhow!("failed to init db backend: {:?}", err)),
+ };
- let ChangeSet {
- indexed_additions,
- extension,
- } = db_backend.load_from_persistence()?;
- indexed_graph.apply_additions(indexed_additions);
+ let init_changeset = db_backend.load_from_persistence()?;
Ok((
args,
keymap,
- Mutex::new(indexed_graph),
+ index,
Mutex::new(Database::new(db_backend)),
- extension,
+ init_changeset,
))
}
use bdk_chain::{
bitcoin::{Address, BlockHash, Network, OutPoint, Txid},
- indexed_tx_graph::IndexedAdditions,
- local_chain::{self, LocalChain},
+ indexed_tx_graph::{IndexedAdditions, IndexedTxGraph},
+ keychain::LocalChangeSet,
+ local_chain::LocalChain,
Append, ConfirmationHeightAnchor,
};
use bdk_electrum::{
use example_cli::{
anyhow::{self, Context},
clap::{self, Parser, Subcommand},
+ Keychain,
};
const DB_MAGIC: &[u8] = b"bdk_example_electrum";
pub batch_size: usize,
}
+type ChangeSet = LocalChangeSet<Keychain, ConfirmationHeightAnchor>;
+
fn main() -> anyhow::Result<()> {
- let (args, keymap, graph, db, chain_changeset) =
- example_cli::init::<ElectrumCommands, ConfirmationHeightAnchor, local_chain::ChangeSet>(
- DB_MAGIC, DB_PATH,
- )?;
+ let (args, keymap, index, db, init_changeset) =
+ example_cli::init::<ElectrumCommands, ChangeSet>(DB_MAGIC, DB_PATH)?;
+
+ let graph = Mutex::new({
+ let mut graph = IndexedTxGraph::new(index);
+ graph.apply_additions(init_changeset.indexed_additions);
+ graph
+ });
let chain = Mutex::new({
let mut chain = LocalChain::default();
- chain.apply_changeset(chain_changeset);
+ chain.apply_changeset(init_changeset.chain_changeset);
chain
});
additions
};
- example_cli::ChangeSet {
+ ChangeSet {
indexed_additions,
- extension: chain_changeset,
+ chain_changeset,
}
};