Skip to content

Commit 5278b81

Browse files
committed
feat(chain)!: rm get_chain_position and associated methods
1 parent 1c2fabc commit 5278b81

File tree

4 files changed

+153
-330
lines changed

4 files changed

+153
-330
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 1 addition & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
//! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`]
2121
//! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
2222
//! We decide which transactions are canonical based on the transaction's anchors and the
23-
//! `last_seen` (as unconfirmed) timestamp; see the [`try_get_chain_position`] documentation for
24-
//! more details.
23+
//! `last_seen` (as unconfirmed) timestamp.
2524
//!
2625
//! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
2726
//! persistent storage, or to be applied to another [`TxGraph`].
@@ -89,7 +88,6 @@
8988
//! let changeset = graph.apply_update(update);
9089
//! assert!(changeset.is_empty());
9190
//! ```
92-
//! [`try_get_chain_position`]: TxGraph::try_get_chain_position
9391
//! [`insert_txout`]: TxGraph::insert_txout
9492
9593
use crate::collections::*;
@@ -739,217 +737,6 @@ impl<A: Clone + Ord> TxGraph<A> {
739737
}
740738

741739
impl<A: Anchor> TxGraph<A> {
742-
/// Get the position of the transaction in `chain` with tip `chain_tip`.
743-
///
744-
/// Chain data is fetched from `chain`, a [`ChainOracle`] implementation.
745-
///
746-
/// This method returns `Ok(None)` if the transaction is not found in the chain, and no longer
747-
/// belongs in the mempool. The following factors are used to approximate whether an
748-
/// unconfirmed transaction exists in the mempool (not evicted):
749-
///
750-
/// 1. Unconfirmed transactions that conflict with confirmed transactions are evicted.
751-
/// 2. Unconfirmed transactions that spend from transactions that are evicted, are also
752-
/// evicted.
753-
/// 3. Given two conflicting unconfirmed transactions, the transaction with the lower
754-
/// `last_seen_unconfirmed` parameter is evicted. A transaction's `last_seen_unconfirmed`
755-
/// parameter is the max of all it's descendants' `last_seen_unconfirmed` parameters. If the
756-
/// final `last_seen_unconfirmed`s are the same, the transaction with the lower `txid` (by
757-
/// lexicographical order) is evicted.
758-
///
759-
/// # Error
760-
///
761-
/// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
762-
/// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
763-
///
764-
/// [`get_chain_position`]: Self::get_chain_position
765-
pub fn try_get_chain_position<C: ChainOracle>(
766-
&self,
767-
chain: &C,
768-
chain_tip: BlockId,
769-
txid: Txid,
770-
) -> Result<Option<ChainPosition<&A>>, C::Error> {
771-
let tx_node = match self.txs.get(&txid) {
772-
Some(v) => v,
773-
None => return Ok(None),
774-
};
775-
776-
for anchor in self.anchors.get(&txid).unwrap_or(&self.empty_anchors) {
777-
match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
778-
Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))),
779-
_ => continue,
780-
}
781-
}
782-
783-
// If no anchors are in best chain and we don't have a last_seen, we can return
784-
// early because by definition the tx doesn't have a chain position.
785-
let last_seen = match self.last_seen.get(&txid) {
786-
Some(t) => *t,
787-
None => return Ok(None),
788-
};
789-
790-
// The tx is not anchored to a block in the best chain, which means that it
791-
// might be in mempool, or it might have been dropped already.
792-
// Let's check conflicts to find out!
793-
let tx = match tx_node {
794-
TxNodeInternal::Whole(tx) => {
795-
// A coinbase tx that is not anchored in the best chain cannot be unconfirmed and
796-
// should always be filtered out.
797-
if tx.is_coinbase() {
798-
return Ok(None);
799-
}
800-
tx.clone()
801-
}
802-
TxNodeInternal::Partial(_) => {
803-
// Partial transactions (outputs only) cannot have conflicts.
804-
return Ok(None);
805-
}
806-
};
807-
808-
// We want to retrieve all the transactions that conflict with us, plus all the
809-
// transactions that conflict with our unconfirmed ancestors, since they conflict with us
810-
// as well.
811-
// We only traverse unconfirmed ancestors since conflicts of confirmed transactions
812-
// cannot be in the best chain.
813-
814-
// First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
815-
// resulting array will also include `tx`
816-
let unconfirmed_ancestor_txs =
817-
TxAncestors::new_include_root(self, tx.clone(), |_, ancestor_tx: Arc<Transaction>| {
818-
let tx_node = self.get_tx_node(ancestor_tx.as_ref().compute_txid())?;
819-
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
820-
// the best chain)
821-
for block in tx_node.anchors {
822-
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
823-
Ok(Some(true)) => return None,
824-
Err(e) => return Some(Err(e)),
825-
_ => continue,
826-
}
827-
}
828-
Some(Ok(tx_node))
829-
})
830-
.collect::<Result<Vec<_>, C::Error>>()?;
831-
832-
// We determine our tx's last seen, which is the max between our last seen,
833-
// and our unconf descendants' last seen.
834-
let unconfirmed_descendants_txs = TxDescendants::new_include_root(
835-
self,
836-
tx.as_ref().compute_txid(),
837-
|_, descendant_txid: Txid| {
838-
let tx_node = self.get_tx_node(descendant_txid)?;
839-
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
840-
// the best chain)
841-
for block in tx_node.anchors {
842-
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
843-
Ok(Some(true)) => return None,
844-
Err(e) => return Some(Err(e)),
845-
_ => continue,
846-
}
847-
}
848-
Some(Ok(tx_node))
849-
},
850-
)
851-
.collect::<Result<Vec<_>, C::Error>>()?;
852-
853-
let tx_last_seen = unconfirmed_descendants_txs
854-
.iter()
855-
.max_by_key(|tx| tx.last_seen_unconfirmed)
856-
.map(|tx| tx.last_seen_unconfirmed)
857-
.expect("descendants always includes at least one transaction (the root tx");
858-
859-
// Now we traverse our ancestors and consider all their conflicts
860-
for tx_node in unconfirmed_ancestor_txs {
861-
// We retrieve all the transactions conflicting with this specific ancestor
862-
let conflicting_txs =
863-
self.walk_conflicts(tx_node.tx.as_ref(), |_, txid| self.get_tx_node(txid));
864-
865-
// If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
866-
// this tx cannot exist in the best chain
867-
for conflicting_tx in conflicting_txs {
868-
for block in conflicting_tx.anchors {
869-
if chain.is_block_in_chain(block.anchor_block(), chain_tip)? == Some(true) {
870-
return Ok(None);
871-
}
872-
}
873-
if conflicting_tx.last_seen_unconfirmed > tx_last_seen {
874-
return Ok(None);
875-
}
876-
if conflicting_tx.last_seen_unconfirmed == Some(last_seen)
877-
&& conflicting_tx.as_ref().compute_txid() > tx.as_ref().compute_txid()
878-
{
879-
// Conflicting tx has priority if txid of conflicting tx > txid of original tx
880-
return Ok(None);
881-
}
882-
}
883-
}
884-
885-
Ok(Some(ChainPosition::Unconfirmed(last_seen)))
886-
}
887-
888-
/// Get the position of the transaction in `chain` with tip `chain_tip`.
889-
///
890-
/// This is the infallible version of [`try_get_chain_position`].
891-
///
892-
/// [`try_get_chain_position`]: Self::try_get_chain_position
893-
pub fn get_chain_position<C: ChainOracle<Error = Infallible>>(
894-
&self,
895-
chain: &C,
896-
chain_tip: BlockId,
897-
txid: Txid,
898-
) -> Option<ChainPosition<&A>> {
899-
self.try_get_chain_position(chain, chain_tip, txid)
900-
.expect("error is infallible")
901-
}
902-
903-
/// Get the txid of the spending transaction and where the spending transaction is observed in
904-
/// the `chain` of `chain_tip`.
905-
///
906-
/// If no in-chain transaction spends `outpoint`, `None` will be returned.
907-
///
908-
/// # Error
909-
///
910-
/// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails.
911-
///
912-
/// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead.
913-
///
914-
/// [`get_chain_spend`]: Self::get_chain_spend
915-
pub fn try_get_chain_spend<C: ChainOracle>(
916-
&self,
917-
chain: &C,
918-
chain_tip: BlockId,
919-
outpoint: OutPoint,
920-
) -> Result<Option<(ChainPosition<&A>, Txid)>, C::Error> {
921-
if self
922-
.try_get_chain_position(chain, chain_tip, outpoint.txid)?
923-
.is_none()
924-
{
925-
return Ok(None);
926-
}
927-
if let Some(spends) = self.spends.get(&outpoint) {
928-
for &txid in spends {
929-
if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
930-
return Ok(Some((observed_at, txid)));
931-
}
932-
}
933-
}
934-
Ok(None)
935-
}
936-
937-
/// Get the txid of the spending transaction and where the spending transaction is observed in
938-
/// the `chain` of `chain_tip`.
939-
///
940-
/// This is the infallible version of [`try_get_chain_spend`]
941-
///
942-
/// [`try_get_chain_spend`]: Self::try_get_chain_spend
943-
pub fn get_chain_spend<C: ChainOracle<Error = Infallible>>(
944-
&self,
945-
chain: &C,
946-
static_block: BlockId,
947-
outpoint: OutPoint,
948-
) -> Option<(ChainPosition<&A>, Txid)> {
949-
self.try_get_chain_spend(chain, static_block, outpoint)
950-
.expect("error is infallible")
951-
}
952-
953740
/// List graph transactions that are in `chain` with `chain_tip`.
954741
///
955742
/// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -587,14 +587,17 @@ fn test_get_chain_position() {
587587
}
588588

