Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5ae5fe3
[bdk_chain_redesign] Introduce `BlockAnchor` trait
evanlinjin Mar 24, 2023
61a8606
[bdk_chain_redesign] Introduce `ChainOracle` and `TxIndex` traits
evanlinjin Mar 24, 2023
43b648f
[bdk_chain_redesign] Add `..in_chain` methods
evanlinjin Mar 26, 2023
784cd34
[bdk_chain_redesign] List chain data methods can be try/non-try
evanlinjin Mar 27, 2023
6cbb18d
[bdk_chain_redesign] MOVE: `IndexedTxGraph` into submodule
evanlinjin Mar 27, 2023
d0a2aa8
[bdk_chain_redesign] Add `apply_additions` to `IndexedTxGraph`
evanlinjin Mar 27, 2023
db7883d
[bdk_chain_redesign] Add balance methods to `IndexedTxGraph`
evanlinjin Mar 27, 2023
313965d
[bdk_chain_redesign] `mut_index` should be `index_mut`
evanlinjin Mar 27, 2023
e902c10
[bdk_chain_redesign] Fix `apply_additions` logic for `IndexedTxGraph`.
evanlinjin Mar 27, 2023
236c50f
[bdk_chain_redesign] `IndexedTxGraph` keeps track of the last synced …
evanlinjin Mar 27, 2023
3440a05
[bdk_chain_redesign] Add docs
evanlinjin Mar 28, 2023
34d0277
[bdk_chain_redesign] Rm anchor type param for structs that don't use it
evanlinjin Mar 28, 2023
468701a
[bdk_chain_redesign] Initial work on `LocalChain`.
evanlinjin Mar 29, 2023
8c90617
[bdk_chain_redesign] Make default anchor for `TxGraph` as `()`
evanlinjin Mar 30, 2023
a1172de
[bdk_chain_redesign] Revert some API changes
evanlinjin Mar 30, 2023
a63ffe9
[bdk_chain_redesign] Simplify `TxIndex`
evanlinjin Mar 31, 2023
7810059
[bdk_chain_redesign] `TxGraph` tweaks
evanlinjin Mar 31, 2023
c09cd2a
[bdk_chain_redesign] Added methods to `LocalChain`
evanlinjin Mar 31, 2023
a7eaebb
[bdk_chain_redesign] Add serde support for `IndexedAdditions`
evanlinjin Mar 31, 2023
6e59dce
[bdk_chain_redesign] `chain_oracle::Cache`
evanlinjin Apr 5, 2023
89cfa4d
[bdk_chain_redesign] Better names, comments and generic bounds
evanlinjin Apr 5, 2023
da4cef0
[bdk_chain_redesign] Introduce `Append` trait for additions
evanlinjin Apr 5, 2023
ddd5e95
[bdk_chain_redesign] Modify signature of `TxIndex`
evanlinjin Apr 5, 2023
24cd8c5
[bdk_chain_redesign] More tweaks and renamings
evanlinjin Apr 5, 2023
bff80ec
[bdk_chain_redesign] Improve `BlockAnchor` docs
evanlinjin Apr 7, 2023
611d2e3
[bdk_chain_redesign] Consistent `ChainOracle`
evanlinjin Apr 10, 2023
ee1060f
[bdk_chain_redesign] Simplify `LocalChain`
evanlinjin Apr 10, 2023
a7fbe0a
[bdk_chain_redesign] Documentation improvements
evanlinjin Apr 10, 2023
7d92337
[bdk_chain_redesign] Remove `IndexedTxGraph::last_height`
evanlinjin Apr 10, 2023
0ff20d4
trial
rajarshimaitra Apr 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions crates/bdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ impl<D> Wallet<D> {
network: Network,
) -> Result<Self, NewError<D::LoadError>>
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
let secp = Secp256k1::new();

Expand Down Expand Up @@ -257,7 +257,7 @@ impl<D> Wallet<D> {
/// (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
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
self._get_address(address_index, KeychainKind::External)
}
Expand All @@ -271,14 +271,14 @@ impl<D> Wallet<D> {
/// be returned for any [`AddressIndex`].
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
self._get_address(address_index, KeychainKind::Internal)
}

