[bdk_chain_redesign] Introduce BlockAnchor
trait
* Introduce `GraphedTx` struct to access transaction data of graphed transactions. * Ability to insert/access anchors and "seen at" values for graphed transactions. * `Additions` now records changes to anchors and last_seen_at.
This commit is contained in:
parent
82f9caddab
commit
5ae5fe30eb
@ -23,7 +23,9 @@ pub use bdk_chain::keychain::Balance;
|
|||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
chain_graph,
|
chain_graph,
|
||||||
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
|
keychain::{persist, KeychainChangeSet, KeychainScan, KeychainTracker},
|
||||||
sparse_chain, BlockId, ConfirmationTime,
|
sparse_chain,
|
||||||
|
tx_graph::GraphedTx,
|
||||||
|
BlockId, ConfirmationTime,
|
||||||
};
|
};
|
||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@ -83,19 +85,19 @@ const COINBASE_MATURITY: u32 = 100;
|
|||||||
pub struct Wallet<D = ()> {
|
pub struct Wallet<D = ()> {
|
||||||
signers: Arc<SignersContainer>,
|
signers: Arc<SignersContainer>,
|
||||||
change_signers: Arc<SignersContainer>,
|
change_signers: Arc<SignersContainer>,
|
||||||
keychain_tracker: KeychainTracker<KeychainKind, ConfirmationTime>,
|
keychain_tracker: KeychainTracker<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
persist: persist::Persist<KeychainKind, ConfirmationTime, D>,
|
persist: persist::Persist<KeychainKind, BlockId, ConfirmationTime, D>,
|
||||||
network: Network,
|
network: Network,
|
||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
|
/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources.
|
||||||
/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
|
/// The type parameter `T` indicates the kind of transaction contained in the update. It's usually a [`bitcoin::Transaction`].
|
||||||
pub type Update = KeychainScan<KeychainKind, ConfirmationTime>;
|
pub type Update = KeychainScan<KeychainKind, BlockId, ConfirmationTime>;
|
||||||
/// Error indicating that something was wrong with an [`Update<T>`].
|
/// Error indicating that something was wrong with an [`Update<T>`].
|
||||||
pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
|
pub type UpdateError = chain_graph::UpdateError<ConfirmationTime>;
|
||||||
/// The changeset produced internally by applying an update
|
/// The changeset produced internally by applying an update
|
||||||
pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, ConfirmationTime>;
|
pub(crate) type ChangeSet = KeychainChangeSet<KeychainKind, BlockId, ConfirmationTime>;
|
||||||
|
|
||||||
/// The address index selection strategy to use to derived an address from the wallet's external
|
/// 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`.
|
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
|
||||||
@ -195,7 +197,7 @@ impl<D> Wallet<D> {
|
|||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, NewError<D::LoadError>>
|
) -> Result<Self, NewError<D::LoadError>>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
@ -257,7 +259,7 @@ impl<D> Wallet<D> {
|
|||||||
/// (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`].
|
||||||
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
self._get_address(address_index, KeychainKind::External)
|
self._get_address(address_index, KeychainKind::External)
|
||||||
}
|
}
|
||||||
@ -271,14 +273,14 @@ impl<D> Wallet<D> {
|
|||||||
/// be returned for any [`AddressIndex`].
|
/// be returned for any [`AddressIndex`].
|
||||||
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
self._get_address(address_index, KeychainKind::Internal)
|
self._get_address(address_index, KeychainKind::Internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
|
fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
let keychain = self.map_keychain(keychain);
|
let keychain = self.map_keychain(keychain);
|
||||||
let txout_index = &mut self.keychain_tracker.txout_index;
|
let txout_index = &mut self.keychain_tracker.txout_index;
|
||||||
@ -453,7 +455,11 @@ impl<D> Wallet<D> {
|
|||||||
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
|
let fee = inputs.map(|inputs| inputs.saturating_sub(outputs));
|
||||||
|
|
||||||
Some(TransactionDetails {
|
Some(TransactionDetails {
|
||||||
transaction: if include_raw { Some(tx.clone()) } else { None },
|
transaction: if include_raw {
|
||||||
|
Some(tx.tx.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
txid,
|
txid,
|
||||||
received,
|
received,
|
||||||
sent,
|
sent,
|
||||||
@ -518,7 +524,8 @@ impl<D> Wallet<D> {
|
|||||||
/// unconfirmed transactions last.
|
/// unconfirmed transactions last.
|
||||||
pub fn transactions(
|
pub fn transactions(
|
||||||
&self,
|
&self,
|
||||||
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, &Transaction)> + '_ {
|
) -> impl DoubleEndedIterator<Item = (ConfirmationTime, GraphedTx<'_, Transaction, BlockId>)> + '_
|
||||||
|
{
|
||||||
self.keychain_tracker
|
self.keychain_tracker
|
||||||
.chain_graph()
|
.chain_graph()
|
||||||
.transactions_in_chain()
|
.transactions_in_chain()
|
||||||
@ -613,7 +620,7 @@ impl<D> Wallet<D> {
|
|||||||
params: TxParams,
|
params: TxParams,
|
||||||
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
|
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
let external_descriptor = self
|
let external_descriptor = self
|
||||||
.keychain_tracker
|
.keychain_tracker
|
||||||
@ -1027,7 +1034,7 @@ impl<D> Wallet<D> {
|
|||||||
Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
|
Some((ConfirmationTime::Confirmed { .. }, _tx)) => {
|
||||||
return Err(Error::TransactionConfirmed)
|
return Err(Error::TransactionConfirmed)
|
||||||
}
|
}
|
||||||
Some((_, tx)) => tx.clone(),
|
Some((_, tx)) => tx.tx.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if !tx
|
if !tx
|
||||||
@ -1085,7 +1092,7 @@ impl<D> Wallet<D> {
|
|||||||
outpoint: txin.previous_output,
|
outpoint: txin.previous_output,
|
||||||
psbt_input: Box::new(psbt::Input {
|
psbt_input: Box::new(psbt::Input {
|
||||||
witness_utxo: Some(txout.clone()),
|
witness_utxo: Some(txout.clone()),
|
||||||
non_witness_utxo: Some(prev_tx.clone()),
|
non_witness_utxo: Some(prev_tx.tx.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -1613,7 +1620,7 @@ impl<D> Wallet<D> {
|
|||||||
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
|
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
|
||||||
}
|
}
|
||||||
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
|
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
|
||||||
psbt_input.non_witness_utxo = Some(prev_tx.clone());
|
psbt_input.non_witness_utxo = Some(prev_tx.tx.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(psbt_input)
|
Ok(psbt_input)
|
||||||
@ -1687,7 +1694,7 @@ impl<D> Wallet<D> {
|
|||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
pub fn apply_update(&mut self, update: Update) -> Result<(), UpdateError>
|
pub fn apply_update(&mut self, update: Update) -> Result<(), UpdateError>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
let changeset = self.keychain_tracker.apply_update(update)?;
|
let changeset = self.keychain_tracker.apply_update(update)?;
|
||||||
self.persist.stage(changeset);
|
self.persist.stage(changeset);
|
||||||
@ -1699,7 +1706,7 @@ impl<D> Wallet<D> {
|
|||||||
/// [`staged`]: Self::staged
|
/// [`staged`]: Self::staged
|
||||||
pub fn commit(&mut self) -> Result<(), D::WriteError>
|
pub fn commit(&mut self) -> Result<(), D::WriteError>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
self.persist.commit()
|
self.persist.commit()
|
||||||
}
|
}
|
||||||
@ -1717,7 +1724,7 @@ impl<D> Wallet<D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the inner [`ChainGraph`](bdk_chain::chain_graph::ChainGraph).
|
/// Get a reference to the inner [`ChainGraph`](bdk_chain::chain_graph::ChainGraph).
|
||||||
pub fn as_chain_graph(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
|
pub fn as_chain_graph(&self) -> &bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime> {
|
||||||
self.keychain_tracker.chain_graph()
|
self.keychain_tracker.chain_graph()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1728,8 +1735,8 @@ impl<D> AsRef<bdk_chain::tx_graph::TxGraph> for Wallet<D> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> AsRef<bdk_chain::chain_graph::ChainGraph<ConfirmationTime>> for Wallet<D> {
|
impl<D> AsRef<bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime>> for Wallet<D> {
|
||||||
fn as_ref(&self) -> &bdk_chain::chain_graph::ChainGraph<ConfirmationTime> {
|
fn as_ref(&self) -> &bdk_chain::chain_graph::ChainGraph<BlockId, ConfirmationTime> {
|
||||||
self.keychain_tracker.chain_graph()
|
self.keychain_tracker.chain_graph()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
use crate::collections::BTreeMap;
|
use crate::collections::BTreeMap;
|
||||||
use crate::collections::HashSet;
|
use crate::collections::HashSet;
|
||||||
use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
|
use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
|
||||||
|
use bdk_chain::BlockId;
|
||||||
use bdk_chain::ConfirmationTime;
|
use bdk_chain::ConfirmationTime;
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
@ -526,7 +527,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
|
|||||||
/// [`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, TransactionDetails), Error>
|
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
|
||||||
where
|
where
|
||||||
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
|
D: persist::PersistBackend<KeychainKind, BlockId, ConfirmationTime>,
|
||||||
{
|
{
|
||||||
self.wallet
|
self.wallet
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -2,7 +2,7 @@ use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
sparse_chain::{self, ChainPosition},
|
sparse_chain::{self, ChainPosition},
|
||||||
COINBASE_MATURITY,
|
BlockAnchor, COINBASE_MATURITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents the height at which a transaction is confirmed.
|
/// Represents the height at which a transaction is confirmed.
|
||||||
@ -118,7 +118,7 @@ impl ConfirmationTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to a block in the canonical chain.
|
/// A reference to a block in the canonical chain.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde",
|
feature = "serde",
|
||||||
derive(serde::Deserialize, serde::Serialize),
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
@ -140,6 +140,12 @@ impl Default for BlockId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockAnchor for BlockId {
|
||||||
|
fn anchor_block(&self) -> BlockId {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(u32, BlockHash)> for BlockId {
|
impl From<(u32, BlockHash)> for BlockId {
|
||||||
fn from((height, hash): (u32, BlockHash)) -> Self {
|
fn from((height, hash): (u32, BlockHash)) -> Self {
|
||||||
Self { height, hash }
|
Self { height, hash }
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sparse_chain::{self, ChainPosition, SparseChain},
|
sparse_chain::{self, ChainPosition, SparseChain},
|
||||||
tx_graph::{self, TxGraph},
|
tx_graph::{self, GraphedTx, TxGraph},
|
||||||
BlockId, ForEachTxOut, FullTxOut, TxHeight,
|
BlockAnchor, BlockId, ForEachTxOut, FullTxOut, TxHeight,
|
||||||
};
|
};
|
||||||
use alloc::{string::ToString, vec::Vec};
|
use alloc::{string::ToString, vec::Vec};
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||||
@ -25,12 +25,12 @@ use core::fmt::Debug;
|
|||||||
/// `graph` but not the other way around. Transactions may fall out of the *chain* (via re-org or
|
/// `graph` but not the other way around. Transactions may fall out of the *chain* (via re-org or
|
||||||
/// mempool eviction) but will remain in the *graph*.
|
/// mempool eviction) but will remain in the *graph*.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ChainGraph<P = TxHeight> {
|
pub struct ChainGraph<A = BlockId, P = TxHeight> {
|
||||||
chain: SparseChain<P>,
|
chain: SparseChain<P>,
|
||||||
graph: TxGraph,
|
graph: TxGraph<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> Default for ChainGraph<P> {
|
impl<A, P> Default for ChainGraph<A, P> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain: Default::default(),
|
chain: Default::default(),
|
||||||
@ -39,38 +39,39 @@ impl<P> Default for ChainGraph<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> AsRef<SparseChain<P>> for ChainGraph<P> {
|
impl<A, P> AsRef<SparseChain<P>> for ChainGraph<A, P> {
|
||||||
fn as_ref(&self) -> &SparseChain<P> {
|
fn as_ref(&self) -> &SparseChain<P> {
|
||||||
&self.chain
|
&self.chain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> AsRef<TxGraph> for ChainGraph<P> {
|
impl<A, P> AsRef<TxGraph<A>> for ChainGraph<A, P> {
|
||||||
fn as_ref(&self) -> &TxGraph {
|
fn as_ref(&self) -> &TxGraph<A> {
|
||||||
&self.graph
|
&self.graph
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> AsRef<ChainGraph<P>> for ChainGraph<P> {
|
impl<A, P> AsRef<ChainGraph<A, P>> for ChainGraph<A, P> {
|
||||||
fn as_ref(&self) -> &ChainGraph<P> {
|
fn as_ref(&self) -> &ChainGraph<A, P> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> ChainGraph<P> {
|
impl<A, P> ChainGraph<A, P> {
|
||||||
/// Returns a reference to the internal [`SparseChain`].
|
/// Returns a reference to the internal [`SparseChain`].
|
||||||
pub fn chain(&self) -> &SparseChain<P> {
|
pub fn chain(&self) -> &SparseChain<P> {
|
||||||
&self.chain
|
&self.chain
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the internal [`TxGraph`].
|
/// Returns a reference to the internal [`TxGraph`].
|
||||||
pub fn graph(&self) -> &TxGraph {
|
pub fn graph(&self) -> &TxGraph<A> {
|
||||||
&self.graph
|
&self.graph
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> ChainGraph<P>
|
impl<A, P> ChainGraph<A, P>
|
||||||
where
|
where
|
||||||
|
A: BlockAnchor,
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
{
|
{
|
||||||
/// Create a new chain graph from a `chain` and a `graph`.
|
/// Create a new chain graph from a `chain` and a `graph`.
|
||||||
@ -81,12 +82,14 @@ where
|
|||||||
/// transaction in `graph`.
|
/// transaction in `graph`.
|
||||||
/// 2. The `chain` has two transactions that are allegedly in it, but they conflict in the `graph`
|
/// 2. The `chain` has two transactions that are allegedly in it, but they conflict in the `graph`
|
||||||
/// (so could not possibly be in the same chain).
|
/// (so could not possibly be in the same chain).
|
||||||
pub fn new(chain: SparseChain<P>, graph: TxGraph) -> Result<Self, NewError<P>> {
|
pub fn new(chain: SparseChain<P>, graph: TxGraph<A>) -> Result<Self, NewError<P>> {
|
||||||
let mut missing = HashSet::default();
|
let mut missing = HashSet::default();
|
||||||
for (pos, txid) in chain.txids() {
|
for (pos, txid) in chain.txids() {
|
||||||
if let Some(tx) = graph.get_tx(*txid) {
|
if let Some(graphed_tx) = graph.get_tx(*txid) {
|
||||||
let conflict = graph
|
let conflict = graph
|
||||||
.walk_conflicts(tx, |_, txid| Some((chain.tx_position(txid)?.clone(), txid)))
|
.walk_conflicts(graphed_tx.tx, |_, txid| {
|
||||||
|
Some((chain.tx_position(txid)?.clone(), txid))
|
||||||
|
})
|
||||||
.next();
|
.next();
|
||||||
if let Some((conflict_pos, conflict)) = conflict {
|
if let Some((conflict_pos, conflict)) = conflict {
|
||||||
return Err(NewError::Conflict {
|
return Err(NewError::Conflict {
|
||||||
@ -126,7 +129,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
update: SparseChain<P>,
|
update: SparseChain<P>,
|
||||||
new_txs: impl IntoIterator<Item = Transaction>,
|
new_txs: impl IntoIterator<Item = Transaction>,
|
||||||
) -> Result<ChainGraph<P>, NewError<P>> {
|
) -> Result<ChainGraph<A, P>, NewError<P>> {
|
||||||
let mut inflated_chain = SparseChain::default();
|
let mut inflated_chain = SparseChain::default();
|
||||||
let mut inflated_graph = TxGraph::default();
|
let mut inflated_graph = TxGraph::default();
|
||||||
|
|
||||||
@ -143,7 +146,7 @@ where
|
|||||||
match self.chain.tx_position(*txid) {
|
match self.chain.tx_position(*txid) {
|
||||||
Some(original_pos) => {
|
Some(original_pos) => {
|
||||||
if original_pos != pos {
|
if original_pos != pos {
|
||||||
let tx = self
|
let graphed_tx = self
|
||||||
.graph
|
.graph
|
||||||
.get_tx(*txid)
|
.get_tx(*txid)
|
||||||
.expect("tx must exist as it is referenced in sparsechain")
|
.expect("tx must exist as it is referenced in sparsechain")
|
||||||
@ -151,7 +154,7 @@ where
|
|||||||
let _ = inflated_chain
|
let _ = inflated_chain
|
||||||
.insert_tx(*txid, pos.clone())
|
.insert_tx(*txid, pos.clone())
|
||||||
.expect("must insert since this was already in update");
|
.expect("must insert since this was already in update");
|
||||||
let _ = inflated_graph.insert_tx(tx);
|
let _ = inflated_graph.insert_tx(graphed_tx.tx.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -185,7 +188,7 @@ where
|
|||||||
|
|
||||||
/// Determines the changes required to invalidate checkpoints `from_height` (inclusive) and
|
/// Determines the changes required to invalidate checkpoints `from_height` (inclusive) and
|
||||||
/// above. Displaced transactions will have their positions moved to [`TxHeight::Unconfirmed`].
|
/// above. Displaced transactions will have their positions moved to [`TxHeight::Unconfirmed`].
|
||||||
pub fn invalidate_checkpoints_preview(&self, from_height: u32) -> ChangeSet<P> {
|
pub fn invalidate_checkpoints_preview(&self, from_height: u32) -> ChangeSet<A, P> {
|
||||||
ChangeSet {
|
ChangeSet {
|
||||||
chain: self.chain.invalidate_checkpoints_preview(from_height),
|
chain: self.chain.invalidate_checkpoints_preview(from_height),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -197,9 +200,9 @@ where
|
|||||||
///
|
///
|
||||||
/// This is equivalent to calling [`Self::invalidate_checkpoints_preview`] and
|
/// This is equivalent to calling [`Self::invalidate_checkpoints_preview`] and
|
||||||
/// [`Self::apply_changeset`] in sequence.
|
/// [`Self::apply_changeset`] in sequence.
|
||||||
pub fn invalidate_checkpoints(&mut self, from_height: u32) -> ChangeSet<P>
|
pub fn invalidate_checkpoints(&mut self, from_height: u32) -> ChangeSet<A, P>
|
||||||
where
|
where
|
||||||
ChangeSet<P>: Clone,
|
ChangeSet<A, P>: Clone,
|
||||||
{
|
{
|
||||||
let changeset = self.invalidate_checkpoints_preview(from_height);
|
let changeset = self.invalidate_checkpoints_preview(from_height);
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
@ -210,10 +213,10 @@ where
|
|||||||
///
|
///
|
||||||
/// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
|
/// This does not necessarily mean that it is *confirmed* in the blockchain; it might just be in
|
||||||
/// the unconfirmed transaction list within the [`SparseChain`].
|
/// the unconfirmed transaction list within the [`SparseChain`].
|
||||||
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, &Transaction)> {
|
pub fn get_tx_in_chain(&self, txid: Txid) -> Option<(&P, GraphedTx<'_, Transaction, A>)> {
|
||||||
let position = self.chain.tx_position(txid)?;
|
let position = self.chain.tx_position(txid)?;
|
||||||
let full_tx = self.graph.get_tx(txid).expect("must exist");
|
let graphed_tx = self.graph.get_tx(txid).expect("must exist");
|
||||||
Some((position, full_tx))
|
Some((position, graphed_tx))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
|
/// Determines the changes required to insert a transaction into the inner [`ChainGraph`] and
|
||||||
@ -225,7 +228,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
pos: P,
|
pos: P,
|
||||||
) -> Result<ChangeSet<P>, InsertTxError<P>> {
|
) -> Result<ChangeSet<A, P>, InsertTxError<P>> {
|
||||||
let mut changeset = ChangeSet {
|
let mut changeset = ChangeSet {
|
||||||
chain: self.chain.insert_tx_preview(tx.txid(), pos)?,
|
chain: self.chain.insert_tx_preview(tx.txid(), pos)?,
|
||||||
graph: self.graph.insert_tx_preview(tx),
|
graph: self.graph.insert_tx_preview(tx),
|
||||||
@ -238,14 +241,18 @@ where
|
|||||||
///
|
///
|
||||||
/// This is equivalent to calling [`Self::insert_tx_preview`] and [`Self::apply_changeset`] in
|
/// This is equivalent to calling [`Self::insert_tx_preview`] and [`Self::apply_changeset`] in
|
||||||
/// sequence.
|
/// sequence.
|
||||||
pub fn insert_tx(&mut self, tx: Transaction, pos: P) -> Result<ChangeSet<P>, InsertTxError<P>> {
|
pub fn insert_tx(
|
||||||
|
&mut self,
|
||||||
|
tx: Transaction,
|
||||||
|
pos: P,
|
||||||
|
) -> Result<ChangeSet<A, P>, InsertTxError<P>> {
|
||||||
let changeset = self.insert_tx_preview(tx, pos)?;
|
let changeset = self.insert_tx_preview(tx, pos)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the changes required to insert a [`TxOut`] into the internal [`TxGraph`].
|
/// Determines the changes required to insert a [`TxOut`] into the internal [`TxGraph`].
|
||||||
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P> {
|
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, P> {
|
||||||
ChangeSet {
|
ChangeSet {
|
||||||
chain: Default::default(),
|
chain: Default::default(),
|
||||||
graph: self.graph.insert_txout_preview(outpoint, txout),
|
graph: self.graph.insert_txout_preview(outpoint, txout),
|
||||||
@ -256,7 +263,7 @@ where
|
|||||||
///
|
///
|
||||||
/// This is equivalent to calling [`Self::insert_txout_preview`] and [`Self::apply_changeset`]
|
/// This is equivalent to calling [`Self::insert_txout_preview`] and [`Self::apply_changeset`]
|
||||||
/// in sequence.
|
/// in sequence.
|
||||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<P> {
|
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, P> {
|
||||||
let changeset = self.insert_txout_preview(outpoint, txout);
|
let changeset = self.insert_txout_preview(outpoint, txout);
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
changeset
|
changeset
|
||||||
@ -269,7 +276,7 @@ where
|
|||||||
pub fn insert_checkpoint_preview(
|
pub fn insert_checkpoint_preview(
|
||||||
&self,
|
&self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> Result<ChangeSet<P>, InsertCheckpointError> {
|
) -> Result<ChangeSet<A, P>, InsertCheckpointError> {
|
||||||
self.chain
|
self.chain
|
||||||
.insert_checkpoint_preview(block_id)
|
.insert_checkpoint_preview(block_id)
|
||||||
.map(|chain_changeset| ChangeSet {
|
.map(|chain_changeset| ChangeSet {
|
||||||
@ -285,7 +292,7 @@ where
|
|||||||
pub fn insert_checkpoint(
|
pub fn insert_checkpoint(
|
||||||
&mut self,
|
&mut self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> Result<ChangeSet<P>, InsertCheckpointError> {
|
) -> Result<ChangeSet<A, P>, InsertCheckpointError> {
|
||||||
let changeset = self.insert_checkpoint_preview(block_id)?;
|
let changeset = self.insert_checkpoint_preview(block_id)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
@ -294,8 +301,8 @@ where
|
|||||||
/// Calculates the difference between self and `update` in the form of a [`ChangeSet`].
|
/// Calculates the difference between self and `update` in the form of a [`ChangeSet`].
|
||||||
pub fn determine_changeset(
|
pub fn determine_changeset(
|
||||||
&self,
|
&self,
|
||||||
update: &ChainGraph<P>,
|
update: &ChainGraph<A, P>,
|
||||||
) -> Result<ChangeSet<P>, UpdateError<P>> {
|
) -> Result<ChangeSet<A, P>, UpdateError<P>> {
|
||||||
let chain_changeset = self
|
let chain_changeset = self
|
||||||
.chain
|
.chain
|
||||||
.determine_changeset(&update.chain)
|
.determine_changeset(&update.chain)
|
||||||
@ -330,7 +337,10 @@ where
|
|||||||
///
|
///
|
||||||
/// **WARNING:** If there are any missing full txs, conflict resolution will not be complete. In
|
/// **WARNING:** If there are any missing full txs, conflict resolution will not be complete. In
|
||||||
/// debug mode, this will result in panic.
|
/// debug mode, this will result in panic.
|
||||||
fn fix_conflicts(&self, changeset: &mut ChangeSet<P>) -> Result<(), UnresolvableConflict<P>> {
|
fn fix_conflicts(
|
||||||
|
&self,
|
||||||
|
changeset: &mut ChangeSet<A, P>,
|
||||||
|
) -> Result<(), UnresolvableConflict<P>> {
|
||||||
let mut chain_conflicts = vec![];
|
let mut chain_conflicts = vec![];
|
||||||
|
|
||||||
for (&txid, pos_change) in &changeset.chain.txids {
|
for (&txid, pos_change) in &changeset.chain.txids {
|
||||||
@ -346,7 +356,7 @@ where
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut full_tx = self.graph.get_tx(txid);
|
let mut full_tx = self.graph.get_tx(txid).map(|tx| tx.tx);
|
||||||
|
|
||||||
if full_tx.is_none() {
|
if full_tx.is_none() {
|
||||||
full_tx = changeset.graph.tx.iter().find(|tx| tx.txid() == txid)
|
full_tx = changeset.graph.tx.iter().find(|tx| tx.txid() == txid)
|
||||||
@ -406,14 +416,17 @@ where
|
|||||||
///
|
///
|
||||||
/// **Warning** this method assumes that the changeset is correctly formed. If it is not, the
|
/// **Warning** this method assumes that the changeset is correctly formed. If it is not, the
|
||||||
/// chain graph may behave incorrectly in the future and panic unexpectedly.
|
/// chain graph may behave incorrectly in the future and panic unexpectedly.
|
||||||
pub fn apply_changeset(&mut self, changeset: ChangeSet<P>) {
|
pub fn apply_changeset(&mut self, changeset: ChangeSet<A, P>) {
|
||||||
self.chain.apply_changeset(changeset.chain);
|
self.chain.apply_changeset(changeset.chain);
|
||||||
self.graph.apply_additions(changeset.graph);
|
self.graph.apply_additions(changeset.graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the `update` chain graph. Note this is shorthand for calling
|
/// Applies the `update` chain graph. Note this is shorthand for calling
|
||||||
/// [`Self::determine_changeset()`] and [`Self::apply_changeset()`] in sequence.
|
/// [`Self::determine_changeset()`] and [`Self::apply_changeset()`] in sequence.
|
||||||
pub fn apply_update(&mut self, update: ChainGraph<P>) -> Result<ChangeSet<P>, UpdateError<P>> {
|
pub fn apply_update(
|
||||||
|
&mut self,
|
||||||
|
update: ChainGraph<A, P>,
|
||||||
|
) -> Result<ChangeSet<A, P>, UpdateError<P>> {
|
||||||
let changeset = self.determine_changeset(&update)?;
|
let changeset = self.determine_changeset(&update)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
@ -426,7 +439,9 @@ where
|
|||||||
|
|
||||||
/// Iterate over the full transactions and their position in the chain ordered by their position
|
/// Iterate over the full transactions and their position in the chain ordered by their position
|
||||||
/// in ascending order.
|
/// in ascending order.
|
||||||
pub fn transactions_in_chain(&self) -> impl DoubleEndedIterator<Item = (&P, &Transaction)> {
|
pub fn transactions_in_chain(
|
||||||
|
&self,
|
||||||
|
) -> impl DoubleEndedIterator<Item = (&P, GraphedTx<'_, Transaction, A>)> {
|
||||||
self.chain
|
self.chain
|
||||||
.txids()
|
.txids()
|
||||||
.map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
|
.map(move |(pos, txid)| (pos, self.graph.get_tx(*txid).expect("must exist")))
|
||||||
@ -457,18 +472,18 @@ where
|
|||||||
serde(
|
serde(
|
||||||
crate = "serde_crate",
|
crate = "serde_crate",
|
||||||
bound(
|
bound(
|
||||||
deserialize = "P: serde::Deserialize<'de>",
|
deserialize = "A: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
|
||||||
serialize = "P: serde::Serialize"
|
serialize = "A: Ord + serde::Serialize, P: serde::Serialize"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct ChangeSet<P> {
|
pub struct ChangeSet<A, P> {
|
||||||
pub chain: sparse_chain::ChangeSet<P>,
|
pub chain: sparse_chain::ChangeSet<P>,
|
||||||
pub graph: tx_graph::Additions,
|
pub graph: tx_graph::Additions<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> ChangeSet<P> {
|
impl<A, P> ChangeSet<A, P> {
|
||||||
/// Returns `true` if this [`ChangeSet`] records no changes.
|
/// Returns `true` if this [`ChangeSet`] records no changes.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.chain.is_empty() && self.graph.is_empty()
|
self.chain.is_empty() && self.graph.is_empty()
|
||||||
@ -484,7 +499,7 @@ impl<P> ChangeSet<P> {
|
|||||||
|
|
||||||
/// Appends the changes in `other` into self such that applying `self` afterward has the same
|
/// Appends the changes in `other` into self such that applying `self` afterward has the same
|
||||||
/// effect as sequentially applying the original `self` and `other`.
|
/// effect as sequentially applying the original `self` and `other`.
|
||||||
pub fn append(&mut self, other: ChangeSet<P>)
|
pub fn append(&mut self, other: ChangeSet<A, P>)
|
||||||
where
|
where
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
{
|
{
|
||||||
@ -493,7 +508,7 @@ impl<P> ChangeSet<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> Default for ChangeSet<P> {
|
impl<A, P> Default for ChangeSet<A, P> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain: Default::default(),
|
chain: Default::default(),
|
||||||
@ -508,7 +523,7 @@ impl<P> ForEachTxOut for ChainGraph<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> ForEachTxOut for ChangeSet<P> {
|
impl<A, P> ForEachTxOut for ChangeSet<A, P> {
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||||
self.graph.for_each_txout(f)
|
self.graph.for_each_txout(f)
|
||||||
}
|
}
|
||||||
|
@ -99,14 +99,14 @@ impl<K> AsRef<BTreeMap<K, u32>> for DerivationAdditions<K> {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
/// An update that includes the last active indexes of each keychain.
|
/// An update that includes the last active indexes of each keychain.
|
||||||
pub struct KeychainScan<K, P> {
|
pub struct KeychainScan<K, A, P> {
|
||||||
/// The update data in the form of a chain that could be applied
|
/// The update data in the form of a chain that could be applied
|
||||||
pub update: ChainGraph<P>,
|
pub update: ChainGraph<A, P>,
|
||||||
/// The last active indexes of each keychain
|
/// The last active indexes of each keychain
|
||||||
pub last_active_indices: BTreeMap<K, u32>,
|
pub last_active_indices: BTreeMap<K, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> Default for KeychainScan<K, P> {
|
impl<K, A: Default, P> Default for KeychainScan<K, A, P> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
update: Default::default(),
|
update: Default::default(),
|
||||||
@ -115,8 +115,8 @@ impl<K, P> Default for KeychainScan<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> From<ChainGraph<P>> for KeychainScan<K, P> {
|
impl<K, A, P> From<ChainGraph<A, P>> for KeychainScan<K, A, P> {
|
||||||
fn from(update: ChainGraph<P>) -> Self {
|
fn from(update: ChainGraph<A, P>) -> Self {
|
||||||
KeychainScan {
|
KeychainScan {
|
||||||
update,
|
update,
|
||||||
last_active_indices: Default::default(),
|
last_active_indices: Default::default(),
|
||||||
@ -134,20 +134,20 @@ impl<K, P> From<ChainGraph<P>> for KeychainScan<K, P> {
|
|||||||
serde(
|
serde(
|
||||||
crate = "serde_crate",
|
crate = "serde_crate",
|
||||||
bound(
|
bound(
|
||||||
deserialize = "K: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
|
deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>, P: serde::Deserialize<'de>",
|
||||||
serialize = "K: Ord + serde::Serialize, P: serde::Serialize"
|
serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize, P: serde::Serialize"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct KeychainChangeSet<K, P> {
|
pub struct KeychainChangeSet<K, A, P> {
|
||||||
/// The changes in local keychain derivation indices
|
/// The changes in local keychain derivation indices
|
||||||
pub derivation_indices: DerivationAdditions<K>,
|
pub derivation_indices: DerivationAdditions<K>,
|
||||||
/// The changes that have occurred in the blockchain
|
/// The changes that have occurred in the blockchain
|
||||||
pub chain_graph: chain_graph::ChangeSet<P>,
|
pub chain_graph: chain_graph::ChangeSet<A, P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> Default for KeychainChangeSet<K, P> {
|
impl<K, A, P> Default for KeychainChangeSet<K, A, P> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain_graph: Default::default(),
|
chain_graph: Default::default(),
|
||||||
@ -156,7 +156,7 @@ impl<K, P> Default for KeychainChangeSet<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> KeychainChangeSet<K, P> {
|
impl<K, A, P> KeychainChangeSet<K, A, P> {
|
||||||
/// Returns whether the [`KeychainChangeSet`] is empty (no changes recorded).
|
/// Returns whether the [`KeychainChangeSet`] is empty (no changes recorded).
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.chain_graph.is_empty() && self.derivation_indices.is_empty()
|
self.chain_graph.is_empty() && self.derivation_indices.is_empty()
|
||||||
@ -167,7 +167,7 @@ impl<K, P> KeychainChangeSet<K, P> {
|
|||||||
///
|
///
|
||||||
/// Note the derivation indices cannot be decreased, so `other` will only change the derivation
|
/// Note the derivation indices cannot be decreased, so `other` will only change the derivation
|
||||||
/// index for a keychain, if it's value is higher than the one in `self`.
|
/// index for a keychain, if it's value is higher than the one in `self`.
|
||||||
pub fn append(&mut self, other: KeychainChangeSet<K, P>)
|
pub fn append(&mut self, other: KeychainChangeSet<K, A, P>)
|
||||||
where
|
where
|
||||||
K: Ord,
|
K: Ord,
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
@ -177,8 +177,8 @@ impl<K, P> KeychainChangeSet<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> From<chain_graph::ChangeSet<P>> for KeychainChangeSet<K, P> {
|
impl<K, A, P> From<chain_graph::ChangeSet<A, P>> for KeychainChangeSet<K, A, P> {
|
||||||
fn from(changeset: chain_graph::ChangeSet<P>) -> Self {
|
fn from(changeset: chain_graph::ChangeSet<A, P>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
chain_graph: changeset,
|
chain_graph: changeset,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -186,7 +186,7 @@ impl<K, P> From<chain_graph::ChangeSet<P>> for KeychainChangeSet<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, P> {
|
impl<K, A, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, A, P> {
|
||||||
fn from(additions: DerivationAdditions<K>) -> Self {
|
fn from(additions: DerivationAdditions<K>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
derivation_indices: additions,
|
derivation_indices: additions,
|
||||||
@ -195,13 +195,13 @@ impl<K, P> From<DerivationAdditions<K>> for KeychainChangeSet<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> AsRef<TxGraph> for KeychainScan<K, P> {
|
impl<K, A, P> AsRef<TxGraph<A>> for KeychainScan<K, A, P> {
|
||||||
fn as_ref(&self) -> &TxGraph {
|
fn as_ref(&self) -> &TxGraph<A> {
|
||||||
self.update.graph()
|
self.update.graph()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> ForEachTxOut for KeychainChangeSet<K, P> {
|
impl<K, A, P> ForEachTxOut for KeychainChangeSet<K, A, P> {
|
||||||
fn for_each_txout(&self, f: impl FnMut((bitcoin::OutPoint, &bitcoin::TxOut))) {
|
fn for_each_txout(&self, f: impl FnMut((bitcoin::OutPoint, &bitcoin::TxOut))) {
|
||||||
self.chain_graph.for_each_txout(f)
|
self.chain_graph.for_each_txout(f)
|
||||||
}
|
}
|
||||||
@ -287,12 +287,12 @@ mod test {
|
|||||||
rhs_di.insert(Keychain::Four, 4);
|
rhs_di.insert(Keychain::Four, 4);
|
||||||
let mut lhs = KeychainChangeSet {
|
let mut lhs = KeychainChangeSet {
|
||||||
derivation_indices: DerivationAdditions(lhs_di),
|
derivation_indices: DerivationAdditions(lhs_di),
|
||||||
chain_graph: chain_graph::ChangeSet::<TxHeight>::default(),
|
chain_graph: chain_graph::ChangeSet::<(), TxHeight>::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rhs = KeychainChangeSet {
|
let rhs = KeychainChangeSet {
|
||||||
derivation_indices: DerivationAdditions(rhs_di),
|
derivation_indices: DerivationAdditions(rhs_di),
|
||||||
chain_graph: chain_graph::ChangeSet::<TxHeight>::default(),
|
chain_graph: chain_graph::ChangeSet::<(), TxHeight>::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
lhs.append(rhs);
|
lhs.append(rhs);
|
||||||
|
@ -18,12 +18,12 @@ use crate::{keychain, sparse_chain::ChainPosition};
|
|||||||
///
|
///
|
||||||
/// [`KeychainTracker`]: keychain::KeychainTracker
|
/// [`KeychainTracker`]: keychain::KeychainTracker
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Persist<K, P, B> {
|
pub struct Persist<K, A, P, B> {
|
||||||
backend: B,
|
backend: B,
|
||||||
stage: keychain::KeychainChangeSet<K, P>,
|
stage: keychain::KeychainChangeSet<K, A, P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P, B> Persist<K, P, B> {
|
impl<K, A, P, B> Persist<K, A, P, B> {
|
||||||
/// Create a new `Persist` from a [`PersistBackend`].
|
/// Create a new `Persist` from a [`PersistBackend`].
|
||||||
pub fn new(backend: B) -> Self {
|
pub fn new(backend: B) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -35,7 +35,7 @@ impl<K, P, B> Persist<K, P, B> {
|
|||||||
/// Stage a `changeset` to later persistence with [`commit`].
|
/// Stage a `changeset` to later persistence with [`commit`].
|
||||||
///
|
///
|
||||||
/// [`commit`]: Self::commit
|
/// [`commit`]: Self::commit
|
||||||
pub fn stage(&mut self, changeset: keychain::KeychainChangeSet<K, P>)
|
pub fn stage(&mut self, changeset: keychain::KeychainChangeSet<K, A, P>)
|
||||||
where
|
where
|
||||||
K: Ord,
|
K: Ord,
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
@ -44,7 +44,7 @@ impl<K, P, B> Persist<K, P, B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the changes that haven't been committed yet
|
/// Get the changes that haven't been committed yet
|
||||||
pub fn staged(&self) -> &keychain::KeychainChangeSet<K, P> {
|
pub fn staged(&self) -> &keychain::KeychainChangeSet<K, A, P> {
|
||||||
&self.stage
|
&self.stage
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ impl<K, P, B> Persist<K, P, B> {
|
|||||||
/// Returns a backend-defined error if this fails.
|
/// Returns a backend-defined error if this fails.
|
||||||
pub fn commit(&mut self) -> Result<(), B::WriteError>
|
pub fn commit(&mut self) -> Result<(), B::WriteError>
|
||||||
where
|
where
|
||||||
B: PersistBackend<K, P>,
|
B: PersistBackend<K, A, P>,
|
||||||
{
|
{
|
||||||
self.backend.append_changeset(&self.stage)?;
|
self.backend.append_changeset(&self.stage)?;
|
||||||
self.stage = Default::default();
|
self.stage = Default::default();
|
||||||
@ -62,7 +62,7 @@ impl<K, P, B> Persist<K, P, B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A persistence backend for [`Persist`].
|
/// A persistence backend for [`Persist`].
|
||||||
pub trait PersistBackend<K, P> {
|
pub trait PersistBackend<K, A, P> {
|
||||||
/// The error the backend returns when it fails to write.
|
/// The error the backend returns when it fails to write.
|
||||||
type WriteError: core::fmt::Debug;
|
type WriteError: core::fmt::Debug;
|
||||||
|
|
||||||
@ -79,29 +79,29 @@ pub trait PersistBackend<K, P> {
|
|||||||
/// [`load_into_keychain_tracker`]: Self::load_into_keychain_tracker
|
/// [`load_into_keychain_tracker`]: Self::load_into_keychain_tracker
|
||||||
fn append_changeset(
|
fn append_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
changeset: &keychain::KeychainChangeSet<K, P>,
|
changeset: &keychain::KeychainChangeSet<K, A, P>,
|
||||||
) -> Result<(), Self::WriteError>;
|
) -> Result<(), Self::WriteError>;
|
||||||
|
|
||||||
/// Applies all the changesets the backend has received to `tracker`.
|
/// Applies all the changesets the backend has received to `tracker`.
|
||||||
fn load_into_keychain_tracker(
|
fn load_into_keychain_tracker(
|
||||||
&mut self,
|
&mut self,
|
||||||
tracker: &mut keychain::KeychainTracker<K, P>,
|
tracker: &mut keychain::KeychainTracker<K, A, P>,
|
||||||
) -> Result<(), Self::LoadError>;
|
) -> Result<(), Self::LoadError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> PersistBackend<K, P> for () {
|
impl<K, A, P> PersistBackend<K, A, P> for () {
|
||||||
type WriteError = ();
|
type WriteError = ();
|
||||||
type LoadError = ();
|
type LoadError = ();
|
||||||
|
|
||||||
fn append_changeset(
|
fn append_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
_changeset: &keychain::KeychainChangeSet<K, P>,
|
_changeset: &keychain::KeychainChangeSet<K, A, P>,
|
||||||
) -> Result<(), Self::WriteError> {
|
) -> Result<(), Self::WriteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn load_into_keychain_tracker(
|
fn load_into_keychain_tracker(
|
||||||
&mut self,
|
&mut self,
|
||||||
_tracker: &mut keychain::KeychainTracker<K, P>,
|
_tracker: &mut keychain::KeychainTracker<K, A, P>,
|
||||||
) -> Result<(), Self::LoadError> {
|
) -> Result<(), Self::LoadError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,16 @@ use super::{Balance, DerivationAdditions};
|
|||||||
/// The [`KeychainTracker`] atomically updates its [`KeychainTxOutIndex`] whenever new chain data is
|
/// The [`KeychainTracker`] atomically updates its [`KeychainTxOutIndex`] whenever new chain data is
|
||||||
/// incorporated into its internal [`ChainGraph`].
|
/// incorporated into its internal [`ChainGraph`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KeychainTracker<K, P> {
|
pub struct KeychainTracker<K, A, P> {
|
||||||
/// Index between script pubkeys to transaction outputs
|
/// Index between script pubkeys to transaction outputs
|
||||||
pub txout_index: KeychainTxOutIndex<K>,
|
pub txout_index: KeychainTxOutIndex<K>,
|
||||||
chain_graph: ChainGraph<P>,
|
chain_graph: ChainGraph<A, P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> KeychainTracker<K, P>
|
impl<K, A, P> KeychainTracker<K, A, P>
|
||||||
where
|
where
|
||||||
P: sparse_chain::ChainPosition,
|
P: sparse_chain::ChainPosition,
|
||||||
|
A: crate::BlockAnchor,
|
||||||
K: Ord + Clone + core::fmt::Debug,
|
K: Ord + Clone + core::fmt::Debug,
|
||||||
{
|
{
|
||||||
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses.
|
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses.
|
||||||
@ -64,8 +65,8 @@ where
|
|||||||
/// [`KeychainTxOutIndex`].
|
/// [`KeychainTxOutIndex`].
|
||||||
pub fn determine_changeset(
|
pub fn determine_changeset(
|
||||||
&self,
|
&self,
|
||||||
scan: &KeychainScan<K, P>,
|
scan: &KeychainScan<K, A, P>,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::UpdateError<P>> {
|
||||||
// TODO: `KeychainTxOutIndex::determine_additions`
|
// TODO: `KeychainTxOutIndex::determine_additions`
|
||||||
let mut derivation_indices = scan.last_active_indices.clone();
|
let mut derivation_indices = scan.last_active_indices.clone();
|
||||||
derivation_indices.retain(|keychain, index| {
|
derivation_indices.retain(|keychain, index| {
|
||||||
@ -89,8 +90,8 @@ where
|
|||||||
/// [`apply_changeset`]: Self::apply_changeset
|
/// [`apply_changeset`]: Self::apply_changeset
|
||||||
pub fn apply_update(
|
pub fn apply_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
scan: KeychainScan<K, P>,
|
scan: KeychainScan<K, A, P>,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::UpdateError<P>> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::UpdateError<P>> {
|
||||||
let changeset = self.determine_changeset(&scan)?;
|
let changeset = self.determine_changeset(&scan)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
@ -100,7 +101,7 @@ where
|
|||||||
///
|
///
|
||||||
/// Internally, this calls [`KeychainTxOutIndex::apply_additions`] and
|
/// Internally, this calls [`KeychainTxOutIndex::apply_additions`] and
|
||||||
/// [`ChainGraph::apply_changeset`] in sequence.
|
/// [`ChainGraph::apply_changeset`] in sequence.
|
||||||
pub fn apply_changeset(&mut self, changeset: KeychainChangeSet<K, P>) {
|
pub fn apply_changeset(&mut self, changeset: KeychainChangeSet<K, A, P>) {
|
||||||
let KeychainChangeSet {
|
let KeychainChangeSet {
|
||||||
derivation_indices,
|
derivation_indices,
|
||||||
chain_graph,
|
chain_graph,
|
||||||
@ -132,12 +133,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the internal [`ChainGraph`].
|
/// Returns a reference to the internal [`ChainGraph`].
|
||||||
pub fn chain_graph(&self) -> &ChainGraph<P> {
|
pub fn chain_graph(&self) -> &ChainGraph<A, P> {
|
||||||
&self.chain_graph
|
&self.chain_graph
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the internal [`TxGraph`] (which is part of the [`ChainGraph`]).
|
/// Returns a reference to the internal [`TxGraph`] (which is part of the [`ChainGraph`]).
|
||||||
pub fn graph(&self) -> &TxGraph {
|
pub fn graph(&self) -> &TxGraph<A> {
|
||||||
self.chain_graph().graph()
|
self.chain_graph().graph()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +160,7 @@ where
|
|||||||
pub fn insert_checkpoint_preview(
|
pub fn insert_checkpoint_preview(
|
||||||
&self,
|
&self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertCheckpointError> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertCheckpointError> {
|
||||||
Ok(KeychainChangeSet {
|
Ok(KeychainChangeSet {
|
||||||
chain_graph: self.chain_graph.insert_checkpoint_preview(block_id)?,
|
chain_graph: self.chain_graph.insert_checkpoint_preview(block_id)?,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -176,7 +177,7 @@ where
|
|||||||
pub fn insert_checkpoint(
|
pub fn insert_checkpoint(
|
||||||
&mut self,
|
&mut self,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertCheckpointError> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertCheckpointError> {
|
||||||
let changeset = self.insert_checkpoint_preview(block_id)?;
|
let changeset = self.insert_checkpoint_preview(block_id)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
@ -191,7 +192,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
pos: P,
|
pos: P,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertTxError<P>> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertTxError<P>> {
|
||||||
Ok(KeychainChangeSet {
|
Ok(KeychainChangeSet {
|
||||||
chain_graph: self.chain_graph.insert_tx_preview(tx, pos)?,
|
chain_graph: self.chain_graph.insert_tx_preview(tx, pos)?,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -209,7 +210,7 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
pos: P,
|
pos: P,
|
||||||
) -> Result<KeychainChangeSet<K, P>, chain_graph::InsertTxError<P>> {
|
) -> Result<KeychainChangeSet<K, A, P>, chain_graph::InsertTxError<P>> {
|
||||||
let changeset = self.insert_tx_preview(tx, pos)?;
|
let changeset = self.insert_tx_preview(tx, pos)?;
|
||||||
self.apply_changeset(changeset.clone());
|
self.apply_changeset(changeset.clone());
|
||||||
Ok(changeset)
|
Ok(changeset)
|
||||||
@ -280,7 +281,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> Default for KeychainTracker<K, P> {
|
impl<K, A, P> Default for KeychainTracker<K, A, P> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
txout_index: Default::default(),
|
txout_index: Default::default(),
|
||||||
@ -289,20 +290,20 @@ impl<K, P> Default for KeychainTracker<K, P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> AsRef<SparseChain<P>> for KeychainTracker<K, P> {
|
impl<K, A, P> AsRef<SparseChain<P>> for KeychainTracker<K, A, P> {
|
||||||
fn as_ref(&self) -> &SparseChain<P> {
|
fn as_ref(&self) -> &SparseChain<P> {
|
||||||
self.chain_graph.chain()
|
self.chain_graph.chain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> AsRef<TxGraph> for KeychainTracker<K, P> {
|
impl<K, A, P> AsRef<TxGraph<A>> for KeychainTracker<K, A, P> {
|
||||||
fn as_ref(&self) -> &TxGraph {
|
fn as_ref(&self) -> &TxGraph<A> {
|
||||||
self.chain_graph.graph()
|
self.chain_graph.graph()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> AsRef<ChainGraph<P>> for KeychainTracker<K, P> {
|
impl<K, A, P> AsRef<ChainGraph<A, P>> for KeychainTracker<K, A, P> {
|
||||||
fn as_ref(&self) -> &ChainGraph<P> {
|
fn as_ref(&self) -> &ChainGraph<A, P> {
|
||||||
&self.chain_graph
|
&self.chain_graph
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -899,7 +899,7 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
/// Attempt to retrieve a [`FullTxOut`] of the given `outpoint`.
|
/// Attempt to retrieve a [`FullTxOut`] of the given `outpoint`.
|
||||||
///
|
///
|
||||||
/// This will return `Some` only if the output's transaction is in both `self` and `graph`.
|
/// This will return `Some` only if the output's transaction is in both `self` and `graph`.
|
||||||
pub fn full_txout(&self, graph: &TxGraph, outpoint: OutPoint) -> Option<FullTxOut<P>> {
|
pub fn full_txout<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<FullTxOut<P>> {
|
||||||
let chain_pos = self.tx_position(outpoint.txid)?;
|
let chain_pos = self.tx_position(outpoint.txid)?;
|
||||||
|
|
||||||
let tx = graph.get_tx(outpoint.txid)?;
|
let tx = graph.get_tx(outpoint.txid)?;
|
||||||
@ -972,7 +972,7 @@ impl<P: ChainPosition> SparseChain<P> {
|
|||||||
///
|
///
|
||||||
/// Note that the transaction including `outpoint` does not need to be in the `graph` or the
|
/// Note that the transaction including `outpoint` does not need to be in the `graph` or the
|
||||||
/// `chain` for this to return `Some`.
|
/// `chain` for this to return `Some`.
|
||||||
pub fn spent_by(&self, graph: &TxGraph, outpoint: OutPoint) -> Option<(&P, Txid)> {
|
pub fn spent_by<A>(&self, graph: &TxGraph<A>, outpoint: OutPoint) -> Option<(&P, Txid)> {
|
||||||
graph
|
graph
|
||||||
.outspends(outpoint)
|
.outspends(outpoint)
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use bitcoin::{Block, OutPoint, Transaction, TxOut};
|
use bitcoin::{Block, BlockHash, OutPoint, Transaction, TxOut};
|
||||||
|
|
||||||
|
use crate::BlockId;
|
||||||
|
|
||||||
/// Trait to do something with every txout contained in a structure.
|
/// Trait to do something with every txout contained in a structure.
|
||||||
///
|
///
|
||||||
@ -31,3 +33,19 @@ impl ForEachTxOut for Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait that "anchors" blockchain data in a specific block of height and hash.
|
||||||
|
///
|
||||||
|
/// This trait is typically associated with blockchain data such as transactions.
|
||||||
|
pub trait BlockAnchor:
|
||||||
|
core::fmt::Debug + Clone + Eq + PartialOrd + Ord + core::hash::Hash + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
/// Returns the [`BlockId`] that the associated blockchain data is "anchored" in.
|
||||||
|
fn anchor_block(&self) -> BlockId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockAnchor for (u32, BlockHash) {
|
||||||
|
fn anchor_block(&self) -> BlockId {
|
||||||
|
(*self).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
//! of the changes to [`TxGraph`].
|
//! of the changes to [`TxGraph`].
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! # use bdk_chain::BlockId;
|
||||||
//! # use bdk_chain::tx_graph::TxGraph;
|
//! # use bdk_chain::tx_graph::TxGraph;
|
||||||
//! # use bdk_chain::example_utils::*;
|
//! # use bdk_chain::example_utils::*;
|
||||||
//! # use bitcoin::Transaction;
|
//! # use bitcoin::Transaction;
|
||||||
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
||||||
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
||||||
//! let mut graph = TxGraph::default();
|
//! let mut graph = TxGraph::<BlockId>::default();
|
||||||
//!
|
//!
|
||||||
//! // preview a transaction insertion (not actually inserted)
|
//! // preview a transaction insertion (not actually inserted)
|
||||||
//! let additions = graph.insert_tx_preview(tx_a);
|
//! let additions = graph.insert_tx_preview(tx_a);
|
||||||
@ -34,12 +35,13 @@
|
|||||||
//! A [`TxGraph`] can also be updated with another [`TxGraph`].
|
//! A [`TxGraph`] can also be updated with another [`TxGraph`].
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! # use bdk_chain::BlockId;
|
||||||
//! # use bdk_chain::tx_graph::TxGraph;
|
//! # use bdk_chain::tx_graph::TxGraph;
|
||||||
//! # use bdk_chain::example_utils::*;
|
//! # use bdk_chain::example_utils::*;
|
||||||
//! # use bitcoin::Transaction;
|
//! # use bitcoin::Transaction;
|
||||||
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
//! # let tx_a = tx_from_hex(RAW_TX_1);
|
||||||
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
//! # let tx_b = tx_from_hex(RAW_TX_2);
|
||||||
//! let mut graph = TxGraph::default();
|
//! let mut graph = TxGraph::<BlockId>::default();
|
||||||
//! let update = TxGraph::new(vec![tx_a, tx_b]);
|
//! let update = TxGraph::new(vec![tx_a, tx_b]);
|
||||||
//!
|
//!
|
||||||
//! // preview additions as the result of the update
|
//! // preview additions as the result of the update
|
||||||
@ -52,28 +54,76 @@
|
|||||||
//! let additions = graph.apply_update(update);
|
//! let additions = graph.apply_update(update);
|
||||||
//! assert!(additions.is_empty());
|
//! assert!(additions.is_empty());
|
||||||
//! ```
|
//! ```
|
||||||
use crate::{collections::*, ForEachTxOut};
|
|
||||||
|
use crate::{collections::*, BlockAnchor, BlockId, ForEachTxOut};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
use bitcoin::{OutPoint, Transaction, TxOut, Txid};
|
||||||
use core::ops::RangeInclusive;
|
use core::ops::{Deref, RangeInclusive};
|
||||||
|
|
||||||
/// A graph of transactions and spends.
|
/// A graph of transactions and spends.
|
||||||
///
|
///
|
||||||
/// See the [module-level documentation] for more.
|
/// See the [module-level documentation] for more.
|
||||||
///
|
///
|
||||||
/// [module-level documentation]: crate::tx_graph
|
/// [module-level documentation]: crate::tx_graph
|
||||||
#[derive(Clone, Debug, PartialEq, Default)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct TxGraph {
|
pub struct TxGraph<A = BlockId> {
|
||||||
txs: HashMap<Txid, TxNode>,
|
// all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
|
||||||
|
txs: HashMap<Txid, (TxNode, BTreeSet<A>, u64)>,
|
||||||
spends: BTreeMap<OutPoint, HashSet<Txid>>,
|
spends: BTreeMap<OutPoint, HashSet<Txid>>,
|
||||||
|
anchors: BTreeSet<(A, Txid)>,
|
||||||
|
|
||||||
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
|
// This atrocity exists so that `TxGraph::outspends()` can return a reference.
|
||||||
// FIXME: This can be removed once `HashSet::new` is a const fn.
|
// FIXME: This can be removed once `HashSet::new` is a const fn.
|
||||||
empty_outspends: HashSet<Txid>,
|
empty_outspends: HashSet<Txid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Node of a [`TxGraph`]. This can either be a whole transaction, or a partial transaction (where
|
impl<A> Default for TxGraph<A> {
|
||||||
/// we only have select outputs).
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
txs: Default::default(),
|
||||||
|
spends: Default::default(),
|
||||||
|
anchors: Default::default(),
|
||||||
|
empty_outspends: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An outward-facing view of a transaction that resides in a [`TxGraph`].
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct GraphedTx<'a, T, A> {
|
||||||
|
/// Txid of the transaction.
|
||||||
|
pub txid: Txid,
|
||||||
|
/// A partial or full representation of the transaction.
|
||||||
|
pub tx: &'a T,
|
||||||
|
/// The blocks that the transaction is "anchored" in.
|
||||||
|
pub anchors: &'a BTreeSet<A>,
|
||||||
|
/// The last-seen unix timestamp of the transaction.
|
||||||
|
pub last_seen: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, A> Deref for GraphedTx<'a, T, A> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, A> GraphedTx<'a, Transaction, A> {
|
||||||
|
pub fn from_tx(tx: &'a Transaction, anchors: &'a BTreeSet<A>) -> Self {
|
||||||
|
Self {
|
||||||
|
txid: tx.txid(),
|
||||||
|
tx,
|
||||||
|
anchors,
|
||||||
|
last_seen: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal representation of a transaction node of a [`TxGraph`].
|
||||||
|
///
|
||||||
|
/// This can either be a whole transaction, or a partial transaction (where we only have select
|
||||||
|
/// outputs).
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
enum TxNode {
|
enum TxNode {
|
||||||
Whole(Transaction),
|
Whole(Transaction),
|
||||||
@ -86,10 +136,10 @@ impl Default for TxNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxGraph {
|
impl<A> TxGraph<A> {
|
||||||
/// Iterate over all tx outputs known by [`TxGraph`].
|
/// Iterate over all tx outputs known by [`TxGraph`].
|
||||||
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
|
||||||
self.txs.iter().flat_map(|(txid, tx)| match tx {
|
self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
|
||||||
TxNode::Whole(tx) => tx
|
TxNode::Whole(tx) => tx
|
||||||
.output
|
.output
|
||||||
.iter()
|
.iter()
|
||||||
@ -104,11 +154,18 @@ impl TxGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all full transactions in the graph.
|
/// Iterate over all full transactions in the graph.
|
||||||
pub fn full_transactions(&self) -> impl Iterator<Item = &Transaction> {
|
pub fn full_transactions(&self) -> impl Iterator<Item = GraphedTx<'_, Transaction, A>> {
|
||||||
self.txs.iter().filter_map(|(_, tx)| match tx {
|
self.txs
|
||||||
TxNode::Whole(tx) => Some(tx),
|
.iter()
|
||||||
TxNode::Partial(_) => None,
|
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
|
||||||
})
|
TxNode::Whole(tx) => Some(GraphedTx {
|
||||||
|
txid,
|
||||||
|
tx,
|
||||||
|
anchors,
|
||||||
|
last_seen: *last_seen,
|
||||||
|
}),
|
||||||
|
TxNode::Partial(_) => None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a transaction by txid. This only returns `Some` for full transactions.
|
/// Get a transaction by txid. This only returns `Some` for full transactions.
|
||||||
@ -116,16 +173,21 @@ impl TxGraph {
|
|||||||
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
|
/// Refer to [`get_txout`] for getting a specific [`TxOut`].
|
||||||
///
|
///
|
||||||
/// [`get_txout`]: Self::get_txout
|
/// [`get_txout`]: Self::get_txout
|
||||||
pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
|
pub fn get_tx(&self, txid: Txid) -> Option<GraphedTx<'_, Transaction, A>> {
|
||||||
match self.txs.get(&txid)? {
|
match &self.txs.get(&txid)? {
|
||||||
TxNode::Whole(tx) => Some(tx),
|
(TxNode::Whole(tx), anchors, last_seen) => Some(GraphedTx {
|
||||||
TxNode::Partial(_) => None,
|
txid,
|
||||||
|
tx,
|
||||||
|
anchors,
|
||||||
|
last_seen: *last_seen,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtains a single tx output (if any) at the specified outpoint.
|
/// Obtains a single tx output (if any) at the specified outpoint.
|
||||||
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
|
pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
|
||||||
match self.txs.get(&outpoint.txid)? {
|
match &self.txs.get(&outpoint.txid)?.0 {
|
||||||
TxNode::Whole(tx) => tx.output.get(outpoint.vout as usize),
|
TxNode::Whole(tx) => tx.output.get(outpoint.vout as usize),
|
||||||
TxNode::Partial(txouts) => txouts.get(&outpoint.vout),
|
TxNode::Partial(txouts) => txouts.get(&outpoint.vout),
|
||||||
}
|
}
|
||||||
@ -133,7 +195,7 @@ impl TxGraph {
|
|||||||
|
|
||||||
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
|
/// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
|
||||||
pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
|
pub fn txouts(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
|
||||||
Some(match self.txs.get(&txid)? {
|
Some(match &self.txs.get(&txid)?.0 {
|
||||||
TxNode::Whole(tx) => tx
|
TxNode::Whole(tx) => tx
|
||||||
.output
|
.output
|
||||||
.iter()
|
.iter()
|
||||||
@ -178,7 +240,7 @@ impl TxGraph {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxGraph {
|
impl<A: BlockAnchor> TxGraph<A> {
|
||||||
/// Construct a new [`TxGraph`] from a list of transactions.
|
/// Construct a new [`TxGraph`] from a list of transactions.
|
||||||
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
|
pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
|
||||||
let mut new = Self::default();
|
let mut new = Self::default();
|
||||||
@ -187,11 +249,12 @@ impl TxGraph {
|
|||||||
}
|
}
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts the given [`TxOut`] at [`OutPoint`].
|
/// Inserts the given [`TxOut`] at [`OutPoint`].
|
||||||
///
|
///
|
||||||
/// Note this will ignore the action if we already have the full transaction that the txout is
|
/// Note this will ignore the action if we already have the full transaction that the txout is
|
||||||
/// alleged to be on (even if it doesn't match it!).
|
/// alleged to be on (even if it doesn't match it!).
|
||||||
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions {
|
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
||||||
let additions = self.insert_txout_preview(outpoint, txout);
|
let additions = self.insert_txout_preview(outpoint, txout);
|
||||||
self.apply_additions(additions.clone());
|
self.apply_additions(additions.clone());
|
||||||
additions
|
additions
|
||||||
@ -200,25 +263,52 @@ impl TxGraph {
|
|||||||
/// Inserts the given transaction into [`TxGraph`].
|
/// Inserts the given transaction into [`TxGraph`].
|
||||||
///
|
///
|
||||||
/// The [`Additions`] returned will be empty if `tx` already exists.
|
/// The [`Additions`] returned will be empty if `tx` already exists.
|
||||||
pub fn insert_tx(&mut self, tx: Transaction) -> Additions {
|
pub fn insert_tx(&mut self, tx: Transaction) -> Additions<A> {
|
||||||
let additions = self.insert_tx_preview(tx);
|
let additions = self.insert_tx_preview(tx);
|
||||||
self.apply_additions(additions.clone());
|
self.apply_additions(additions.clone());
|
||||||
additions
|
additions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts the given `anchor` into [`TxGraph`].
|
||||||
|
///
|
||||||
|
/// This is equivalent to calling [`insert_anchor_preview`] and [`apply_additions`] in sequence.
|
||||||
|
/// The [`Additions`] returned will be empty if graph already knows that `txid` exists in
|
||||||
|
/// `anchor`.
|
||||||
|
///
|
||||||
|
/// [`insert_anchor_preview`]: Self::insert_anchor_preview
|
||||||
|
/// [`apply_additions`]: Self::apply_additions
|
||||||
|
pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> Additions<A> {
|
||||||
|
let additions = self.insert_anchor_preview(txid, anchor);
|
||||||
|
self.apply_additions(additions.clone());
|
||||||
|
additions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts the given `seen_at` into [`TxGraph`].
|
||||||
|
///
|
||||||
|
/// This is equivalent to calling [`insert_seen_at_preview`] and [`apply_additions`] in
|
||||||
|
/// sequence.
|
||||||
|
///
|
||||||
|
/// [`insert_seen_at_preview`]: Self::insert_seen_at_preview
|
||||||
|
/// [`apply_additions`]: Self::apply_additions
|
||||||
|
pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> Additions<A> {
|
||||||
|
let additions = self.insert_seen_at_preview(txid, seen_at);
|
||||||
|
self.apply_additions(additions.clone());
|
||||||
|
additions
|
||||||
|
}
|
||||||
|
|
||||||
/// Extends this graph with another so that `self` becomes the union of the two sets of
|
/// Extends this graph with another so that `self` becomes the union of the two sets of
|
||||||
/// transactions.
|
/// transactions.
|
||||||
///
|
///
|
||||||
/// The returned [`Additions`] is the set difference between `update` and `self` (transactions that
|
/// The returned [`Additions`] is the set difference between `update` and `self` (transactions that
|
||||||
/// exist in `update` but not in `self`).
|
/// exist in `update` but not in `self`).
|
||||||
pub fn apply_update(&mut self, update: TxGraph) -> Additions {
|
pub fn apply_update(&mut self, update: TxGraph<A>) -> Additions<A> {
|
||||||
let additions = self.determine_additions(&update);
|
let additions = self.determine_additions(&update);
|
||||||
self.apply_additions(additions.clone());
|
self.apply_additions(additions.clone());
|
||||||
additions
|
additions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies [`Additions`] to [`TxGraph`].
|
/// Applies [`Additions`] to [`TxGraph`].
|
||||||
pub fn apply_additions(&mut self, additions: Additions) {
|
pub fn apply_additions(&mut self, additions: Additions<A>) {
|
||||||
for tx in additions.tx {
|
for tx in additions.tx {
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
|
|
||||||
@ -232,12 +322,21 @@ impl TxGraph {
|
|||||||
self.spends.entry(outpoint).or_default().insert(txid);
|
self.spends.entry(outpoint).or_default().insert(txid);
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(TxNode::Whole(old_tx)) = self.txs.insert(txid, TxNode::Whole(tx)) {
|
match self.txs.get_mut(&txid) {
|
||||||
debug_assert_eq!(
|
Some((tx_node @ TxNode::Partial(_), _, _)) => {
|
||||||
old_tx.txid(),
|
*tx_node = TxNode::Whole(tx);
|
||||||
txid,
|
}
|
||||||
"old tx of the same txid should not be different."
|
Some((TxNode::Whole(tx), _, _)) => {
|
||||||
);
|
debug_assert_eq!(
|
||||||
|
tx.txid(),
|
||||||
|
txid,
|
||||||
|
"tx should produce txid that is same as key"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.txs
|
||||||
|
.insert(txid, (TxNode::Whole(tx), BTreeSet::new(), 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,47 +344,75 @@ impl TxGraph {
|
|||||||
let tx_entry = self
|
let tx_entry = self
|
||||||
.txs
|
.txs
|
||||||
.entry(outpoint.txid)
|
.entry(outpoint.txid)
|
||||||
.or_insert_with(TxNode::default);
|
.or_insert_with(Default::default);
|
||||||
|
|
||||||
match tx_entry {
|
match tx_entry {
|
||||||
TxNode::Whole(_) => { /* do nothing since we already have full tx */ }
|
(TxNode::Whole(_), _, _) => { /* do nothing since we already have full tx */ }
|
||||||
TxNode::Partial(txouts) => {
|
(TxNode::Partial(txouts), _, _) => {
|
||||||
txouts.insert(outpoint.vout, txout);
|
txouts.insert(outpoint.vout, txout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (anchor, txid) in additions.anchors {
|
||||||
|
if self.anchors.insert((anchor.clone(), txid)) {
|
||||||
|
let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default);
|
||||||
|
anchors.insert(anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (txid, new_last_seen) in additions.last_seen {
|
||||||
|
let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default);
|
||||||
|
if new_last_seen > *last_seen {
|
||||||
|
*last_seen = new_last_seen;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Previews the resultant [`Additions`] when [`Self`] is updated against the `update` graph.
|
/// Previews the resultant [`Additions`] when [`Self`] is updated against the `update` graph.
|
||||||
///
|
///
|
||||||
/// The [`Additions`] would be the set difference between `update` and `self` (transactions that
|
/// The [`Additions`] would be the set difference between `update` and `self` (transactions that
|
||||||
/// exist in `update` but not in `self`).
|
/// exist in `update` but not in `self`).
|
||||||
pub fn determine_additions(&self, update: &TxGraph) -> Additions {
|
pub fn determine_additions(&self, update: &TxGraph<A>) -> Additions<A> {
|
||||||
let mut additions = Additions::default();
|
let mut additions = Additions::default();
|
||||||
|
|
||||||
for (&txid, update_tx) in &update.txs {
|
for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
|
||||||
if self.get_tx(txid).is_some() {
|
let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
|
||||||
continue;
|
(None, TxNode::Whole(update_tx)) => {
|
||||||
}
|
additions.tx.insert(update_tx.clone());
|
||||||
|
0
|
||||||
match update_tx {
|
|
||||||
TxNode::Whole(tx) => {
|
|
||||||
if matches!(self.txs.get(&txid), None | Some(TxNode::Partial(_))) {
|
|
||||||
additions.tx.insert(tx.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TxNode::Partial(partial) => {
|
(None, TxNode::Partial(update_txos)) => {
|
||||||
for (&vout, update_txout) in partial {
|
additions.txout.extend(
|
||||||
let outpoint = OutPoint::new(txid, vout);
|
update_txos
|
||||||
|
.iter()
|
||||||
if self.get_txout(outpoint) != Some(update_txout) {
|
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
|
||||||
additions.txout.insert(outpoint, update_txout.clone());
|
);
|
||||||
}
|
0
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
(Some((TxNode::Whole(_), _, last_seen)), _) => *last_seen,
|
||||||
|
(Some((TxNode::Partial(_), _, last_seen)), TxNode::Whole(update_tx)) => {
|
||||||
|
additions.tx.insert(update_tx.clone());
|
||||||
|
*last_seen
|
||||||
|
}
|
||||||
|
(Some((TxNode::Partial(txos), _, last_seen)), TxNode::Partial(update_txos)) => {
|
||||||
|
additions.txout.extend(
|
||||||
|
update_txos
|
||||||
|
.iter()
|
||||||
|
.filter(|(vout, _)| !txos.contains_key(*vout))
|
||||||
|
.map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
|
||||||
|
);
|
||||||
|
*last_seen
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if *update_last_seen > prev_last_seen {
|
||||||
|
additions.last_seen.insert(txid, *update_last_seen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additions.anchors = update.anchors.difference(&self.anchors).cloned().collect();
|
||||||
|
|
||||||
additions
|
additions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,9 +420,11 @@ impl TxGraph {
|
|||||||
/// mutate [`Self`].
|
/// mutate [`Self`].
|
||||||
///
|
///
|
||||||
/// The [`Additions`] result will be empty if `tx` already exists in `self`.
|
/// The [`Additions`] result will be empty if `tx` already exists in `self`.
|
||||||
pub fn insert_tx_preview(&self, tx: Transaction) -> Additions {
|
pub fn insert_tx_preview(&self, tx: Transaction) -> Additions<A> {
|
||||||
let mut update = Self::default();
|
let mut update = Self::default();
|
||||||
update.txs.insert(tx.txid(), TxNode::Whole(tx));
|
update
|
||||||
|
.txs
|
||||||
|
.insert(tx.txid(), (TxNode::Whole(tx), BTreeSet::new(), 0));
|
||||||
self.determine_additions(&update)
|
self.determine_additions(&update)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,17 +433,38 @@ impl TxGraph {
|
|||||||
///
|
///
|
||||||
/// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
|
/// The [`Additions`] result will be empty if the `outpoint` (or a full transaction containing
|
||||||
/// the `outpoint`) already existed in `self`.
|
/// the `outpoint`) already existed in `self`.
|
||||||
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions {
|
pub fn insert_txout_preview(&self, outpoint: OutPoint, txout: TxOut) -> Additions<A> {
|
||||||
let mut update = Self::default();
|
let mut update = Self::default();
|
||||||
update.txs.insert(
|
update.txs.insert(
|
||||||
outpoint.txid,
|
outpoint.txid,
|
||||||
TxNode::Partial([(outpoint.vout, txout)].into()),
|
(
|
||||||
|
TxNode::Partial([(outpoint.vout, txout)].into()),
|
||||||
|
BTreeSet::new(),
|
||||||
|
0,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
self.determine_additions(&update)
|
self.determine_additions(&update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the resultant [`Additions`] if the `txid` is set in `anchor`.
|
||||||
|
pub fn insert_anchor_preview(&self, txid: Txid, anchor: A) -> Additions<A> {
|
||||||
|
let mut update = Self::default();
|
||||||
|
update.anchors.insert((anchor, txid));
|
||||||
|
self.determine_additions(&update)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the resultant [`Additions`] if the `txid` is set to `seen_at`.
|
||||||
|
///
|
||||||
|
/// Note that [`TxGraph`] only keeps track of the lastest `seen_at`.
|
||||||
|
pub fn insert_seen_at_preview(&self, txid: Txid, seen_at: u64) -> Additions<A> {
|
||||||
|
let mut update = Self::default();
|
||||||
|
let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
|
||||||
|
*update_last_seen = seen_at;
|
||||||
|
self.determine_additions(&update)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxGraph {
|
impl<A> TxGraph<A> {
|
||||||
/// The transactions spending from this output.
|
/// The transactions spending from this output.
|
||||||
///
|
///
|
||||||
/// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
|
/// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
|
||||||
@ -344,11 +494,20 @@ impl TxGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all partial transactions (outputs only) in the graph.
|
/// Iterate over all partial transactions (outputs only) in the graph.
|
||||||
pub fn partial_transactions(&self) -> impl Iterator<Item = (Txid, &BTreeMap<u32, TxOut>)> {
|
pub fn partial_transactions(
|
||||||
self.txs.iter().filter_map(|(txid, tx)| match tx {
|
&self,
|
||||||
TxNode::Whole(_) => None,
|
) -> impl Iterator<Item = GraphedTx<'_, BTreeMap<u32, TxOut>, A>> {
|
||||||
TxNode::Partial(partial) => Some((*txid, partial)),
|
self.txs
|
||||||
})
|
.iter()
|
||||||
|
.filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
|
||||||
|
TxNode::Whole(_) => None,
|
||||||
|
TxNode::Partial(partial) => Some(GraphedTx {
|
||||||
|
txid,
|
||||||
|
tx: partial,
|
||||||
|
anchors,
|
||||||
|
last_seen: *last_seen,
|
||||||
|
}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an iterator that filters and maps descendants from the starting `txid`.
|
/// Creates an iterator that filters and maps descendants from the starting `txid`.
|
||||||
@ -361,7 +520,7 @@ impl TxGraph {
|
|||||||
///
|
///
|
||||||
/// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
|
/// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
|
||||||
/// and decide whether to visit descendants.
|
/// and decide whether to visit descendants.
|
||||||
pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<F>
|
pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<A, F>
|
||||||
where
|
where
|
||||||
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
||||||
{
|
{
|
||||||
@ -372,7 +531,11 @@ impl TxGraph {
|
|||||||
/// descendants of directly-conflicting transactions, which are also considered conflicts).
|
/// descendants of directly-conflicting transactions, which are also considered conflicts).
|
||||||
///
|
///
|
||||||
/// Refer to [`Self::walk_descendants`] for `walk_map` usage.
|
/// Refer to [`Self::walk_descendants`] for `walk_map` usage.
|
||||||
pub fn walk_conflicts<'g, F, O>(&'g self, tx: &'g Transaction, walk_map: F) -> TxDescendants<F>
|
pub fn walk_conflicts<'g, F, O>(
|
||||||
|
&'g self,
|
||||||
|
tx: &'g Transaction,
|
||||||
|
walk_map: F,
|
||||||
|
) -> TxDescendants<A, F>
|
||||||
where
|
where
|
||||||
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
F: FnMut(usize, Txid) -> Option<O> + 'g,
|
||||||
{
|
{
|
||||||
@ -413,19 +576,38 @@ impl TxGraph {
|
|||||||
/// Refer to [module-level documentation] for more.
|
/// Refer to [module-level documentation] for more.
|
||||||
///
|
///
|
||||||
/// [module-level documentation]: crate::tx_graph
|
/// [module-level documentation]: crate::tx_graph
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde",
|
feature = "serde",
|
||||||
derive(serde::Deserialize, serde::Serialize),
|
derive(serde::Deserialize, serde::Serialize),
|
||||||
serde(crate = "serde_crate")
|
serde(
|
||||||
|
crate = "serde_crate",
|
||||||
|
bound(
|
||||||
|
deserialize = "A: Ord + serde::Deserialize<'de>",
|
||||||
|
serialize = "A: Ord + serde::Serialize",
|
||||||
|
)
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct Additions {
|
pub struct Additions<A = BlockId> {
|
||||||
pub tx: BTreeSet<Transaction>,
|
pub tx: BTreeSet<Transaction>,
|
||||||
pub txout: BTreeMap<OutPoint, TxOut>,
|
pub txout: BTreeMap<OutPoint, TxOut>,
|
||||||
|
pub anchors: BTreeSet<(A, Txid)>,
|
||||||
|
pub last_seen: BTreeMap<Txid, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Additions {
|
impl<A> Default for Additions<A> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tx: Default::default(),
|
||||||
|
txout: Default::default(),
|
||||||
|
anchors: Default::default(),
|
||||||
|
last_seen: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Additions<A> {
|
||||||
/// Returns true if the [`Additions`] is empty (no transactions or txouts).
|
/// Returns true if the [`Additions`] is empty (no transactions or txouts).
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.tx.is_empty() && self.txout.is_empty()
|
self.tx.is_empty() && self.txout.is_empty()
|
||||||
@ -446,25 +628,25 @@ impl Additions {
|
|||||||
|
|
||||||
/// Appends the changes in `other` into self such that applying `self` afterward has the same
|
/// Appends the changes in `other` into self such that applying `self` afterward has the same
|
||||||
/// effect as sequentially applying the original `self` and `other`.
|
/// effect as sequentially applying the original `self` and `other`.
|
||||||
pub fn append(&mut self, mut other: Additions) {
|
pub fn append(&mut self, mut other: Additions<A>) {
|
||||||
self.tx.append(&mut other.tx);
|
self.tx.append(&mut other.tx);
|
||||||
self.txout.append(&mut other.txout);
|
self.txout.append(&mut other.txout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<TxGraph> for TxGraph {
|
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
|
||||||
fn as_ref(&self) -> &TxGraph {
|
fn as_ref(&self) -> &TxGraph<A> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ForEachTxOut for Additions {
|
impl<A> ForEachTxOut for Additions<A> {
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||||
self.txouts().for_each(f)
|
self.txouts().for_each(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ForEachTxOut for TxGraph {
|
impl<A> ForEachTxOut for TxGraph<A> {
|
||||||
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
|
||||||
self.all_txouts().for_each(f)
|
self.all_txouts().for_each(f)
|
||||||
}
|
}
|
||||||
@ -475,17 +657,17 @@ impl ForEachTxOut for TxGraph {
|
|||||||
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
|
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
|
||||||
///
|
///
|
||||||
/// [`walk_descendants`]: TxGraph::walk_descendants
|
/// [`walk_descendants`]: TxGraph::walk_descendants
|
||||||
pub struct TxDescendants<'g, F> {
|
pub struct TxDescendants<'g, A, F> {
|
||||||
graph: &'g TxGraph,
|
graph: &'g TxGraph<A>,
|
||||||
visited: HashSet<Txid>,
|
visited: HashSet<Txid>,
|
||||||
stack: Vec<(usize, Txid)>,
|
stack: Vec<(usize, Txid)>,
|
||||||
filter_map: F,
|
filter_map: F,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'g, F> TxDescendants<'g, F> {
|
impl<'g, A, F> TxDescendants<'g, A, F> {
|
||||||
/// Creates a `TxDescendants` that includes the starting `txid` when iterating.
|
/// Creates a `TxDescendants` that includes the starting `txid` when iterating.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) fn new_include_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self {
|
pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
|
||||||
Self {
|
Self {
|
||||||
graph,
|
graph,
|
||||||
visited: Default::default(),
|
visited: Default::default(),
|
||||||
@ -495,7 +677,7 @@ impl<'g, F> TxDescendants<'g, F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
|
/// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
|
||||||
pub(crate) fn new_exclude_root(graph: &'g TxGraph, txid: Txid, filter_map: F) -> Self {
|
pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
|
||||||
let mut descendants = Self {
|
let mut descendants = Self {
|
||||||
graph,
|
graph,
|
||||||
visited: Default::default(),
|
visited: Default::default(),
|
||||||
@ -508,7 +690,11 @@ impl<'g, F> TxDescendants<'g, F> {
|
|||||||
|
|
||||||
/// Creates a `TxDescendants` from multiple starting transactions that include the starting
|
/// Creates a `TxDescendants` from multiple starting transactions that include the starting
|
||||||
/// `txid`s when iterating.
|
/// `txid`s when iterating.
|
||||||
pub(crate) fn from_multiple_include_root<I>(graph: &'g TxGraph, txids: I, filter_map: F) -> Self
|
pub(crate) fn from_multiple_include_root<I>(
|
||||||
|
graph: &'g TxGraph<A>,
|
||||||
|
txids: I,
|
||||||
|
filter_map: F,
|
||||||
|
) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = Txid>,
|
I: IntoIterator<Item = Txid>,
|
||||||
{
|
{
|
||||||
@ -523,7 +709,11 @@ impl<'g, F> TxDescendants<'g, F> {
|
|||||||
/// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
|
/// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
|
||||||
/// `txid`s when iterating.
|
/// `txid`s when iterating.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub(crate) fn from_multiple_exclude_root<I>(graph: &'g TxGraph, txids: I, filter_map: F) -> Self
|
pub(crate) fn from_multiple_exclude_root<I>(
|
||||||
|
graph: &'g TxGraph<A>,
|
||||||
|
txids: I,
|
||||||
|
filter_map: F,
|
||||||
|
) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = Txid>,
|
I: IntoIterator<Item = Txid>,
|
||||||
{
|
{
|
||||||
@ -540,7 +730,7 @@ impl<'g, F> TxDescendants<'g, F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'g, F> TxDescendants<'g, F> {
|
impl<'g, A, F> TxDescendants<'g, A, F> {
|
||||||
fn populate_stack(&mut self, depth: usize, txid: Txid) {
|
fn populate_stack(&mut self, depth: usize, txid: Txid) {
|
||||||
let spend_paths = self
|
let spend_paths = self
|
||||||
.graph
|
.graph
|
||||||
@ -552,7 +742,7 @@ impl<'g, F> TxDescendants<'g, F> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'g, F, O> Iterator for TxDescendants<'g, F>
|
impl<'g, A, F, O> Iterator for TxDescendants<'g, A, F>
|
||||||
where
|
where
|
||||||
F: FnMut(usize, Txid) -> Option<O>,
|
F: FnMut(usize, Txid) -> Option<O>,
|
||||||
{
|
{
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
chain_graph::*,
|
chain_graph::*,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
sparse_chain,
|
sparse_chain,
|
||||||
tx_graph::{self, TxGraph},
|
tx_graph::{self, GraphedTx, TxGraph},
|
||||||
BlockId, TxHeight,
|
BlockId, TxHeight,
|
||||||
};
|
};
|
||||||
use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness};
|
use bitcoin::{
|
||||||
|
BlockHash, OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_spent_by() {
|
fn test_spent_by() {
|
||||||
@ -43,7 +47,7 @@ fn test_spent_by() {
|
|||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cg1 = ChainGraph::default();
|
let mut cg1 = ChainGraph::<(u32, BlockHash), _>::default();
|
||||||
let _ = cg1
|
let _ = cg1
|
||||||
.insert_tx(tx1, TxHeight::Unconfirmed)
|
.insert_tx(tx1, TxHeight::Unconfirmed)
|
||||||
.expect("should insert");
|
.expect("should insert");
|
||||||
@ -124,7 +128,7 @@ fn update_evicts_conflicting_tx() {
|
|||||||
cg
|
cg
|
||||||
};
|
};
|
||||||
|
|
||||||
let changeset = ChangeSet::<TxHeight> {
|
let changeset = ChangeSet::<(u32, BlockHash), TxHeight> {
|
||||||
chain: sparse_chain::ChangeSet {
|
chain: sparse_chain::ChangeSet {
|
||||||
checkpoints: Default::default(),
|
checkpoints: Default::default(),
|
||||||
txids: [
|
txids: [
|
||||||
@ -133,9 +137,10 @@ fn update_evicts_conflicting_tx() {
|
|||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
graph: tx_graph::Additions {
|
graph: tx_graph::Additions::<(u32, BlockHash)> {
|
||||||
tx: [tx_b2.clone()].into(),
|
tx: [tx_b2.clone()].into(),
|
||||||
txout: [].into(),
|
txout: [].into(),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -149,7 +154,7 @@ fn update_evicts_conflicting_tx() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let cg1 = {
|
let cg1 = {
|
||||||
let mut cg = ChainGraph::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
|
||||||
let _ = cg.insert_checkpoint(cp_a).expect("should insert cp");
|
let _ = cg.insert_checkpoint(cp_a).expect("should insert cp");
|
||||||
let _ = cg.insert_checkpoint(cp_b).expect("should insert cp");
|
let _ = cg.insert_checkpoint(cp_b).expect("should insert cp");
|
||||||
let _ = cg
|
let _ = cg
|
||||||
@ -203,7 +208,7 @@ fn update_evicts_conflicting_tx() {
|
|||||||
cg
|
cg
|
||||||
};
|
};
|
||||||
|
|
||||||
let changeset = ChangeSet::<TxHeight> {
|
let changeset = ChangeSet::<(u32, BlockHash), TxHeight> {
|
||||||
chain: sparse_chain::ChangeSet {
|
chain: sparse_chain::ChangeSet {
|
||||||
checkpoints: [(1, Some(h!("B'")))].into(),
|
checkpoints: [(1, Some(h!("B'")))].into(),
|
||||||
txids: [
|
txids: [
|
||||||
@ -212,9 +217,10 @@ fn update_evicts_conflicting_tx() {
|
|||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
},
|
},
|
||||||
graph: tx_graph::Additions {
|
graph: tx_graph::Additions::<(u32, BlockHash)> {
|
||||||
tx: [tx_b2].into(),
|
tx: [tx_b2].into(),
|
||||||
txout: [].into(),
|
txout: [].into(),
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -250,7 +256,7 @@ fn chain_graph_new_missing() {
|
|||||||
(tx_b.txid(), TxHeight::Confirmed(0))
|
(tx_b.txid(), TxHeight::Confirmed(0))
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
|
|
||||||
let mut expected_missing = HashSet::new();
|
let mut expected_missing = HashSet::new();
|
||||||
expected_missing.insert(tx_a.txid());
|
expected_missing.insert(tx_a.txid());
|
||||||
@ -287,7 +293,7 @@ fn chain_graph_new_missing() {
|
|||||||
|
|
||||||
let new_graph = ChainGraph::new(update.clone(), graph.clone()).unwrap();
|
let new_graph = ChainGraph::new(update.clone(), graph.clone()).unwrap();
|
||||||
let expected_graph = {
|
let expected_graph = {
|
||||||
let mut cg = ChainGraph::<TxHeight>::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
|
||||||
let _ = cg
|
let _ = cg
|
||||||
.insert_checkpoint(update.latest_checkpoint().unwrap())
|
.insert_checkpoint(update.latest_checkpoint().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -342,7 +348,7 @@ fn chain_graph_new_conflicts() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let graph = TxGraph::new([tx_a, tx_b, tx_b2]);
|
let graph = TxGraph::<(u32, BlockHash)>::new([tx_a, tx_b, tx_b2]);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ChainGraph::new(chain, graph),
|
ChainGraph::new(chain, graph),
|
||||||
@ -352,7 +358,7 @@ fn chain_graph_new_conflicts() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_tx_in_chain() {
|
fn test_get_tx_in_chain() {
|
||||||
let mut cg = ChainGraph::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
|
||||||
let tx = Transaction {
|
let tx = Transaction {
|
||||||
version: 0x01,
|
version: 0x01,
|
||||||
lock_time: PackedLockTime(0),
|
lock_time: PackedLockTime(0),
|
||||||
@ -363,13 +369,21 @@ fn test_get_tx_in_chain() {
|
|||||||
let _ = cg.insert_tx(tx.clone(), TxHeight::Unconfirmed).unwrap();
|
let _ = cg.insert_tx(tx.clone(), TxHeight::Unconfirmed).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cg.get_tx_in_chain(tx.txid()),
|
cg.get_tx_in_chain(tx.txid()),
|
||||||
Some((&TxHeight::Unconfirmed, &tx))
|
Some((
|
||||||
|
&TxHeight::Unconfirmed,
|
||||||
|
GraphedTx {
|
||||||
|
txid: tx.txid(),
|
||||||
|
tx: &tx,
|
||||||
|
anchors: &BTreeSet::new(),
|
||||||
|
last_seen: 0
|
||||||
|
}
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_iterate_transactions() {
|
fn test_iterate_transactions() {
|
||||||
let mut cg = ChainGraph::default();
|
let mut cg = ChainGraph::<BlockId, _>::default();
|
||||||
let txs = (0..3)
|
let txs = (0..3)
|
||||||
.map(|i| Transaction {
|
.map(|i| Transaction {
|
||||||
version: i,
|
version: i,
|
||||||
@ -395,9 +409,18 @@ fn test_iterate_transactions() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
cg.transactions_in_chain().collect::<Vec<_>>(),
|
cg.transactions_in_chain().collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
(&TxHeight::Confirmed(0), &txs[2]),
|
(
|
||||||
(&TxHeight::Confirmed(1), &txs[0]),
|
&TxHeight::Confirmed(0),
|
||||||
(&TxHeight::Unconfirmed, &txs[1]),
|
GraphedTx::from_tx(&txs[2], &BTreeSet::new())
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&TxHeight::Confirmed(1),
|
||||||
|
GraphedTx::from_tx(&txs[0], &BTreeSet::new())
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&TxHeight::Unconfirmed,
|
||||||
|
GraphedTx::from_tx(&txs[1], &BTreeSet::new())
|
||||||
|
),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -457,7 +480,7 @@ fn test_apply_changes_reintroduce_tx() {
|
|||||||
|
|
||||||
// block1, block2a, tx1, tx2a
|
// block1, block2a, tx1, tx2a
|
||||||
let mut cg = {
|
let mut cg = {
|
||||||
let mut cg = ChainGraph::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), _>::default();
|
||||||
let _ = cg.insert_checkpoint(block1).unwrap();
|
let _ = cg.insert_checkpoint(block1).unwrap();
|
||||||
let _ = cg.insert_checkpoint(block2a).unwrap();
|
let _ = cg.insert_checkpoint(block2a).unwrap();
|
||||||
let _ = cg.insert_tx(tx1, TxHeight::Confirmed(1)).unwrap();
|
let _ = cg.insert_tx(tx1, TxHeight::Confirmed(1)).unwrap();
|
||||||
@ -613,7 +636,7 @@ fn test_evict_descendants() {
|
|||||||
let txid_conflict = tx_conflict.txid();
|
let txid_conflict = tx_conflict.txid();
|
||||||
|
|
||||||
let cg = {
|
let cg = {
|
||||||
let mut cg = ChainGraph::<TxHeight>::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
|
||||||
let _ = cg.insert_checkpoint(block_1);
|
let _ = cg.insert_checkpoint(block_1);
|
||||||
let _ = cg.insert_checkpoint(block_2a);
|
let _ = cg.insert_checkpoint(block_2a);
|
||||||
let _ = cg.insert_tx(tx_1, TxHeight::Confirmed(1));
|
let _ = cg.insert_tx(tx_1, TxHeight::Confirmed(1));
|
||||||
@ -625,7 +648,7 @@ fn test_evict_descendants() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let update = {
|
let update = {
|
||||||
let mut cg = ChainGraph::<TxHeight>::default();
|
let mut cg = ChainGraph::<(u32, BlockHash), TxHeight>::default();
|
||||||
let _ = cg.insert_checkpoint(block_1);
|
let _ = cg.insert_checkpoint(block_1);
|
||||||
let _ = cg.insert_checkpoint(block_2b);
|
let _ = cg.insert_checkpoint(block_2b);
|
||||||
let _ = cg.insert_tx(tx_conflict.clone(), TxHeight::Confirmed(2));
|
let _ = cg.insert_tx(tx_conflict.clone(), TxHeight::Confirmed(2));
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
#![cfg(feature = "miniscript")]
|
#![cfg(feature = "miniscript")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
keychain::{Balance, KeychainTracker},
|
keychain::{Balance, KeychainTracker},
|
||||||
miniscript::{
|
miniscript::{
|
||||||
bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
|
bitcoin::{secp256k1::Secp256k1, OutPoint, PackedLockTime, Transaction, TxOut},
|
||||||
Descriptor,
|
Descriptor,
|
||||||
},
|
},
|
||||||
|
tx_graph::GraphedTx,
|
||||||
BlockId, ConfirmationTime, TxHeight,
|
BlockId, ConfirmationTime, TxHeight,
|
||||||
};
|
};
|
||||||
use bitcoin::TxIn;
|
use bitcoin::{BlockHash, TxIn};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_insert_tx() {
|
fn test_insert_tx() {
|
||||||
let mut tracker = KeychainTracker::default();
|
let mut tracker = KeychainTracker::<_, BlockId, _>::default();
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let (descriptor, _) = Descriptor::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
|
let (descriptor, _) = Descriptor::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
|
||||||
tracker.add_keychain((), descriptor.clone());
|
tracker.add_keychain((), descriptor.clone());
|
||||||
@ -40,7 +43,10 @@ fn test_insert_tx() {
|
|||||||
.chain_graph()
|
.chain_graph()
|
||||||
.transactions_in_chain()
|
.transactions_in_chain()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![(&ConfirmationTime::Unconfirmed, &tx)]
|
vec![(
|
||||||
|
&ConfirmationTime::Unconfirmed,
|
||||||
|
GraphedTx::from_tx(&tx, &BTreeSet::new())
|
||||||
|
)]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -66,7 +72,7 @@ fn test_balance() {
|
|||||||
One,
|
One,
|
||||||
Two,
|
Two,
|
||||||
}
|
}
|
||||||
let mut tracker = KeychainTracker::<Keychain, TxHeight>::default();
|
let mut tracker = KeychainTracker::<Keychain, (u32, BlockHash), TxHeight>::default();
|
||||||
let one = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)#rg247h69").unwrap();
|
let one = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)#rg247h69").unwrap();
|
||||||
let two = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/1/*)#ju05rz2a").unwrap();
|
let two = Descriptor::from_str("tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/1/*)#ju05rz2a").unwrap();
|
||||||
tracker.add_keychain(Keychain::One, one);
|
tracker.add_keychain(Keychain::One, one);
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
mod common;
|
mod common;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
collections::*,
|
collections::*,
|
||||||
tx_graph::{Additions, TxGraph},
|
tx_graph::{Additions, GraphedTx, TxGraph},
|
||||||
|
BlockId,
|
||||||
|
};
|
||||||
|
use bitcoin::{
|
||||||
|
hashes::Hash, BlockHash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid,
|
||||||
};
|
};
|
||||||
use bitcoin::{hashes::Hash, OutPoint, PackedLockTime, Script, Transaction, TxIn, TxOut, Txid};
|
|
||||||
use core::iter;
|
use core::iter;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -35,7 +38,7 @@ fn insert_txouts() {
|
|||||||
)];
|
)];
|
||||||
|
|
||||||
let mut graph = {
|
let mut graph = {
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
for (outpoint, txout) in &original_ops {
|
for (outpoint, txout) in &original_ops {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
graph.insert_txout(*outpoint, txout.clone()),
|
graph.insert_txout(*outpoint, txout.clone()),
|
||||||
@ -69,6 +72,7 @@ fn insert_txouts() {
|
|||||||
Additions {
|
Additions {
|
||||||
tx: [].into(),
|
tx: [].into(),
|
||||||
txout: update_ops.into(),
|
txout: update_ops.into(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -90,7 +94,7 @@ fn insert_tx_graph_doesnt_count_coinbase_as_spent() {
|
|||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let _ = graph.insert_tx(tx);
|
let _ = graph.insert_tx(tx);
|
||||||
assert!(graph.outspends(OutPoint::null()).is_empty());
|
assert!(graph.outspends(OutPoint::null()).is_empty());
|
||||||
assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none());
|
assert!(graph.tx_outspends(Txid::all_zeros()).next().is_none());
|
||||||
@ -120,8 +124,8 @@ fn insert_tx_graph_keeps_track_of_spend() {
|
|||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut graph1 = TxGraph::default();
|
let mut graph1 = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let mut graph2 = TxGraph::default();
|
let mut graph2 = TxGraph::<(u32, BlockHash)>::default();
|
||||||
|
|
||||||
// insert in different order
|
// insert in different order
|
||||||
let _ = graph1.insert_tx(tx1.clone());
|
let _ = graph1.insert_tx(tx1.clone());
|
||||||
@ -149,14 +153,17 @@ fn insert_tx_can_retrieve_full_tx_from_graph() {
|
|||||||
output: vec![TxOut::default()],
|
output: vec![TxOut::default()],
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<BlockId>::default();
|
||||||
let _ = graph.insert_tx(tx.clone());
|
let _ = graph.insert_tx(tx.clone());
|
||||||
assert_eq!(graph.get_tx(tx.txid()), Some(&tx));
|
assert_eq!(
|
||||||
|
graph.get_tx(tx.txid()),
|
||||||
|
Some(GraphedTx::from_tx(&tx, &BTreeSet::new()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_tx_displaces_txouts() {
|
fn insert_tx_displaces_txouts() {
|
||||||
let mut tx_graph = TxGraph::default();
|
let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let tx = Transaction {
|
let tx = Transaction {
|
||||||
version: 0x01,
|
version: 0x01,
|
||||||
lock_time: PackedLockTime(0),
|
lock_time: PackedLockTime(0),
|
||||||
@ -212,7 +219,7 @@ fn insert_tx_displaces_txouts() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_txout_does_not_displace_tx() {
|
fn insert_txout_does_not_displace_tx() {
|
||||||
let mut tx_graph = TxGraph::default();
|
let mut tx_graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let tx = Transaction {
|
let tx = Transaction {
|
||||||
version: 0x01,
|
version: 0x01,
|
||||||
lock_time: PackedLockTime(0),
|
lock_time: PackedLockTime(0),
|
||||||
@ -268,7 +275,7 @@ fn insert_txout_does_not_displace_tx() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_fee() {
|
fn test_calculate_fee() {
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let intx1 = Transaction {
|
let intx1 = Transaction {
|
||||||
version: 0x01,
|
version: 0x01,
|
||||||
lock_time: PackedLockTime(0),
|
lock_time: PackedLockTime(0),
|
||||||
@ -362,7 +369,7 @@ fn test_calculate_fee_on_coinbase() {
|
|||||||
output: vec![TxOut::default()],
|
output: vec![TxOut::default()],
|
||||||
};
|
};
|
||||||
|
|
||||||
let graph = TxGraph::default();
|
let graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
|
|
||||||
assert_eq!(graph.calculate_fee(&tx), Some(0));
|
assert_eq!(graph.calculate_fee(&tx), Some(0));
|
||||||
}
|
}
|
||||||
@ -404,7 +411,7 @@ fn test_conflicting_descendants() {
|
|||||||
let txid_a = tx_a.txid();
|
let txid_a = tx_a.txid();
|
||||||
let txid_b = tx_b.txid();
|
let txid_b = tx_b.txid();
|
||||||
|
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let _ = graph.insert_tx(tx_a);
|
let _ = graph.insert_tx(tx_a);
|
||||||
let _ = graph.insert_tx(tx_b);
|
let _ = graph.insert_tx(tx_b);
|
||||||
|
|
||||||
@ -480,7 +487,7 @@ fn test_descendants_no_repeat() {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut graph = TxGraph::default();
|
let mut graph = TxGraph::<(u32, BlockHash)>::default();
|
||||||
let mut expected_txids = BTreeSet::new();
|
let mut expected_txids = BTreeSet::new();
|
||||||
|
|
||||||
// these are NOT descendants of `tx_a`
|
// these are NOT descendants of `tx_a`
|
||||||
|
@ -32,7 +32,7 @@ use bdk_chain::{
|
|||||||
keychain::KeychainScan,
|
keychain::KeychainScan,
|
||||||
sparse_chain::{self, ChainPosition, SparseChain},
|
sparse_chain::{self, ChainPosition, SparseChain},
|
||||||
tx_graph::TxGraph,
|
tx_graph::TxGraph,
|
||||||
BlockId, ConfirmationTime, TxHeight,
|
BlockAnchor, BlockId, ConfirmationTime, TxHeight,
|
||||||
};
|
};
|
||||||
pub use electrum_client;
|
pub use electrum_client;
|
||||||
use electrum_client::{Client, ElectrumApi, Error};
|
use electrum_client::{Client, ElectrumApi, Error};
|
||||||
@ -243,13 +243,14 @@ impl<K: Ord + Clone + Debug, P: ChainPosition> ElectrumUpdate<K, P> {
|
|||||||
/// `tracker`.
|
/// `tracker`.
|
||||||
///
|
///
|
||||||
/// This will fail if there are missing full transactions not provided via `new_txs`.
|
/// This will fail if there are missing full transactions not provided via `new_txs`.
|
||||||
pub fn into_keychain_scan<CG>(
|
pub fn into_keychain_scan<CG, A>(
|
||||||
self,
|
self,
|
||||||
new_txs: Vec<Transaction>,
|
new_txs: Vec<Transaction>,
|
||||||
chain_graph: &CG,
|
chain_graph: &CG,
|
||||||
) -> Result<KeychainScan<K, P>, chain_graph::NewError<P>>
|
) -> Result<KeychainScan<K, A, P>, chain_graph::NewError<P>>
|
||||||
where
|
where
|
||||||
CG: AsRef<ChainGraph<P>>,
|
CG: AsRef<ChainGraph<A, P>>,
|
||||||
|
A: BlockAnchor,
|
||||||
{
|
{
|
||||||
Ok(KeychainScan {
|
Ok(KeychainScan {
|
||||||
update: chain_graph
|
update: chain_graph
|
||||||
|
@ -48,7 +48,7 @@ pub trait EsploraAsyncExt {
|
|||||||
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<KeychainScan<K, ConfirmationTime>, Error>;
|
) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error>;
|
||||||
|
|
||||||
/// Convenience method to call [`scan`] without requiring a keychain.
|
/// Convenience method to call [`scan`] without requiring a keychain.
|
||||||
///
|
///
|
||||||
@ -61,7 +61,7 @@ pub trait EsploraAsyncExt {
|
|||||||
txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
|
txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
|
||||||
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<ChainGraph<ConfirmationTime>, Error> {
|
) -> Result<ChainGraph<BlockId, ConfirmationTime>, Error> {
|
||||||
let wallet_scan = self
|
let wallet_scan = self
|
||||||
.scan(
|
.scan(
|
||||||
local_chain,
|
local_chain,
|
||||||
@ -100,7 +100,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
|
|||||||
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<KeychainScan<K, ConfirmationTime>, Error> {
|
) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error> {
|
||||||
let txids = txids.into_iter();
|
let txids = txids.into_iter();
|
||||||
let outpoints = outpoints.into_iter();
|
let outpoints = outpoints.into_iter();
|
||||||
let parallel_requests = parallel_requests.max(1);
|
let parallel_requests = parallel_requests.max(1);
|
||||||
|
@ -38,7 +38,7 @@ pub trait EsploraExt {
|
|||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<KeychainScan<K, ConfirmationTime>, Error>;
|
) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error>;
|
||||||
|
|
||||||
/// Convenience method to call [`scan`] without requiring a keychain.
|
/// Convenience method to call [`scan`] without requiring a keychain.
|
||||||
///
|
///
|
||||||
@ -51,7 +51,7 @@ pub trait EsploraExt {
|
|||||||
txids: impl IntoIterator<Item = Txid>,
|
txids: impl IntoIterator<Item = Txid>,
|
||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<ChainGraph<ConfirmationTime>, Error> {
|
) -> Result<ChainGraph<BlockId, ConfirmationTime>, Error> {
|
||||||
let wallet_scan = self.scan(
|
let wallet_scan = self.scan(
|
||||||
local_chain,
|
local_chain,
|
||||||
[(
|
[(
|
||||||
@ -81,7 +81,7 @@ impl EsploraExt for esplora_client::BlockingClient {
|
|||||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||||
stop_gap: usize,
|
stop_gap: usize,
|
||||||
parallel_requests: usize,
|
parallel_requests: usize,
|
||||||
) -> Result<KeychainScan<K, ConfirmationTime>, Error> {
|
) -> Result<KeychainScan<K, BlockId, ConfirmationTime>, Error> {
|
||||||
let parallel_requests = parallel_requests.max(1);
|
let parallel_requests = parallel_requests.max(1);
|
||||||
let mut scan = KeychainScan::default();
|
let mut scan = KeychainScan::default();
|
||||||
let update = &mut scan.update;
|
let update = &mut scan.update;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
//! [`KeychainChangeSet`]s which can be used to restore a [`KeychainTracker`].
|
//! [`KeychainChangeSet`]s which can be used to restore a [`KeychainTracker`].
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
keychain::{KeychainChangeSet, KeychainTracker},
|
keychain::{KeychainChangeSet, KeychainTracker},
|
||||||
sparse_chain,
|
sparse_chain, BlockAnchor,
|
||||||
};
|
};
|
||||||
use bincode::{DefaultOptions, Options};
|
use bincode::{DefaultOptions, Options};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
@ -23,20 +23,21 @@ const MAGIC_BYTES: [u8; MAGIC_BYTES_LEN] = [98, 100, 107, 102, 115, 48, 48, 48,
|
|||||||
/// Persists an append only list of `KeychainChangeSet<K,P>` to a single file.
|
/// Persists an append only list of `KeychainChangeSet<K,P>` to a single file.
|
||||||
/// [`KeychainChangeSet<K,P>`] record the changes made to a [`KeychainTracker<K,P>`].
|
/// [`KeychainChangeSet<K,P>`] record the changes made to a [`KeychainTracker<K,P>`].
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct KeychainStore<K, P> {
|
pub struct KeychainStore<K, A, P> {
|
||||||
db_file: File,
|
db_file: File,
|
||||||
changeset_type_params: core::marker::PhantomData<(K, P)>,
|
changeset_type_params: core::marker::PhantomData<(K, A, P)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bincode() -> impl bincode::Options {
|
fn bincode() -> impl bincode::Options {
|
||||||
DefaultOptions::new().with_varint_encoding()
|
DefaultOptions::new().with_varint_encoding()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, P> KeychainStore<K, P>
|
impl<K, A, P> KeychainStore<K, A, P>
|
||||||
where
|
where
|
||||||
K: Ord + Clone + core::fmt::Debug,
|
K: Ord + Clone + core::fmt::Debug,
|
||||||
|
A: BlockAnchor,
|
||||||
P: sparse_chain::ChainPosition,
|
P: sparse_chain::ChainPosition,
|
||||||
KeychainChangeSet<K, P>: serde::Serialize + serde::de::DeserializeOwned,
|
KeychainChangeSet<K, A, P>: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
/// Creates a new store from a [`File`].
|
/// Creates a new store from a [`File`].
|
||||||
///
|
///
|
||||||
@ -85,7 +86,9 @@ where
|
|||||||
/// **WARNING**: This method changes the write position in the underlying file. You should
|
/// **WARNING**: This method changes the write position in the underlying file. You should
|
||||||
/// always iterate over all entries until `None` is returned if you want your next write to go
|
/// always iterate over all entries until `None` is returned if you want your next write to go
|
||||||
/// at the end; otherwise, you will write over existing entries.
|
/// at the end; otherwise, you will write over existing entries.
|
||||||
pub fn iter_changesets(&mut self) -> Result<EntryIter<'_, KeychainChangeSet<K, P>>, io::Error> {
|
pub fn iter_changesets(
|
||||||
|
&mut self,
|
||||||
|
) -> Result<EntryIter<'_, KeychainChangeSet<K, A, P>>, io::Error> {
|
||||||
self.db_file
|
self.db_file
|
||||||
.seek(io::SeekFrom::Start(MAGIC_BYTES_LEN as _))?;
|
.seek(io::SeekFrom::Start(MAGIC_BYTES_LEN as _))?;
|
||||||
|
|
||||||
@ -104,7 +107,7 @@ where
|
|||||||
///
|
///
|
||||||
/// **WARNING**: This method changes the write position of the underlying file. The next
|
/// **WARNING**: This method changes the write position of the underlying file. The next
|
||||||
/// changeset will be written over the erroring entry (or the end of the file if none existed).
|
/// changeset will be written over the erroring entry (or the end of the file if none existed).
|
||||||
pub fn aggregate_changeset(&mut self) -> (KeychainChangeSet<K, P>, Result<(), IterError>) {
|
pub fn aggregate_changeset(&mut self) -> (KeychainChangeSet<K, A, P>, Result<(), IterError>) {
|
||||||
let mut changeset = KeychainChangeSet::default();
|
let mut changeset = KeychainChangeSet::default();
|
||||||
let result = (|| {
|
let result = (|| {
|
||||||
let iter_changeset = self.iter_changesets()?;
|
let iter_changeset = self.iter_changesets()?;
|
||||||
@ -124,7 +127,7 @@ where
|
|||||||
/// changeset will be written over the erroring entry (or the end of the file if none existed).
|
/// changeset will be written over the erroring entry (or the end of the file if none existed).
|
||||||
pub fn load_into_keychain_tracker(
|
pub fn load_into_keychain_tracker(
|
||||||
&mut self,
|
&mut self,
|
||||||
tracker: &mut KeychainTracker<K, P>,
|
tracker: &mut KeychainTracker<K, A, P>,
|
||||||
) -> Result<(), IterError> {
|
) -> Result<(), IterError> {
|
||||||
for changeset in self.iter_changesets()? {
|
for changeset in self.iter_changesets()? {
|
||||||
tracker.apply_changeset(changeset?)
|
tracker.apply_changeset(changeset?)
|
||||||
@ -138,7 +141,7 @@ where
|
|||||||
/// directly after the appended changeset.
|
/// directly after the appended changeset.
|
||||||
pub fn append_changeset(
|
pub fn append_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
changeset: &KeychainChangeSet<K, P>,
|
changeset: &KeychainChangeSet<K, A, P>,
|
||||||
) -> Result<(), io::Error> {
|
) -> Result<(), io::Error> {
|
||||||
if changeset.is_empty() {
|
if changeset.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -288,7 +291,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
keychain::{DerivationAdditions, KeychainChangeSet},
|
keychain::{DerivationAdditions, KeychainChangeSet},
|
||||||
TxHeight,
|
BlockId, TxHeight,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
@ -332,7 +335,7 @@ mod test {
|
|||||||
file.write_all(&MAGIC_BYTES[..MAGIC_BYTES_LEN - 1])
|
file.write_all(&MAGIC_BYTES[..MAGIC_BYTES_LEN - 1])
|
||||||
.expect("should write");
|
.expect("should write");
|
||||||
|
|
||||||
match KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap()) {
|
match KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap()) {
|
||||||
Err(FileError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof),
|
Err(FileError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof),
|
||||||
unexpected => panic!("unexpected result: {:?}", unexpected),
|
unexpected => panic!("unexpected result: {:?}", unexpected),
|
||||||
};
|
};
|
||||||
@ -346,7 +349,7 @@ mod test {
|
|||||||
file.write_all(invalid_magic_bytes.as_bytes())
|
file.write_all(invalid_magic_bytes.as_bytes())
|
||||||
.expect("should write");
|
.expect("should write");
|
||||||
|
|
||||||
match KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap()) {
|
match KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap()) {
|
||||||
Err(FileError::InvalidMagicBytes(b)) => {
|
Err(FileError::InvalidMagicBytes(b)) => {
|
||||||
assert_eq!(b, invalid_magic_bytes.as_bytes())
|
assert_eq!(b, invalid_magic_bytes.as_bytes())
|
||||||
}
|
}
|
||||||
@ -370,8 +373,9 @@ mod test {
|
|||||||
let mut file = NamedTempFile::new().unwrap();
|
let mut file = NamedTempFile::new().unwrap();
|
||||||
file.write_all(&data).expect("should write");
|
file.write_all(&data).expect("should write");
|
||||||
|
|
||||||
let mut store = KeychainStore::<TestKeychain, TxHeight>::new(file.reopen().unwrap())
|
let mut store =
|
||||||
.expect("should open");
|
KeychainStore::<TestKeychain, BlockId, TxHeight>::new(file.reopen().unwrap())
|
||||||
|
.expect("should open");
|
||||||
match store.iter_changesets().expect("seek should succeed").next() {
|
match store.iter_changesets().expect("seek should succeed").next() {
|
||||||
Some(Err(IterError::Bincode(_))) => {}
|
Some(Err(IterError::Bincode(_))) => {}
|
||||||
unexpected_res => panic!("unexpected result: {:?}", unexpected_res),
|
unexpected_res => panic!("unexpected result: {:?}", unexpected_res),
|
||||||
|
@ -3,14 +3,16 @@ mod file_store;
|
|||||||
use bdk_chain::{
|
use bdk_chain::{
|
||||||
keychain::{KeychainChangeSet, KeychainTracker, PersistBackend},
|
keychain::{KeychainChangeSet, KeychainTracker, PersistBackend},
|
||||||
sparse_chain::ChainPosition,
|
sparse_chain::ChainPosition,
|
||||||
|
BlockAnchor,
|
||||||
};
|
};
|
||||||
pub use file_store::*;
|
pub use file_store::*;
|
||||||
|
|
||||||
impl<K, P> PersistBackend<K, P> for KeychainStore<K, P>
|
impl<K, A, P> PersistBackend<K, A, P> for KeychainStore<K, A, P>
|
||||||
where
|
where
|
||||||
K: Ord + Clone + core::fmt::Debug,
|
K: Ord + Clone + core::fmt::Debug,
|
||||||
|
A: BlockAnchor,
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
KeychainChangeSet<K, P>: serde::Serialize + serde::de::DeserializeOwned,
|
KeychainChangeSet<K, A, P>: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
type WriteError = std::io::Error;
|
type WriteError = std::io::Error;
|
||||||
|
|
||||||
@ -18,14 +20,14 @@ where
|
|||||||
|
|
||||||
fn append_changeset(
|
fn append_changeset(
|
||||||
&mut self,
|
&mut self,
|
||||||
changeset: &KeychainChangeSet<K, P>,
|
changeset: &KeychainChangeSet<K, A, P>,
|
||||||
) -> Result<(), Self::WriteError> {
|
) -> Result<(), Self::WriteError> {
|
||||||
KeychainStore::append_changeset(self, changeset)
|
KeychainStore::append_changeset(self, changeset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_into_keychain_tracker(
|
fn load_into_keychain_tracker(
|
||||||
&mut self,
|
&mut self,
|
||||||
tracker: &mut KeychainTracker<K, P>,
|
tracker: &mut KeychainTracker<K, A, P>,
|
||||||
) -> Result<(), Self::LoadError> {
|
) -> Result<(), Self::LoadError> {
|
||||||
KeychainStore::load_into_keychain_tracker(self, tracker)
|
KeychainStore::load_into_keychain_tracker(self, tracker)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ pub struct ScanOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, tracker, db) = cli::init::<ElectrumCommands, _>()?;
|
let (args, keymap, tracker, db) = cli::init::<ElectrumCommands, _, _>()?;
|
||||||
|
|
||||||
let electrum_url = match args.network {
|
let electrum_url = match args.network {
|
||||||
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
|
Network::Bitcoin => "ssl://electrum.blockstream.info:50002",
|
||||||
|
@ -49,7 +49,7 @@ pub struct ScanOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let (args, keymap, keychain_tracker, db) = cli::init::<EsploraCommands, _>()?;
|
let (args, keymap, keychain_tracker, db) = cli::init::<EsploraCommands, _, _>()?;
|
||||||
let esplora_url = match args.network {
|
let esplora_url = match args.network {
|
||||||
Network::Bitcoin => "https://mempool.space/api",
|
Network::Bitcoin => "https://mempool.space/api",
|
||||||
Network::Testnet => "https://mempool.space/testnet/api",
|
Network::Testnet => "https://mempool.space/testnet/api",
|
||||||
|
@ -13,7 +13,7 @@ use bdk_chain::{
|
|||||||
Descriptor, DescriptorPublicKey,
|
Descriptor, DescriptorPublicKey,
|
||||||
},
|
},
|
||||||
sparse_chain::{self, ChainPosition},
|
sparse_chain::{self, ChainPosition},
|
||||||
DescriptorExt, FullTxOut,
|
BlockAnchor, DescriptorExt, FullTxOut,
|
||||||
};
|
};
|
||||||
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
|
use bdk_coin_select::{coin_select_bnb, CoinSelector, CoinSelectorOpt, WeightedValue};
|
||||||
use bdk_file_store::KeychainStore;
|
use bdk_file_store::KeychainStore;
|
||||||
@ -179,15 +179,16 @@ pub struct AddrsOutput {
|
|||||||
used: bool,
|
used: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_address_cmd<P>(
|
pub fn run_address_cmd<A, P>(
|
||||||
tracker: &Mutex<KeychainTracker<Keychain, P>>,
|
tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
|
||||||
db: &Mutex<KeychainStore<Keychain, P>>,
|
db: &Mutex<KeychainStore<Keychain, A, P>>,
|
||||||
addr_cmd: AddressCmd,
|
addr_cmd: AddressCmd,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
|
A: bdk_chain::BlockAnchor,
|
||||||
P: bdk_chain::sparse_chain::ChainPosition,
|
P: bdk_chain::sparse_chain::ChainPosition,
|
||||||
KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
|
KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
let mut tracker = tracker.lock().unwrap();
|
let mut tracker = tracker.lock().unwrap();
|
||||||
let txout_index = &mut tracker.txout_index;
|
let txout_index = &mut tracker.txout_index;
|
||||||
@ -241,7 +242,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_balance_cmd<P: ChainPosition>(tracker: &Mutex<KeychainTracker<Keychain, P>>) {
|
pub fn run_balance_cmd<A: BlockAnchor, P: ChainPosition>(
|
||||||
|
tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
|
||||||
|
) {
|
||||||
let tracker = tracker.lock().unwrap();
|
let tracker = tracker.lock().unwrap();
|
||||||
let (confirmed, unconfirmed) =
|
let (confirmed, unconfirmed) =
|
||||||
tracker
|
tracker
|
||||||
@ -258,9 +261,9 @@ pub fn run_balance_cmd<P: ChainPosition>(tracker: &Mutex<KeychainTracker<Keychai
|
|||||||
println!("unconfirmed: {}", unconfirmed);
|
println!("unconfirmed: {}", unconfirmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_txo_cmd<K: Debug + Clone + Ord, P: ChainPosition>(
|
pub fn run_txo_cmd<K: Debug + Clone + Ord, A: BlockAnchor, P: ChainPosition>(
|
||||||
txout_cmd: TxOutCmd,
|
txout_cmd: TxOutCmd,
|
||||||
tracker: &Mutex<KeychainTracker<K, P>>,
|
tracker: &Mutex<KeychainTracker<K, A, P>>,
|
||||||
network: Network,
|
network: Network,
|
||||||
) {
|
) {
|
||||||
match txout_cmd {
|
match txout_cmd {
|
||||||
@ -313,11 +316,11 @@ pub fn run_txo_cmd<K: Debug + Clone + Ord, P: ChainPosition>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)] // FIXME
|
#[allow(clippy::type_complexity)] // FIXME
|
||||||
pub fn create_tx<P: ChainPosition>(
|
pub fn create_tx<A: BlockAnchor, P: ChainPosition>(
|
||||||
value: u64,
|
value: u64,
|
||||||
address: Address,
|
address: Address,
|
||||||
coin_select: CoinSelectionAlgo,
|
coin_select: CoinSelectionAlgo,
|
||||||
keychain_tracker: &mut KeychainTracker<Keychain, P>,
|
keychain_tracker: &mut KeychainTracker<Keychain, A, P>,
|
||||||
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
|
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
Transaction,
|
Transaction,
|
||||||
@ -526,19 +529,20 @@ pub fn create_tx<P: ChainPosition>(
|
|||||||
Ok((transaction, change_info))
|
Ok((transaction, change_info))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_commands<C: clap::Subcommand, P>(
|
pub fn handle_commands<C: clap::Subcommand, A, P>(
|
||||||
command: Commands<C>,
|
command: Commands<C>,
|
||||||
broadcast: impl FnOnce(&Transaction) -> Result<()>,
|
broadcast: impl FnOnce(&Transaction) -> Result<()>,
|
||||||
// we Mutex around these not because we need them for a simple CLI app but to demonstrate how
|
// we Mutex around these not because we need them for a simple CLI app but to demonstrate how
|
||||||
// all the stuff we're doing can be made thread-safe and not keep locks up over an IO bound.
|
// all the stuff we're doing can be made thread-safe and not keep locks up over an IO bound.
|
||||||
tracker: &Mutex<KeychainTracker<Keychain, P>>,
|
tracker: &Mutex<KeychainTracker<Keychain, A, P>>,
|
||||||
store: &Mutex<KeychainStore<Keychain, P>>,
|
store: &Mutex<KeychainStore<Keychain, A, P>>,
|
||||||
network: Network,
|
network: Network,
|
||||||
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
|
keymap: &HashMap<DescriptorPublicKey, DescriptorSecretKey>,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
|
A: BlockAnchor,
|
||||||
P: ChainPosition,
|
P: ChainPosition,
|
||||||
KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
|
KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
match command {
|
match command {
|
||||||
// TODO: Make these functions return stuffs
|
// TODO: Make these functions return stuffs
|
||||||
@ -619,17 +623,18 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)] // FIXME
|
#[allow(clippy::type_complexity)] // FIXME
|
||||||
pub fn init<C: clap::Subcommand, P>() -> anyhow::Result<(
|
pub fn init<C: clap::Subcommand, A, P>() -> anyhow::Result<(
|
||||||
Args<C>,
|
Args<C>,
|
||||||
KeyMap,
|
KeyMap,
|
||||||
// These don't need to have mutexes around them, but we want the cli example code to make it obvious how they
|
// These don't need to have mutexes around them, but we want the cli example code to make it obvious how they
|
||||||
// are thread-safe, forcing the example developers to show where they would lock and unlock things.
|
// are thread-safe, forcing the example developers to show where they would lock and unlock things.
|
||||||
Mutex<KeychainTracker<Keychain, P>>,
|
Mutex<KeychainTracker<Keychain, A, P>>,
|
||||||
Mutex<KeychainStore<Keychain, P>>,
|
Mutex<KeychainStore<Keychain, A, P>>,
|
||||||
)>
|
)>
|
||||||
where
|
where
|
||||||
|
A: BlockAnchor,
|
||||||
P: sparse_chain::ChainPosition,
|
P: sparse_chain::ChainPosition,
|
||||||
KeychainChangeSet<Keychain, P>: serde::Serialize + serde::de::DeserializeOwned,
|
KeychainChangeSet<Keychain, A, P>: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
let args = Args::<C>::parse();
|
let args = Args::<C>::parse();
|
||||||
let secp = Secp256k1::default();
|
let secp = Secp256k1::default();
|
||||||
@ -655,7 +660,7 @@ where
|
|||||||
.add_keychain(Keychain::Internal, internal_descriptor);
|
.add_keychain(Keychain::Internal, internal_descriptor);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut db = KeychainStore::<Keychain, P>::new_from_path(args.db_path.as_path())?;
|
let mut db = KeychainStore::<Keychain, A, P>::new_from_path(args.db_path.as_path())?;
|
||||||
|
|
||||||
if let Err(e) = db.load_into_keychain_tracker(&mut tracker) {
|
if let Err(e) = db.load_into_keychain_tracker(&mut tracker) {
|
||||||
match tracker.chain().latest_checkpoint() {
|
match tracker.chain().latest_checkpoint() {
|
||||||
@ -669,8 +674,8 @@ where
|
|||||||
Ok((args, keymap, Mutex::new(tracker), Mutex::new(db)))
|
Ok((args, keymap, Mutex::new(tracker), Mutex::new(db)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn planned_utxos<'a, AK: bdk_tmp_plan::CanDerive + Clone, P: ChainPosition>(
|
pub fn planned_utxos<'a, AK: bdk_tmp_plan::CanDerive + Clone, A: BlockAnchor, P: ChainPosition>(
|
||||||
tracker: &'a KeychainTracker<Keychain, P>,
|
tracker: &'a KeychainTracker<Keychain, A, P>,
|
||||||
assets: &'a bdk_tmp_plan::Assets<AK>,
|
assets: &'a bdk_tmp_plan::Assets<AK>,
|
||||||
) -> impl Iterator<Item = (bdk_tmp_plan::Plan<AK>, FullTxOut<P>)> + 'a {
|
) -> impl Iterator<Item = (bdk_tmp_plan::Plan<AK>, FullTxOut<P>)> + 'a {
|
||||||
tracker
|
tracker
|
||||||
|
Loading…
x
Reference in New Issue
Block a user