2023-04-05 17:29:20 +08:00
|
|
|
use core::convert::Infallible;
|
2023-03-27 14:21:10 +08:00
|
|
|
|
2023-04-21 13:29:44 +08:00
|
|
|
use alloc::vec::Vec;
|
2023-04-21 14:39:13 +08:00
|
|
|
use bitcoin::{OutPoint, Script, Transaction, TxOut};
|
2023-03-27 14:21:10 +08:00
|
|
|
|
|
|
|
use crate::{
|
2023-03-27 19:55:57 +08:00
|
|
|
keychain::Balance,
|
2023-04-21 14:39:13 +08:00
|
|
|
tx_graph::{Additions, TxGraph},
|
2023-04-21 13:29:44 +08:00
|
|
|
Anchor, Append, BlockId, ChainOracle, FullTxOut, ObservedAs,
|
2023-03-27 14:21:10 +08:00
|
|
|
};
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
|
|
|
|
///
|
|
|
|
/// This structure ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
|
2023-03-27 14:21:10 +08:00
|
|
|
pub struct IndexedTxGraph<A, I> {
|
2023-03-31 22:42:47 +08:00
|
|
|
/// Transaction index.
|
|
|
|
pub index: I,
|
2023-03-27 14:21:10 +08:00
|
|
|
graph: TxGraph<A>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A, I: Default> Default for IndexedTxGraph<A, I> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
graph: Default::default(),
|
|
|
|
index: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
|
2023-03-27 14:21:10 +08:00
|
|
|
/// Get a reference of the internal transaction graph.
|
|
|
|
pub fn graph(&self) -> &TxGraph<A> {
|
|
|
|
&self.graph
|
|
|
|
}
|
|
|
|
|
2023-03-27 15:36:37 +08:00
|
|
|
/// Applies the [`IndexedAdditions`] to the [`IndexedTxGraph`].
|
|
|
|
pub fn apply_additions(&mut self, additions: IndexedAdditions<A, I::Additions>) {
|
|
|
|
let IndexedAdditions {
|
|
|
|
graph_additions,
|
2023-03-31 12:39:00 +08:00
|
|
|
index_additions,
|
2023-03-27 15:36:37 +08:00
|
|
|
} = additions;
|
2023-03-27 21:51:11 +08:00
|
|
|
|
2023-03-31 12:39:00 +08:00
|
|
|
self.index.apply_additions(index_additions);
|
2023-03-27 21:51:11 +08:00
|
|
|
|
|
|
|
for tx in &graph_additions.tx {
|
|
|
|
self.index.index_tx(tx);
|
|
|
|
}
|
|
|
|
for (&outpoint, txout) in &graph_additions.txout {
|
|
|
|
self.index.index_txout(outpoint, txout);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.graph.apply_additions(graph_additions);
|
2023-03-27 15:36:37 +08:00
|
|
|
}
|
2023-04-17 23:25:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
|
|
|
|
where
|
|
|
|
I::Additions: Default + Append,
|
|
|
|
{
|
|
|
|
/// Apply an `update` directly.
|
|
|
|
///
|
|
|
|
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`IndexedAdditions`].
|
|
|
|
pub fn apply_update(&mut self, update: TxGraph<A>) -> IndexedAdditions<A, I::Additions> {
|
|
|
|
let graph_additions = self.graph.apply_update(update);
|
|
|
|
|
|
|
|
let mut index_additions = I::Additions::default();
|
|
|
|
for added_tx in &graph_additions.tx {
|
|
|
|
index_additions.append(self.index.index_tx(added_tx));
|
|
|
|
}
|
|
|
|
for (&added_outpoint, added_txout) in &graph_additions.txout {
|
|
|
|
index_additions.append(self.index.index_txout(added_outpoint, added_txout));
|
|
|
|
}
|
|
|
|
|
|
|
|
IndexedAdditions {
|
|
|
|
graph_additions,
|
|
|
|
index_additions,
|
|
|
|
}
|
|
|
|
}
|
2023-03-27 15:36:37 +08:00
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// Insert a floating `txout` of given `outpoint`.
|
2023-03-27 14:21:10 +08:00
|
|
|
pub fn insert_txout(
|
|
|
|
&mut self,
|
|
|
|
outpoint: OutPoint,
|
|
|
|
txout: &TxOut,
|
|
|
|
) -> IndexedAdditions<A, I::Additions> {
|
2023-04-17 23:25:57 +08:00
|
|
|
let mut update = TxGraph::<A>::default();
|
|
|
|
let _ = update.insert_txout(outpoint, txout.clone());
|
|
|
|
self.apply_update(update)
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// Insert and index a transaction into the graph.
|
|
|
|
///
|
|
|
|
/// `anchors` can be provided to anchor the transaction to various blocks. `seen_at` is a
|
|
|
|
/// unix timestamp of when the transaction is last seen.
|
2023-03-27 14:21:10 +08:00
|
|
|
pub fn insert_tx(
|
|
|
|
&mut self,
|
|
|
|
tx: &Transaction,
|
2023-04-17 23:25:57 +08:00
|
|
|
anchors: impl IntoIterator<Item = A>,
|
|
|
|
seen_at: Option<u64>,
|
2023-03-27 14:21:10 +08:00
|
|
|
) -> IndexedAdditions<A, I::Additions> {
|
|
|
|
let txid = tx.txid();
|
2023-03-27 22:42:39 +08:00
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
let mut update = TxGraph::<A>::default();
|
|
|
|
if self.graph.get_tx(txid).is_none() {
|
|
|
|
let _ = update.insert_tx(tx.clone());
|
2023-03-31 12:39:00 +08:00
|
|
|
}
|
2023-04-17 23:25:57 +08:00
|
|
|
for anchor in anchors.into_iter() {
|
|
|
|
let _ = update.insert_anchor(txid, anchor);
|
|
|
|
}
|
|
|
|
if let Some(seen_at) = seen_at {
|
|
|
|
let _ = update.insert_seen_at(txid, seen_at);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.apply_update(update)
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// Insert relevant transactions from the given `txs` iterator.
|
|
|
|
///
|
|
|
|
/// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
|
2023-04-23 00:12:41 +08:00
|
|
|
/// transactions in `txs` will be ignored. `txs` do not need to be in topological order.
|
2023-04-17 23:25:57 +08:00
|
|
|
///
|
|
|
|
/// `anchors` can be provided to anchor the transactions to blocks. `seen_at` is a unix
|
|
|
|
/// timestamp of when the transactions are last seen.
|
2023-04-21 13:29:44 +08:00
|
|
|
pub fn insert_relevant_txs<'t>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&mut self,
|
2023-04-23 00:12:41 +08:00
|
|
|
txs: impl IntoIterator<Item = (&'t Transaction, impl IntoIterator<Item = A>)>,
|
2023-04-17 23:25:57 +08:00
|
|
|
seen_at: Option<u64>,
|
2023-04-20 18:07:26 +08:00
|
|
|
) -> IndexedAdditions<A, I::Additions> {
|
2023-04-22 22:56:51 +08:00
|
|
|
// The algorithm below allows for non-topologically ordered transactions by using two loops.
|
|
|
|
// This is achieved by:
|
2023-04-21 13:29:44 +08:00
|
|
|
// 1. insert all txs into the index. If they are irrelevant then that's fine it will just
|
|
|
|
// not store anything about them.
|
|
|
|
// 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
|
|
|
|
// returns true or not. (in a second loop).
|
2023-04-22 22:56:51 +08:00
|
|
|
let mut additions = IndexedAdditions::<A, I::Additions>::default();
|
|
|
|
let mut transactions = Vec::new();
|
2023-04-23 00:12:41 +08:00
|
|
|
for (tx, anchors) in txs.into_iter() {
|
2023-04-22 22:56:51 +08:00
|
|
|
additions.index_additions.append(self.index.index_tx(tx));
|
2023-04-23 00:12:41 +08:00
|
|
|
transactions.push((tx, anchors));
|
2023-04-22 22:56:51 +08:00
|
|
|
}
|
|
|
|
additions.append(
|
|
|
|
transactions
|
|
|
|
.into_iter()
|
2023-04-23 00:12:41 +08:00
|
|
|
.filter_map(|(tx, anchors)| match self.index.is_tx_relevant(tx) {
|
|
|
|
true => Some(self.insert_tx(tx, anchors, seen_at)),
|
2023-04-22 22:56:51 +08:00
|
|
|
false => None,
|
|
|
|
})
|
|
|
|
.fold(Default::default(), |mut acc, other| {
|
|
|
|
acc.append(other);
|
|
|
|
acc
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
additions
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
2023-04-17 23:25:57 +08:00
|
|
|
}
|
2023-03-27 14:21:10 +08:00
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
impl<A: Anchor, I: OwnedIndexer> IndexedTxGraph<A, I> {
|
2023-04-20 18:07:26 +08:00
|
|
|
pub fn try_list_owned_txouts<'a, C: ChainOracle + 'a>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
2023-04-17 23:25:57 +08:00
|
|
|
chain_tip: BlockId,
|
2023-04-20 18:07:26 +08:00
|
|
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
2023-04-17 23:25:57 +08:00
|
|
|
self.graph()
|
2023-04-20 18:07:26 +08:00
|
|
|
.try_list_chain_txouts(chain, chain_tip)
|
|
|
|
.filter(|r| {
|
|
|
|
if let Ok(full_txout) = r {
|
|
|
|
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
2023-03-31 12:39:00 +08:00
|
|
|
})
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-22 22:56:51 +08:00
|
|
|
pub fn list_owned_txouts<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
2023-04-17 23:25:57 +08:00
|
|
|
chain_tip: BlockId,
|
2023-04-20 18:07:26 +08:00
|
|
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
2023-04-17 23:25:57 +08:00
|
|
|
self.try_list_owned_txouts(chain, chain_tip)
|
|
|
|
.map(|r| r.expect("oracle is infallible"))
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-20 18:07:26 +08:00
|
|
|
pub fn try_list_owned_unspents<'a, C: ChainOracle + 'a>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
2023-04-17 23:25:57 +08:00
|
|
|
chain_tip: BlockId,
|
2023-04-20 18:07:26 +08:00
|
|
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a {
|
2023-04-17 23:25:57 +08:00
|
|
|
self.graph()
|
2023-04-20 18:07:26 +08:00
|
|
|
.try_list_chain_unspents(chain, chain_tip)
|
|
|
|
.filter(|r| {
|
|
|
|
if let Ok(full_txout) = r {
|
|
|
|
if !self.index.is_spk_owned(&full_txout.txout.script_pubkey) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
2023-04-17 23:25:57 +08:00
|
|
|
})
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-22 22:56:51 +08:00
|
|
|
pub fn list_owned_unspents<'a, C: ChainOracle<Error = Infallible> + 'a>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
2023-04-17 23:25:57 +08:00
|
|
|
chain_tip: BlockId,
|
2023-04-22 22:56:51 +08:00
|
|
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a {
|
2023-04-17 23:25:57 +08:00
|
|
|
self.try_list_owned_unspents(chain, chain_tip)
|
|
|
|
.map(|r| r.expect("oracle is infallible"))
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
2023-03-27 19:55:57 +08:00
|
|
|
|
|
|
|
pub fn try_balance<C, F>(
|
|
|
|
&self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &C,
|
2023-04-17 23:25:57 +08:00
|
|
|
chain_tip: BlockId,
|
2023-03-27 19:55:57 +08:00
|
|
|
mut should_trust: F,
|
|
|
|
) -> Result<Balance, C::Error>
|
|
|
|
where
|
|
|
|
C: ChainOracle,
|
2023-03-31 12:39:00 +08:00
|
|
|
F: FnMut(&Script) -> bool,
|
2023-03-27 19:55:57 +08:00
|
|
|
{
|
2023-04-21 13:29:44 +08:00
|
|
|
let tip_height = chain_tip.anchor_block().height;
|
|
|
|
|
2023-03-27 19:55:57 +08:00
|
|
|
let mut immature = 0;
|
|
|
|
let mut trusted_pending = 0;
|
|
|
|
let mut untrusted_pending = 0;
|
|
|
|
let mut confirmed = 0;
|
|
|
|
|
2023-04-21 13:29:44 +08:00
|
|
|
for res in self.try_list_owned_unspents(chain, chain_tip) {
|
2023-03-31 12:39:00 +08:00
|
|
|
let txout = res?;
|
2023-03-27 19:55:57 +08:00
|
|
|
|
|
|
|
match &txout.chain_position {
|
2023-04-19 12:21:39 +08:00
|
|
|
ObservedAs::Confirmed(_) => {
|
2023-03-27 19:55:57 +08:00
|
|
|
if txout.is_on_coinbase {
|
2023-04-21 13:29:44 +08:00
|
|
|
if txout.is_mature(tip_height) {
|
2023-03-27 19:55:57 +08:00
|
|
|
confirmed += txout.txout.value;
|
|
|
|
} else {
|
|
|
|
immature += txout.txout.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 16:39:54 +08:00
|
|
|
ObservedAs::Unconfirmed(_) => {
|
2023-03-31 12:39:00 +08:00
|
|
|
if should_trust(&txout.txout.script_pubkey) {
|
2023-03-27 19:55:57 +08:00
|
|
|
trusted_pending += txout.txout.value;
|
|
|
|
} else {
|
|
|
|
untrusted_pending += txout.txout.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Balance {
|
|
|
|
immature,
|
|
|
|
trusted_pending,
|
|
|
|
untrusted_pending,
|
|
|
|
confirmed,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-04-21 13:29:44 +08:00
|
|
|
pub fn balance<C, F>(&self, chain: &C, chain_tip: BlockId, should_trust: F) -> Balance
|
2023-03-27 19:55:57 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle<Error = Infallible>,
|
2023-03-31 12:39:00 +08:00
|
|
|
F: FnMut(&Script) -> bool,
|
2023-03-27 19:55:57 +08:00
|
|
|
{
|
2023-04-21 13:29:44 +08:00
|
|
|
self.try_balance(chain, chain_tip, should_trust)
|
2023-03-27 19:55:57 +08:00
|
|
|
.expect("error is infallible")
|
|
|
|
}
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
2023-04-12 11:24:05 +08:00
|
|
|
|
|
|
|
/// A structure that represents changes to an [`IndexedTxGraph`].
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
#[cfg_attr(
|
|
|
|
feature = "serde",
|
|
|
|
derive(serde::Deserialize, serde::Serialize),
|
|
|
|
serde(
|
|
|
|
crate = "serde_crate",
|
|
|
|
bound(
|
|
|
|
deserialize = "A: Ord + serde::Deserialize<'de>, IA: serde::Deserialize<'de>",
|
|
|
|
serialize = "A: Ord + serde::Serialize, IA: serde::Serialize"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)]
|
|
|
|
#[must_use]
|
|
|
|
pub struct IndexedAdditions<A, IA> {
|
|
|
|
/// [`TxGraph`] additions.
|
|
|
|
pub graph_additions: Additions<A>,
|
2023-04-17 23:25:57 +08:00
|
|
|
/// [`Indexer`] additions.
|
2023-04-12 11:24:05 +08:00
|
|
|
pub index_additions: IA,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A, IA: Default> Default for IndexedAdditions<A, IA> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
graph_additions: Default::default(),
|
|
|
|
index_additions: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
impl<A: Anchor, IA: Append> Append for IndexedAdditions<A, IA> {
|
2023-04-12 11:24:05 +08:00
|
|
|
fn append(&mut self, other: Self) {
|
|
|
|
self.graph_additions.append(other.graph_additions);
|
|
|
|
self.index_additions.append(other.index_additions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// Represents a structure that can index transaction data.
|
|
|
|
pub trait Indexer {
|
2023-04-12 11:24:05 +08:00
|
|
|
/// The resultant "additions" when new transaction data is indexed.
|
|
|
|
type Additions;
|
|
|
|
|
|
|
|
/// Scan and index the given `outpoint` and `txout`.
|
|
|
|
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::Additions;
|
|
|
|
|
|
|
|
/// Scan and index the given transaction.
|
|
|
|
fn index_tx(&mut self, tx: &Transaction) -> Self::Additions;
|
|
|
|
|
|
|
|
/// Apply additions to itself.
|
|
|
|
fn apply_additions(&mut self, additions: Self::Additions);
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// Determines whether the transaction should be included in the index.
|
2023-04-12 11:24:05 +08:00
|
|
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
|
|
|
}
|
|
|
|
|
2023-04-17 23:25:57 +08:00
|
|
|
/// A trait that extends [`Indexer`] to also index "owned" script pubkeys.
|
|
|
|
pub trait OwnedIndexer: Indexer {
|
|
|
|
/// Determines whether a given script pubkey (`spk`) is owned.
|
|
|
|
fn is_spk_owned(&self, spk: &Script) -> bool;
|
|
|
|
}
|