Skip to content

Commit 34d6087

Browse files
committed
Merge #975: Improve txout listing and balance APIs for redesigned structures
ed89de7 Improve txout filter/listing method docs for `TxGraph` (志宇) fb75aa9 Clarify `TxGraph::try_filter_chain_unspents` logic (志宇) 96b1075 Fix and improve `Persist::commit` method (志宇) e01d17d Improve `txout` listing and balance APIs for redesigned structures (志宇) Pull request description: ### Description As noted in #971 (comment). Instead of relying on a `OwnedIndexer` trait to filter for relevant txouts, we move the listing and balance methods from `IndexedTxGraph` to `TxGraph` and add an additional input (list of relevant outpoints) to these methods. This provides a simpler implementation and a more flexible API. #### Other Fixes The `Persist::commit` method is fixed in 96b1075. Previously, regardless of whether writing to persistence backend is successful or not, the logic always cleared `self.staged`. This is changed to only clear `self.staged` after successful write. Additionally, the written changeset (if any) is returned, and `PersistBackend::write_changes` will not be called if `self.staged` is empty. ### Notes to the reviewers Yes, slightly more boilerplate to do the same things, but less code to maintain and a much more flexible API. Very worth it IMO. ### Changelog notice ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature ACKs for top commit: LLFourn: ACK ed89de7 Tree-SHA512: efae18c13ee74eff801febca61cb16bf4d8f3203ced96172b9ef555d8d2a749a382f87d024e8c92aacbab7eb4e50a8337de798e10524291ad80c6b35c4dc0161
2 parents 05d353c + ed89de7 commit 34d6087

File tree

6 files changed

+221
-217
lines changed

6 files changed

+221
-217
lines changed

crates/chain/src/indexed_tx_graph.rs

Lines changed: 10 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
use core::convert::Infallible;
2-
31
use alloc::vec::Vec;
4-
use bitcoin::{OutPoint, Script, Transaction, TxOut};
2+
use bitcoin::{OutPoint, Transaction, TxOut};
53

64
use crate::{
7-
keychain::Balance,
85
tx_graph::{Additions, TxGraph},
9-
Anchor, Append, BlockId, ChainOracle, FullTxOut, ObservedAs,
6+
Anchor, Append,
107
};
118

129
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
@@ -29,6 +26,14 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
2926
}
3027

3128
impl<A, I> IndexedTxGraph<A, I> {
29+
/// Construct a new [`IndexedTxGraph`] with a given `index`.
30+
pub fn new(index: I) -> Self {
31+
Self {
32+
index,
33+
graph: TxGraph::default(),
34+
}
35+
}
36+
3237
/// Get a reference of the internal transaction graph.
3338
pub fn graph(&self) -> &TxGraph<A> {
3439
&self.graph
@@ -157,115 +162,6 @@ where
157162
}
158163
}
159164

160-
impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
161-
pub fn try_list_owned_txouts<'a, C: ChainOracle + 'a>(
162-
&'a self,
163-
chain: &'a C,
164-
chain_tip: BlockId,
165-
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
166-
self.graph()
167-
.try_list_chain_txouts(chain, chain_tip)
168-
.filter(|r| {
169-
if let Ok(full_txout) = r {
170-
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
171-
return false;
172-
}
173-
}
174-
true
175-
})
176-
}
177-
178-
pub fn list_owned_txouts<'a, C: ChainOracle<Error = Infallible> + 'a>(
179-
&'a self,
180-
chain: &'a C,
181-
chain_tip: BlockId,
182-
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
183-
self.try_list_owned_txouts(chain, chain_tip)
184-
.map(|r| r.expect("oracle is infallible"))
185-
}
186-
187-
pub fn try_list_owned_unspents<'a, C: ChainOracle + 'a>(
188-
&'a self,
189-
chain: &'a C,
190-
chain_tip: BlockId,
191-
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
192-
self.graph()
193-
.try_list_chain_unspents(chain, chain_tip)
194-
.filter(|r| {
195-
if let Ok(full_txout) = r {
196-
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
197-
return false;
198-
}
199-
}
200-
true
201-
})
202-
}
203-
204-
pub fn list_owned_unspents<'a, C: ChainOracle<Error = Infallible> + 'a>(
205-
&'a self,
206-
chain: &'a C,
207-
chain_tip: BlockId,
208-
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
209-
self.try_list_owned_unspents(chain, chain_tip)
210-
.map(|r| r.expect("oracle is infallible"))
211-
}
212-
213-
pub fn try_balance<C, F>(
214-
&self,
215-
chain: &C,
216-
chain_tip: BlockId,
217-
mut should_trust: F,
218-
) -> Result<Balance, C::Error>
219-
where
220-
C: ChainOracle,
221-
F: FnMut(&Script) -> bool,
222-
{
223-
let tip_height = chain_tip.height;
224-
225-
let mut immature = 0;
226-
let mut trusted_pending = 0;
227-
let mut untrusted_pending = 0;
228-
let mut confirmed = 0;
229-
230-
for res in self.try_list_owned_unspents(chain, chain_tip) {
231-
let txout = res?;
232-
233-
match &txout.chain_position {
234-
ObservedAs::Confirmed(_) => {
235-
if txout.is_confirmed_and_spendable(tip_height) {
236-
confirmed += txout.txout.value;
237-
} else if !txout.is_mature(tip_height) {
238-
immature += txout.txout.value;
239-
}
240-
}
241-
ObservedAs::Unconfirmed(_) => {
242-
if should_trust(&txout.txout.script_pubkey) {
243-
trusted_pending += txout.txout.value;
244-
} else {
245-
untrusted_pending += txout.txout.value;
246-
}
247-
}
248-
}
249-
}
250-
251-
Ok(Balance {
252-
immature,
253-
trusted_pending,
254-
untrusted_pending,
255-
confirmed,
256-
})
257-
}
258-
259-
pub fn balance<C, F>(&self, chain: &C, chain_tip: BlockId, should_trust: F) -> Balance
260-
where
261-
C: ChainOracle<Error = Infallible>,
262-
F: FnMut(&Script) -> bool,
263-
{
264-
self.try_balance(chain, chain_tip, should_trust)
265-
.expect("error is infallible")
266-
}
267-
}
268-
269165
/// A structure that represents changes to an [`IndexedTxGraph`].
270166
#[derive(Clone, Debug, PartialEq)]
271167
#[cfg_attr(
@@ -324,9 +220,3 @@ pub trait Indexer {
324220
/// Determines whether the transaction should be included in the index.
325221
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
326222
}
327-
328-
/// A trait that extends [`Indexer`] to also index "owned" script pubkeys.
329-
pub trait OwnedIndexer: Indexer {
330-
/// Determines whether a given script pubkey (`spk`) is owned.
331-
fn is_spk_owned(&self, spk: &Script) -> bool;
332-
}