589589
// check chain position
590-
let res = graph
590+
let chain_pos = graph
591591
.graph()
592-
.get_chain_position(chain, chain.tip().block_id(), txid);
593-
assert_eq!(
594-
res.map(ChainPosition::cloned),
595-
exp_pos,
596-
"failed test case: {name}"
597-
);
592+
.list_canonical_txs(chain, chain.tip().block_id())
593+
.find_map(|canon_tx| {
594+
if canon_tx.tx_node.txid == txid {
595+
Some(canon_tx.chain_position)
596+
} else {
597+
None
598+
}
599+
});
600+
assert_eq!(chain_pos, exp_pos, "failed test case: {name}");
598601
}
599602

600603
[
@@ -651,7 +654,7 @@ fn test_get_chain_position() {
651654
exp_pos: Some(ChainPosition::Unconfirmed(2)),
652655
},
653656
TestCase {
654-
name: "tx unknown anchor - no chain pos",
657+
name: "tx unknown anchor - unconfirmed",
655658
tx: Transaction {
656659
output: vec![TxOut {
657660
value: Amount::ONE_BTC,
@@ -661,7 +664,7 @@ fn test_get_chain_position() {
661664
},
662665
anchor: Some(block_id!(2, "B'")),
663666
last_seen: None,
664-
exp_pos: None,
667+
exp_pos: Some(ChainPosition::UnconfirmedAndNotSeen),
665668
},
666669
]
667670
.into_iter()

0 commit comments

Comments
 (0)