//! [`insert_txout`]: TxGraph::insert_txout
use crate::{
- collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
- ChainOracle, ChainPosition, FullTxOut,
+ collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ChainPosition,
+ FullTxOut,
};
use alloc::collections::vec_deque::VecDeque;
use alloc::sync::Arc;
}
impl<A: Anchor> TxGraph<A> {
- /// Find missing block heights of `chain`.
- ///
- /// This works by scanning through anchors, and seeing whether the anchor block of the anchor
- /// exists in the [`LocalChain`]. The returned iterator does not output duplicate heights.
- pub fn missing_heights<'a>(&'a self, chain: &'a LocalChain) -> impl Iterator<Item = u32> + 'a {
- // Map of txids to skip.
- //
- // Usually, if a height of a tx anchor is missing from the chain, we would want to return
- // this height in the iterator. The exception is when the tx is confirmed in chain. All the
- // other missing-height anchors of this tx can be skipped.
- //
- // * Some(true) => skip all anchors of this txid
- // * Some(false) => do not skip anchors of this txid
- // * None => we do not know whether we can skip this txid
- let mut txids_to_skip = HashMap::<Txid, bool>::new();
-
- // Keeps track of the last height emitted so we don't double up.
- let mut last_height_emitted = Option::<u32>::None;
-
- self.anchors
- .iter()
- .filter(move |(_, txid)| {
- let skip = *txids_to_skip.entry(*txid).or_insert_with(|| {
- let tx_anchors = match self.txs.get(txid) {
- Some((_, anchors, _)) => anchors,
- None => return true,
- };
- let mut has_missing_height = false;
- for anchor_block in tx_anchors.iter().map(Anchor::anchor_block) {
- match chain.get(anchor_block.height) {
- None => {
- has_missing_height = true;
- continue;
- }
- Some(chain_cp) => {
- if chain_cp.hash() == anchor_block.hash {
- return true;
- }
- }
- }
- }
- !has_missing_height
- });
- #[cfg(feature = "std")]
- debug_assert!({
- println!("txid={} skip={}", txid, skip);
- true
- });
- !skip
- })
- .filter_map(move |(a, _)| {
- let anchor_block = a.anchor_block();
- if Some(anchor_block.height) != last_height_emitted
- && chain.get(anchor_block.height).is_none()
- {
- last_height_emitted = Some(anchor_block.height);
- Some(anchor_block.height)
- } else {
- None
- }
- })
- }
-
/// Get the position of the transaction in `chain` with tip `chain_tip`.
///
/// Chain data is fetched from `chain`, a [`ChainOracle`] implementation.
///
/// This is useful if you want to find which heights you need to fetch data about in order to
/// confirm or exclude these anchors.
- ///
- /// See also: [`TxGraph::missing_heights`]
pub fn anchor_heights(&self) -> impl Iterator<Item = u32> + '_
where
A: Anchor,
!duplicate
})
}
-
- /// Returns an iterator for the [`anchor_heights`] in this changeset that are not included in
- /// `local_chain`. This tells you which heights you need to include in `local_chain` in order
- /// for it to conclusively act as a [`ChainOracle`] for the transaction anchors this changeset
- /// will add.
- ///
- /// [`ChainOracle`]: crate::ChainOracle
- /// [`anchor_heights`]: Self::anchor_heights
- pub fn missing_heights_from<'a>(
- &'a self,
- local_chain: &'a LocalChain,
- ) -> impl Iterator<Item = u32> + 'a
- where
- A: Anchor,
- {
- self.anchor_heights()
- .filter(move |&height| local_chain.get(height).is_none())
- }
}
impl<A: Ord> Append for ChangeSet<A> {
assert_eq!(graph.full_txs().next().unwrap().last_seen_unconfirmed, 2);
}
-#[test]
-fn test_missing_blocks() {
- /// An anchor implementation for testing, made up of `(the_anchor_block, random_data)`.
- #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, core::hash::Hash)]
- struct TestAnchor(BlockId);
-
- impl Anchor for TestAnchor {
- fn anchor_block(&self) -> BlockId {
- self.0
- }
- }
-
- struct Scenario<'a> {
- name: &'a str,
- graph: TxGraph<TestAnchor>,
- chain: LocalChain,
- exp_heights: &'a [u32],
- }
-
- const fn new_anchor(height: u32, hash: BlockHash) -> TestAnchor {
- TestAnchor(BlockId { height, hash })
- }
-
- fn new_scenario<'a>(
- name: &'a str,
- graph_anchors: &'a [(Txid, TestAnchor)],
- chain: &'a [(u32, BlockHash)],
- exp_heights: &'a [u32],
- ) -> Scenario<'a> {
- Scenario {
- name,
- graph: {
- let mut g = TxGraph::default();
- for (txid, anchor) in graph_anchors {
- let _ = g.insert_anchor(*txid, anchor.clone());
- }
- g
- },
- chain: {
- let (mut c, _) = LocalChain::from_genesis_hash(h!("genesis"));
- for (height, hash) in chain {
- let _ = c.insert_block(BlockId {
- height: *height,
- hash: *hash,
- });
- }
- c
- },
- exp_heights,
- }
- }
-
- fn run(scenarios: &[Scenario]) {
- for scenario in scenarios {
- let Scenario {
- name,
- graph,
- chain,
- exp_heights,
- } = scenario;
-
- let heights = graph.missing_heights(chain).collect::<Vec<_>>();
- assert_eq!(&heights, exp_heights, "scenario: {}", name);
- }
- }
-
- run(&[
- new_scenario(
- "2 txs with the same anchor (2:B) which is missing from chain",
- &[
- (h!("tx_1"), new_anchor(2, h!("B"))),
- (h!("tx_2"), new_anchor(2, h!("B"))),
- ],
- &[(1, h!("A")), (3, h!("C"))],
- &[2],
- ),
- new_scenario(
- "2 txs with different anchors at the same height, one of the anchors is missing",
- &[
- (h!("tx_1"), new_anchor(2, h!("B1"))),
- (h!("tx_2"), new_anchor(2, h!("B2"))),
- ],
- &[(1, h!("A")), (2, h!("B1"))],
- &[],
- ),
- new_scenario(
- "tx with 2 anchors of same height which are missing from the chain",
- &[
- (h!("tx"), new_anchor(3, h!("C1"))),
- (h!("tx"), new_anchor(3, h!("C2"))),
- ],
- &[(1, h!("A")), (4, h!("D"))],
- &[3],
- ),
- new_scenario(
- "tx with 2 anchors at the same height, chain has this height but does not match either anchor",
- &[
- (h!("tx"), new_anchor(4, h!("D1"))),
- (h!("tx"), new_anchor(4, h!("D2"))),
- ],
- &[(4, h!("D3")), (5, h!("E"))],
- &[],
- ),
- new_scenario(
- "tx with 2 anchors at different heights, one anchor exists in chain, should return nothing",
- &[
- (h!("tx"), new_anchor(3, h!("C"))),
- (h!("tx"), new_anchor(4, h!("D"))),
- ],
- &[(4, h!("D")), (5, h!("E"))],
- &[],
- ),
- new_scenario(
- "tx with 2 anchors at different heights, first height is already in chain with different hash, iterator should only return 2nd height",
- &[
- (h!("tx"), new_anchor(5, h!("E1"))),
- (h!("tx"), new_anchor(6, h!("F1"))),
- ],
- &[(4, h!("D")), (5, h!("E")), (7, h!("G"))],
- &[6],
- ),
- new_scenario(
- "tx with 2 anchors at different heights, neither height is in chain, both heights should be returned",
- &[
- (h!("tx"), new_anchor(3, h!("C"))),
- (h!("tx"), new_anchor(4, h!("D"))),
- ],
- &[(1, h!("A")), (2, h!("B"))],
- &[3, 4],
- ),
- ]);
-}
-
#[test]
/// The `map_anchors` allow a caller to pass a function to reconstruct the [`TxGraph`] with any [`Anchor`],
/// even though the function is non-deterministic.