crates/chain/src/keychain/txout_index.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
collections::*,
3-
indexed_tx_graph::{Indexer, OwnedIndexer},
3+
indexed_tx_graph::Indexer,
44
miniscript::{Descriptor, DescriptorPublicKey},
55
spk_iter::BIP32_MAX_INDEX,
66
ForEachTxOut, SpkIterator, SpkTxOutIndex,
@@ -109,12 +109,6 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
109109
}
110110
}
111111

112-
impl<K: Clone + Ord + Debug> OwnedIndexer for KeychainTxOutIndex<K> {
113-
fn is_spk_owned(&self, spk: &Script) -> bool {
114-
self.index_of_spk(spk).is_some()
115-
}
116-
}
117-
118112
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
119113
/// Scans an object for relevant outpoints, which are stored and indexed internally.
120114
///
@@ -153,6 +147,11 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
153147
&self.inner
154148
}
155149

150+
/// Get a reference to the set of indexed outpoints.
151+
pub fn outpoints(&self) -> &BTreeSet<((K, u32), OutPoint)> {
152+
self.inner.outpoints()
153+
}
154+
156155
/// Return a reference to the internal map of the keychain to descriptors.
157156
pub fn keychains(&self) -> &BTreeMap<K, Descriptor<DescriptorPublicKey>> {
158157
&self.keychains

crates/chain/src/persist.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ where
4141

4242
/// Commit the staged changes to the underlying persistance backend.
4343
///
44+
/// Changes that are committed (if any) are returned.
45+
///
46+
/// # Error
47+
///
4448
/// Returns a backend-defined error if this fails.
45-
pub fn commit(&mut self) -> Result<(), B::WriteError> {
46-
let mut temp = C::default();
47-
core::mem::swap(&mut temp, &mut self.stage);
48-
self.backend.write_changes(&temp)
49+
pub fn commit(&mut self) -> Result<Option<C>, B::WriteError> {
50+
if self.stage.is_empty() {
51+
return Ok(None);
52+
}
53+
self.backend
54+
.write_changes(&self.stage)
55+
// if written successfully, take and return `self.stage`
56+
.map(|_| Some(core::mem::take(&mut self.stage)))
4957
}
5058
}
5159

crates/chain/src/spk_txout_index.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use core::ops::RangeBounds;
22

33
use crate::{
44
collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap},
5-
indexed_tx_graph::{Indexer, OwnedIndexer},
5+
indexed_tx_graph::Indexer,
66
ForEachTxOut,
77
};
88
use bitcoin::{self, OutPoint, Script, Transaction, TxOut, Txid};
@@ -75,12 +75,6 @@ impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
7575
}
7676
}
7777

78-
impl<I: Clone + Ord + 'static> OwnedIndexer for SpkTxOutIndex<I> {
79-
fn is_spk_owned(&self, spk: &Script) -> bool {
80-
self.spk_indices.get(spk).is_some()
81-
}
82-
}
83-
8478
/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a
8579
/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a
8680
/// reference out of the `ForEachTxOut` closure during scanning.
@@ -126,6 +120,11 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
126120
scan_txout!(self, op, txout)
127121
}
128122

123+
/// Get a reference to the set of indexed outpoints.
124+
pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> {
125+
&self.spk_txouts
126+
}
127+
129128
/// Iterate over all known txouts that spend to tracked script pubkeys.
130129
pub fn txouts(
131130
&self,

0 commit comments

Comments
 (0)