2023-04-05 17:29:20 +08:00
|
|
|
use core::convert::Infallible;
|
2023-03-27 14:21:10 +08:00
|
|
|
|
2023-03-31 12:39:00 +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-03-30 18:33:53 +08:00
|
|
|
tx_graph::{Additions, TxGraph, TxNode},
|
2023-04-12 11:24:05 +08:00
|
|
|
Append, BlockAnchor, BlockId, ChainOracle, FullTxOut, ObservedAs,
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A: BlockAnchor, I: TxIndex> IndexedTxGraph<A, I> {
|
|
|
|
/// 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-03-27 14:21:10 +08:00
|
|
|
/// Insert a `txout` that exists in `outpoint` with the given `observation`.
|
|
|
|
pub fn insert_txout(
|
|
|
|
&mut self,
|
|
|
|
outpoint: OutPoint,
|
|
|
|
txout: &TxOut,
|
2023-04-05 16:39:54 +08:00
|
|
|
observation: ObservedAs<A>,
|
2023-03-27 14:21:10 +08:00
|
|
|
) -> IndexedAdditions<A, I::Additions> {
|
2023-03-31 12:39:00 +08:00
|
|
|
IndexedAdditions {
|
2023-03-27 14:21:10 +08:00
|
|
|
graph_additions: {
|
|
|
|
let mut graph_additions = self.graph.insert_txout(outpoint, txout.clone());
|
|
|
|
graph_additions.append(match observation {
|
2023-04-05 16:39:54 +08:00
|
|
|
ObservedAs::Confirmed(anchor) => {
|
|
|
|
self.graph.insert_anchor(outpoint.txid, anchor)
|
|
|
|
}
|
|
|
|
ObservedAs::Unconfirmed(seen_at) => {
|
2023-03-27 14:21:10 +08:00
|
|
|
self.graph.insert_seen_at(outpoint.txid, seen_at)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
graph_additions
|
|
|
|
},
|
2023-03-31 12:39:00 +08:00
|
|
|
index_additions: <I as TxIndex>::index_txout(&mut self.index, outpoint, txout),
|
|
|
|
}
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn insert_tx(
|
|
|
|
&mut self,
|
|
|
|
tx: &Transaction,
|
2023-04-05 16:39:54 +08:00
|
|
|
observation: ObservedAs<A>,
|
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-03-31 12:39:00 +08:00
|
|
|
IndexedAdditions {
|
2023-03-27 14:21:10 +08:00
|
|
|
graph_additions: {
|
|
|
|
let mut graph_additions = self.graph.insert_tx(tx.clone());
|
|
|
|
graph_additions.append(match observation {
|
2023-04-05 16:39:54 +08:00
|
|
|
ObservedAs::Confirmed(anchor) => self.graph.insert_anchor(txid, anchor),
|
|
|
|
ObservedAs::Unconfirmed(seen_at) => self.graph.insert_seen_at(txid, seen_at),
|
2023-03-27 14:21:10 +08:00
|
|
|
});
|
|
|
|
graph_additions
|
|
|
|
},
|
2023-03-31 12:39:00 +08:00
|
|
|
index_additions: <I as TxIndex>::index_tx(&mut self.index, tx),
|
|
|
|
}
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
2023-04-05 19:13:42 +08:00
|
|
|
pub fn insert_relevant_txs<'t, T>(
|
2023-03-27 14:21:10 +08:00
|
|
|
&mut self,
|
|
|
|
txs: T,
|
2023-04-05 16:39:54 +08:00
|
|
|
observation: ObservedAs<A>,
|
2023-03-27 14:21:10 +08:00
|
|
|
) -> IndexedAdditions<A, I::Additions>
|
|
|
|
where
|
|
|
|
T: Iterator<Item = &'t Transaction>,
|
2023-04-05 17:29:20 +08:00
|
|
|
I::Additions: Default + Append,
|
2023-03-27 14:21:10 +08:00
|
|
|
{
|
|
|
|
txs.filter_map(|tx| {
|
|
|
|
if self.index.is_tx_relevant(tx) {
|
|
|
|
Some(self.insert_tx(tx, observation.clone()))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.fold(IndexedAdditions::default(), |mut acc, other| {
|
2023-04-05 17:29:20 +08:00
|
|
|
acc.append(other);
|
2023-03-27 14:21:10 +08:00
|
|
|
acc
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-29 22:45:01 +08:00
|
|
|
// [TODO] Have to methods, one for relevant-only, and one for any. Have one in `TxGraph`.
|
2023-03-27 14:21:10 +08:00
|
|
|
pub fn try_list_chain_txs<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>>
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle + 'a,
|
|
|
|
{
|
|
|
|
self.graph
|
|
|
|
.full_transactions()
|
|
|
|
.filter(|tx| self.index.is_tx_relevant(tx))
|
|
|
|
.filter_map(move |tx| {
|
|
|
|
self.graph
|
2023-04-10 13:03:51 +08:00
|
|
|
.try_get_chain_position(chain, static_block, tx.txid)
|
2023-04-05 16:39:54 +08:00
|
|
|
.map(|v| {
|
|
|
|
v.map(|observed_in| CanonicalTx {
|
|
|
|
observed_as: observed_in,
|
|
|
|
tx,
|
|
|
|
})
|
|
|
|
})
|
2023-03-27 14:21:10 +08:00
|
|
|
.transpose()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list_chain_txs<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>>
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle<Error = Infallible> + 'a,
|
|
|
|
{
|
2023-04-10 13:03:51 +08:00
|
|
|
self.try_list_chain_txs(chain, static_block)
|
2023-03-27 14:21:10 +08:00
|
|
|
.map(|r| r.expect("error is infallible"))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_list_chain_txouts<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle + 'a,
|
|
|
|
{
|
2023-03-31 12:39:00 +08:00
|
|
|
self.graph
|
|
|
|
.all_txouts()
|
2023-04-05 18:17:08 +08:00
|
|
|
.filter(|&(op, txo)| self.index.is_txout_relevant(op, txo))
|
2023-03-31 12:39:00 +08:00
|
|
|
.filter_map(move |(op, txout)| -> Option<Result<_, C::Error>> {
|
2023-03-27 14:21:10 +08:00
|
|
|
let graph_tx = self.graph.get_tx(op.txid)?;
|
|
|
|
|
|
|
|
let is_on_coinbase = graph_tx.is_coin_base();
|
|
|
|
|
2023-04-10 13:03:51 +08:00
|
|
|
let chain_position =
|
|
|
|
match self
|
|
|
|
.graph
|
|
|
|
.try_get_chain_position(chain, static_block, op.txid)
|
|
|
|
{
|
|
|
|
Ok(Some(observed_at)) => observed_at.cloned(),
|
|
|
|
Ok(None) => return None,
|
|
|
|
Err(err) => return Some(Err(err)),
|
|
|
|
};
|
|
|
|
|
|
|
|
let spent_by = match self.graph.try_get_spend_in_chain(chain, static_block, op) {
|
2023-04-05 16:39:54 +08:00
|
|
|
Ok(Some((obs, txid))) => Some((obs.cloned(), txid)),
|
2023-03-31 12:39:00 +08:00
|
|
|
Ok(None) => None,
|
2023-03-27 14:21:10 +08:00
|
|
|
Err(err) => return Some(Err(err)),
|
|
|
|
};
|
|
|
|
|
|
|
|
let full_txout = FullTxOut {
|
2023-03-31 12:39:00 +08:00
|
|
|
outpoint: op,
|
2023-03-27 14:21:10 +08:00
|
|
|
txout: txout.clone(),
|
|
|
|
chain_position,
|
|
|
|
spent_by,
|
|
|
|
is_on_coinbase,
|
|
|
|
};
|
|
|
|
|
2023-03-31 12:39:00 +08:00
|
|
|
Some(Ok(full_txout))
|
|
|
|
})
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list_chain_txouts<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle<Error = Infallible> + 'a,
|
|
|
|
{
|
2023-04-10 13:03:51 +08:00
|
|
|
self.try_list_chain_txouts(chain, static_block)
|
2023-03-27 14:21:10 +08:00
|
|
|
.map(|r| r.expect("error in infallible"))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return relevant unspents.
|
|
|
|
pub fn try_list_chain_utxos<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = Result<FullTxOut<ObservedAs<A>>, C::Error>> + 'a
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle + 'a,
|
|
|
|
{
|
2023-04-10 13:03:51 +08:00
|
|
|
self.try_list_chain_txouts(chain, static_block)
|
2023-03-31 12:39:00 +08:00
|
|
|
.filter(|r| !matches!(r, Ok(txo) if txo.spent_by.is_none()))
|
2023-03-27 14:21:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn list_chain_utxos<'a, C>(
|
|
|
|
&'a self,
|
2023-04-10 13:03:51 +08:00
|
|
|
chain: &'a C,
|
|
|
|
static_block: BlockId,
|
2023-04-05 16:39:54 +08:00
|
|
|
) -> impl Iterator<Item = FullTxOut<ObservedAs<A>>> + 'a
|
2023-03-27 14:21:10 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle<Error = Infallible> + 'a,
|
|
|
|
{
|
2023-04-10 13:03:51 +08:00
|
|
|
self.try_list_chain_utxos(chain, static_block)
|
2023-03-27 14:21:10 +08:00
|
|
|
.map(|r| r.expect("error is infallible"))
|
|
|
|
}
|
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,
|
|
|
|
static_block: BlockId,
|
2023-03-27 19:55:57 +08:00
|
|
|
tip: u32,
|
|
|
|
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
|
|
|
{
|
|
|
|
let mut immature = 0;
|
|
|
|
let mut trusted_pending = 0;
|
|
|
|
let mut untrusted_pending = 0;
|
|
|
|
let mut confirmed = 0;
|
|
|
|
|
2023-04-10 13:03:51 +08:00
|
|
|
for res in self.try_list_chain_txouts(chain, static_block) {
|
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-05 16:39:54 +08:00
|
|
|
ObservedAs::Confirmed(_) => {
|
2023-03-27 19:55:57 +08:00
|
|
|
if txout.is_on_coinbase {
|
2023-04-10 13:03:51 +08:00
|
|
|
if txout.is_observed_as_confirmed_and_mature(tip) {
|
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-10 13:03:51 +08:00
|
|
|
pub fn balance<C, F>(
|
|
|
|
&self,
|
|
|
|
chain: &C,
|
|
|
|
static_block: BlockId,
|
|
|
|
tip: u32,
|
|
|
|
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-10 13:03:51 +08:00
|
|
|
self.try_balance(chain, static_block, tip, should_trust)
|
2023-03-27 19:55:57 +08:00
|
|
|
.expect("error is infallible")
|
|
|
|
}
|
|
|
|
|
2023-04-10 13:03:51 +08:00
|
|
|
pub fn try_balance_at<C>(
|
|
|
|
&self,
|
|
|
|
chain: &C,
|
|
|
|
static_block: BlockId,
|
|
|
|
height: u32,
|
|
|
|
) -> Result<u64, C::Error>
|
2023-03-27 19:55:57 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle,
|
|
|
|
{
|
|
|
|
let mut sum = 0;
|
2023-04-10 13:03:51 +08:00
|
|
|
for txo_res in self.try_list_chain_txouts(chain, static_block) {
|
2023-03-31 12:39:00 +08:00
|
|
|
let txo = txo_res?;
|
2023-04-10 13:03:51 +08:00
|
|
|
if txo.is_observed_as_confirmed_and_spendable(height) {
|
2023-03-27 19:55:57 +08:00
|
|
|
sum += txo.txout.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(sum)
|
|
|
|
}
|
|
|
|
|
2023-04-10 13:03:51 +08:00
|
|
|
pub fn balance_at<C>(&self, chain: &C, static_block: BlockId, height: u32) -> u64
|
2023-03-27 19:55:57 +08:00
|
|
|
where
|
|
|
|
C: ChainOracle<Error = Infallible>,
|
|
|
|
{
|
2023-04-10 13:03:51 +08:00
|
|
|
self.try_balance_at(chain, static_block, height)
|
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>,
|
|
|
|
/// [`TxIndex`] additions.
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<A: BlockAnchor, IA: Append> Append for IndexedAdditions<A, IA> {
|
|
|
|
fn append(&mut self, other: Self) {
|
|
|
|
self.graph_additions.append(other.graph_additions);
|
|
|
|
self.index_additions.append(other.index_additions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub struct CanonicalTx<'a, T, A> {
|
|
|
|
/// Where the transaction is observed (in a block or in mempool).
|
|
|
|
pub observed_as: ObservedAs<&'a A>,
|
|
|
|
/// The transaction with anchors and last seen timestamp.
|
|
|
|
pub tx: TxNode<'a, T, A>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Represents an index of transaction data.
|
|
|
|
pub trait TxIndex {
|
|
|
|
/// 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);
|
|
|
|
|
|
|
|
/// Returns whether the txout is marked as relevant in the index.
|
|
|
|
fn is_txout_relevant(&self, outpoint: OutPoint, txout: &TxOut) -> bool;
|
|
|
|
|
|
|
|
/// Returns whether the transaction is marked as relevant in the index.
|
|
|
|
fn is_tx_relevant(&self, tx: &Transaction) -> bool;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait SpkIndex: TxIndex {}
|