fn _get_address(&mut self, address_index: AddressIndex, keychain: KeychainKind) -> AddressInfo
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
let keychain = self.map_keychain(keychain);
let txout_index = &mut self.keychain_tracker.txout_index;
Expand Down Expand Up @@ -613,7 +613,7 @@ impl<D> Wallet<D> {
params: TxParams,
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
let external_descriptor = self
.keychain_tracker
Expand Down Expand Up @@ -1687,7 +1687,7 @@ impl<D> Wallet<D> {
/// [`commit`]: Self::commit
pub fn apply_update(&mut self, update: Update) -> Result<(), UpdateError>
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
let changeset = self.keychain_tracker.apply_update(update)?;
self.persist.stage(changeset);
Expand All @@ -1699,7 +1699,7 @@ impl<D> Wallet<D> {
/// [`staged`]: Self::staged
pub fn commit(&mut self) -> Result<(), D::WriteError>
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
self.persist.commit()
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bdk/src/wallet/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
where
D: persist::PersistBackend<KeychainKind, ConfirmationTime>,
D: persist::PersistBackendOld<KeychainKind, ConfirmationTime>,
{
self.wallet
.borrow_mut()
Expand Down
101 changes: 94 additions & 7 deletions crates/chain/src/chain_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@ use bitcoin::{hashes::Hash, BlockHash, OutPoint, TxOut, Txid};

use crate::{
sparse_chain::{self, ChainPosition},
COINBASE_MATURITY,
BlockAnchor, COINBASE_MATURITY,
};

/// Represents an observation of some chain data.
///
/// The generic `A` should be a [`BlockAnchor`] implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, core::hash::Hash)]
pub enum ObservedAs<A> {
/// The chain data is seen as confirmed, and in anchored by `A`.
Confirmed(A),
/// The chain data is seen in mempool at this given timestamp.
Unconfirmed(u64),
}

impl<A: Clone> ObservedAs<&A> {
pub fn cloned(self) -> ObservedAs<A> {
match self {
ObservedAs::Confirmed(a) => ObservedAs::Confirmed(a.clone()),
ObservedAs::Unconfirmed(last_seen) => ObservedAs::Unconfirmed(last_seen),
}
}
}

/// Represents the height at which a transaction is confirmed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
Expand Down Expand Up @@ -118,7 +138,7 @@ impl ConfirmationTime {
}

/// 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(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
Expand All @@ -140,6 +160,12 @@ impl Default for BlockId {
}
}

impl BlockAnchor for BlockId {
fn anchor_block(&self) -> BlockId {
*self
}
}

impl From<(u32, BlockHash)> for BlockId {
fn from((height, hash): (u32, BlockHash)) -> Self {
Self { height, hash }
Expand All @@ -162,21 +188,21 @@ impl From<(&u32, &BlockHash)> for BlockId {
}

/// A `TxOut` with as much data as we can retrieve about it
#[derive(Debug, Clone, PartialEq)]
pub struct FullTxOut<I> {
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FullTxOut<P> {
/// The location of the `TxOut`.
pub outpoint: OutPoint,
/// The `TxOut`.
pub txout: TxOut,
/// The position of the transaction in `outpoint` in the overall chain.
pub chain_position: I,
pub chain_position: P,
/// The txid and chain position of the transaction (if any) that has spent this output.
pub spent_by: Option<(I, Txid)>,
pub spent_by: Option<(P, Txid)>,
/// Whether this output is on a coinbase transaction.
pub is_on_coinbase: bool,
}

impl<I: ChainPosition> FullTxOut<I> {
impl<P: ChainPosition> FullTxOut<P> {
/// Whether the utxo is/was/will be spendable at `height`.
///
/// It is spendable if it is not an immature coinbase output and no spending tx has been
Expand Down Expand Up @@ -215,4 +241,65 @@ impl<I: ChainPosition> FullTxOut<I> {
}
}

impl<A: BlockAnchor> FullTxOut<ObservedAs<A>> {
/// Whether the `txout` is considered mature.
///
/// This is the alternative version of [`is_mature`] which depends on `chain_position` being a
/// [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
///
/// [`is_mature`]: Self::is_mature
pub fn is_observed_as_confirmed_and_mature(&self, tip: u32) -> bool {
if !self.is_on_coinbase {
return false;
}

let tx_height = match &self.chain_position {
ObservedAs::Confirmed(anchor) => anchor.anchor_block().height,
ObservedAs::Unconfirmed(_) => {
debug_assert!(false, "coinbase tx can never be unconfirmed");
return false;
}
};

let age = tip.saturating_sub(tx_height);
if age + 1 < COINBASE_MATURITY {
return false;
}

true
}

/// Whether the utxo is/was/will be spendable with chain `tip`.
///
/// Currently this method does not take into account the locktime.
///
/// This is the alternative version of [`is_spendable_at`] which depends on `chain_position`
/// being a [`ObservedAs<A>`] where `A` implements [`BlockAnchor`].
///
/// [`is_spendable_at`]: Self::is_spendable_at
pub fn is_observed_as_confirmed_and_spendable(&self, tip: u32) -> bool {
if !self.is_observed_as_confirmed_and_mature(tip) {
return false;
}

match &self.chain_position {
ObservedAs::Confirmed(anchor) => {
if anchor.anchor_block().height > tip {
return false;
}
}
ObservedAs::Unconfirmed(_) => return false,
};

// if the spending tx is confirmed within tip height, the txout is no longer spendable
if let Some((ObservedAs::Confirmed(spending_anchor), _)) = &self.spent_by {
if spending_anchor.anchor_block().height <= tip {
return false;
}
}

true
}
}

// TODO: make test
2 changes: 1 addition & 1 deletion crates/chain/src/chain_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ where
#[must_use]
pub struct ChangeSet<P> {
pub chain: sparse_chain::ChangeSet<P>,
pub graph: tx_graph::Additions,
pub graph: tx_graph::Additions<()>,
}

impl<P> ChangeSet<P> {
Expand Down
77 changes: 77 additions & 0 deletions crates/chain/src/chain_oracle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::collections::HashSet;
use core::marker::PhantomData;

use alloc::{collections::VecDeque, vec::Vec};
use bitcoin::BlockHash;

use crate::BlockId;

/// Represents a service that tracks the blockchain.
///
/// The main method is [`is_block_in_chain`] which determines whether a given block of [`BlockId`]
/// is an ancestor of another "static block".
///
/// [`is_block_in_chain`]: Self::is_block_in_chain
pub trait ChainOracle {
/// Error type.
type Error: core::fmt::Debug;

/// Determines whether `block` of [`BlockId`] exists as an ancestor of `static_block`.
///
/// If `None` is returned, it means the implementation cannot determine whether `block` exists.
fn is_block_in_chain(
&self,
block: BlockId,
static_block: BlockId,
) -> Result<Option<bool>, Self::Error>;
}

/// A cache structure increases the performance of getting chain data.
///
/// A simple FIFO cache replacement policy is used. Something more efficient and advanced can be
/// implemented later.
#[derive(Debug, Default)]
pub struct CacheBackend<C> {
cache: HashSet<(BlockHash, BlockHash)>,
fifo: VecDeque<(BlockHash, BlockHash)>,
marker: PhantomData<C>,
}

impl<C> CacheBackend<C> {
/// Get the number of elements in the cache.
pub fn cache_size(&self) -> usize {
self.cache.len()
}

/// Prunes the cache to reach the `max_size` target.
///
/// Returns pruned elements.
pub fn prune(&mut self, max_size: usize) -> Vec<(BlockHash, BlockHash)> {
let prune_count = self.cache.len().saturating_sub(max_size);
(0..prune_count)
.filter_map(|_| self.fifo.pop_front())
.filter(|k| self.cache.remove(k))
.collect()
}

pub fn contains(&self, static_block: BlockId, block: BlockId) -> bool {
if static_block.height < block.height
|| static_block.height == block.height && static_block.hash != block.hash
{
return false;
}

self.cache.contains(&(static_block.hash, block.hash))
}

pub fn insert(&mut self, static_block: BlockId, block: BlockId) -> bool {
let cache_key = (static_block.hash, block.hash);

if self.cache.insert(cache_key) {
self.fifo.push_back(cache_key);
true
} else {
false
}
}
}
Loading