Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/crates/heuristics/src/ast/same_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ mod tests {
use tx_indexer_primitives::loose::LooseIndexBuilder;
use tx_indexer_primitives::{
loose::{TxId, TxOutId},
test_utils::{DummyTxData, DummyTxOutData, temp_dir, write_single_block_file},
test_utils::{
DummyTxData, DummyTxOutData, SEQUENCE_FINAL, temp_dir, write_single_block_file,
},
traits::abstract_types::AbstractTransaction,
unified::AnyOutId,
};
Expand Down Expand Up @@ -100,6 +102,7 @@ mod tests {
DummyTxOutData::new_with_script(5_000, 1, shared_spk), // change (shared with spend2)
],
vec![TxOutId::new(TxId(1), 0)],
vec![SEQUENCE_FINAL; 1],
0,
);

Expand All @@ -110,6 +113,7 @@ mod tests {
DummyTxOutData::new_with_script(5_000, 1, shared_spk), // change (shared with spend1)
],
vec![TxOutId::new(TxId(2), 0)],
vec![SEQUENCE_FINAL; 1],
0,
);

Expand Down
3 changes: 2 additions & 1 deletion src/crates/heuristics/src/ast/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) mod ast_tests {
UnifiedStorage,
loose::LooseIndexBuilder,
loose::{TxId, TxOutId},
test_utils::{DummyTxData, DummyTxOutData},
test_utils::{DummyTxData, DummyTxOutData, SEQUENCE_FINAL},
traits::abstract_types::AbstractTransaction,
unified::{AnyOutId, AnyTxId},
};
Expand Down Expand Up @@ -291,6 +291,7 @@ pub(crate) mod ast_tests {
DummyTxOutData::new(300, 1), // change
],
vec![TxOutId::new(TxId(1), 0)],
vec![SEQUENCE_FINAL; 1],
0,
),
DummyTxData::new_with_amounts(vec![300]),
Expand Down
9 changes: 6 additions & 3 deletions src/crates/heuristics/src/change_identification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ mod tests {
UnifiedStorage,
loose::LooseIndexBuilder,
loose::{TxId, TxOutId},
test_utils::{DummyTxData, DummyTxOut, DummyTxOutData},
test_utils::{DummyTxData, DummyTxOut, DummyTxOutData, SEQUENCE_FINAL},
unified::AnyOutId,
};

Expand Down Expand Up @@ -146,9 +146,9 @@ mod tests {
// Same lock time
let tx_out = DummyTxOut {
vout: 0,
containing_tx: DummyTxData::new(vec![DummyTxOutData::new(100, 0)], vec![], 1),
containing_tx: DummyTxData::new(vec![DummyTxOutData::new(100, 0)], vec![], vec![], 1),
};
let spending_tx = DummyTxData::new(vec![DummyTxOutData::new(100, 0)], vec![], 1);
let spending_tx = DummyTxData::new(vec![DummyTxOutData::new(100, 0)], vec![], vec![], 1);
assert_eq!(
NLockTimeChangeIdentification::is_change(tx_out, spending_tx),
TxOutChangeAnnotation::Change
Expand Down Expand Up @@ -187,6 +187,7 @@ mod tests {
),
],
vec![TxOutId::new(TxId(1), 0), TxOutId::new(TxId(2), 0)],
vec![SEQUENCE_FINAL; 2],
0,
),
]);
Expand Down Expand Up @@ -236,6 +237,7 @@ mod tests {
),
],
vec![TxOutId::new(TxId(1), 0), TxOutId::new(TxId(2), 0)],
vec![SEQUENCE_FINAL; 2],
0,
),
]);
Expand Down Expand Up @@ -282,6 +284,7 @@ mod tests {
),
],
vec![TxOutId::new(TxId(1), 0), TxOutId::new(TxId(2), 0)],
vec![SEQUENCE_FINAL; 2],
0,
),
]);
Expand Down
37 changes: 34 additions & 3 deletions src/crates/primitives/src/handle.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::{
AnyInId, AnyOutId, AnyTxId, HasWitnessData, OutputType,
AnyInId, AnyOutId, AnyTxId, HasWitnessData, OutputType, ScriptPubkeyHash,
traits::{
abstract_types::{
AbstractTransaction, AbstractTxIn, AbstractTxOut, EnumerateInputValueInArbitraryOrder,
EnumerateOutputValueInArbitraryOrder, EnumerateSpentTxOuts, HasNLockTime,
HasScriptPubkey, HasSequence, InputCount, OutputCount, TxConstituent,
EnumerateOutputValueInArbitraryOrder, EnumerateSpentTxOuts, HasBlockHeight,
HasInputPrevOuts, HasNLockTime, HasScriptPubkey, HasSequence, InputCount, OutputCount,
TxConstituent,
},
graph_index::IndexedGraph,
},
Expand Down Expand Up @@ -300,6 +301,36 @@ impl<'a> HasNLockTime for TxHandle<'a> {
}
}

impl<'a> HasBlockHeight for TxHandle<'a> {
fn block_height(&self) -> Option<u64> {
TxHandle::block_height(self)
}
}

impl<'a> TxHandle<'a> {
fn map_input_prev_outs<R: 'a>(
&self,
project: impl Fn(TxOutHandle<'a>) -> R + 'a,
) -> impl Iterator<Item = Option<R>> + '_ {
let index = self.index;
self.inputs().map(move |i| {
let prev = i.prev_txout()?;
index.tx(&prev.txid())?;
Some(project(prev))
})
}
}

impl<'a> HasInputPrevOuts for TxHandle<'a> {
fn input_prev_types(&self) -> impl Iterator<Item = Option<OutputType>> {
self.map_input_prev_outs(|p| p.output_type())
}

fn input_prev_script_hashes(&self) -> impl Iterator<Item = Option<ScriptPubkeyHash>> {
self.map_input_prev_outs(|p| p.script_pubkey_hash())
}
}

impl<'a> TxConstituent for TxOutHandle<'a> {
type Handle = TxHandle<'a>;

Expand Down
15 changes: 12 additions & 3 deletions src/crates/primitives/src/loose/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,18 @@ impl TxIoIndex for InMemoryIndex {
tx.locktime()
}

fn input_sequence(&self, _in_id: &AnyInId) -> u32 {
// TODO: loose transactions don't carry sequence data in the abstract model yet.
panic!("input_sequence not supported for loose transactions");
fn input_sequence(&self, in_id: &AnyInId) -> u32 {
let loose_in = in_id
.loose_id()
.expect("loose storage only supports loose txin ids");
let tx = self
.txs
.get(&loose_in.txid())
.expect("loose txid not found in storage");
tx.inputs()
.nth(loose_in.vin() as usize)
.expect("vin out of range")
.sequence()
}

fn witness_items(&self, _in_id: &AnyInId) -> Vec<Vec<u8>> {
Expand Down
88 changes: 67 additions & 21 deletions src/crates/primitives/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,59 @@ use bitcoin::hashes::Hash as _;
use bitcoin::hashes::hash160::Hash as Hash160;

use crate::{
AnyOutId, AnyTxId, ScriptPubkeyHash,
AnyOutId, AnyTxId, OutputType, ScriptPubkeyHash,
loose::{TxId, TxOutId},
traits::HasNLockTime,
traits::abstract_types::{
AbstractTransaction, AbstractTxIn, AbstractTxOut, EnumerateOutputValueInArbitraryOrder,
EnumerateSpentTxOuts, HasScriptPubkey, InputCount, OutputCount, TxConstituent,
},
traits::{HasBlockHeight, HasInputPrevOuts, HasNLockTime, HasSequence},
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DummyTxData {
outputs: Vec<DummyTxOutData>,
/// The outputs that are spent by this transaction
spent_coins: Vec<TxOutId>,
/// Inputs of this transaction; each carries its prev outpoint and sequence.
inputs: Vec<DummyTxInWrapper>,
n_locktime: u32,
}

pub const SEQUENCE_FINAL: u32 = u32::MAX;

impl DummyTxData {
/// Base constructor.
pub fn new(outputs: Vec<DummyTxOutData>, spent_coins: Vec<TxOutId>, n_locktime: u32) -> Self {
pub fn new(
outputs: Vec<DummyTxOutData>,
spent_coins: Vec<TxOutId>,
sequences: Vec<u32>,
n_locktime: u32,
) -> Self {
assert_eq!(
spent_coins.len(),
sequences.len(),
"spent_coins and sequences must have matching lengths"
);
let inputs = spent_coins
.into_iter()
.zip(sequences)
.map(|(coin, sequence)| DummyTxInWrapper {
prev_txid: coin.txid(),
prev_vout: coin.vout(),
sequence,
})
.collect();
Self {
outputs,
spent_coins,
inputs,
n_locktime,
}
}

/// Tx with explicit outputs, no spent coins.
pub fn new_with_outputs(outputs: Vec<DummyTxOutData>) -> Self {
Self {
outputs,
spent_coins: vec![],
inputs: vec![],
n_locktime: 0,
}
}
Expand All @@ -51,11 +73,15 @@ impl DummyTxData {
/// Create spending tx from amounts and spent coins.
pub fn new_with_spent(amounts: Vec<u64>, spent_coins: Vec<TxOutId>) -> Self {
let base = Self::new_with_amounts(amounts);
Self::new(base.outputs, spent_coins, 0)
let n = spent_coins.len();
Self::new(base.outputs, spent_coins, vec![SEQUENCE_FINAL; n], 0)
}

pub fn spent_coins(&self) -> &[TxOutId] {
&self.spent_coins
pub fn spent_coins(&self) -> Vec<TxOutId> {
self.inputs
.iter()
.map(|i| TxOutId::new(i.prev_txid, i.prev_vout))
.collect()
}
}

Expand All @@ -65,10 +91,33 @@ impl HasNLockTime for DummyTxData {
}
}

impl HasBlockHeight for DummyTxData {
fn block_height(&self) -> Option<u64> {
None
}
}

impl HasInputPrevOuts for DummyTxData {
fn input_prev_types(&self) -> impl Iterator<Item = Option<OutputType>> {
std::iter::repeat_n(None, self.inputs.len())
}
fn input_prev_script_hashes(&self) -> impl Iterator<Item = Option<ScriptPubkeyHash>> {
std::iter::repeat_n(None, self.inputs.len())
}
}

// Wrapper types for implementing abstract traits on dummy types
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct DummyTxInWrapper {
prev_txid: TxId,
prev_vout: u32,
sequence: u32,
}

impl HasSequence for DummyTxInWrapper {
fn sequence(&self) -> u32 {
self.sequence
}
}

impl AbstractTxIn for DummyTxInWrapper {
Expand Down Expand Up @@ -132,14 +181,9 @@ impl AbstractTransaction for DummyTxData {
fn inputs(&self) -> Box<dyn Iterator<Item = Box<dyn AbstractTxIn + '_>> + '_> {
// Collect into a vector to avoid lifetime issues
let inputs: Vec<Box<dyn AbstractTxIn>> = self
.spent_coins
.inputs
.iter()
.map(|spent| {
Box::new(DummyTxInWrapper {
prev_txid: spent.txid(),
prev_vout: spent.vout(),
}) as Box<dyn AbstractTxIn>
})
.map(|input| Box::new(input.clone()) as Box<dyn AbstractTxIn>)
.collect();
Box::new(inputs.into_iter())
}
Expand All @@ -155,7 +199,7 @@ impl AbstractTransaction for DummyTxData {
}

fn input_len(&self) -> usize {
self.spent_coins.len()
self.inputs.len()
}

fn output_len(&self) -> usize {
Expand All @@ -173,7 +217,7 @@ impl AbstractTransaction for DummyTxData {
}

fn is_coinbase(&self) -> bool {
self.spent_coins.is_empty()
self.inputs.is_empty()
}
}

Expand All @@ -185,7 +229,7 @@ impl OutputCount for DummyTxData {

impl InputCount for DummyTxData {
fn input_count(&self) -> usize {
self.spent_coins.len()
self.inputs.len()
}
}

Expand Down Expand Up @@ -222,7 +266,9 @@ pub fn write_single_block_file(dir: &std::path::Path, block: &[u8]) -> std::io::

impl EnumerateSpentTxOuts for DummyTxData {
fn spent_coins(&self) -> impl Iterator<Item = AnyOutId> {
self.spent_coins.iter().copied().map(AnyOutId::from)
self.inputs
.iter()
.map(|i| AnyOutId::from(TxOutId::new(i.prev_txid, i.prev_vout)))
}
}

Expand Down
18 changes: 17 additions & 1 deletion src/crates/primitives/src/traits/abstract_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub trait EnumerateInputValueInArbitraryOrder: AbstractTransaction {
}

/// Trait for transaction inputs
pub trait AbstractTxIn {
pub trait AbstractTxIn: HasSequence {
/// Returns the transaction ID of the previous output
fn prev_txid(&self) -> Option<AnyTxId>;
/// Returns the output index of the previous output
Expand Down Expand Up @@ -111,6 +111,22 @@ pub trait HasPrevOutput {
fn prev_outpoint_vout(&self) -> u32;
}

/// Confirmed block height of a transaction. `None` when unconfirmed
/// (loose).
pub trait HasBlockHeight {
fn block_height(&self) -> Option<u64>;
}

/// Per-input prev-out script type and script-pubkey hash, in input order.
/// Each entry is `None` when the prev tx is unavailable (coinbase, missing
/// from the index, or wrapper opted out of populating it).
pub trait HasInputPrevOuts: AbstractTransaction {
/// Per-input prev-out script type, in input order.
fn input_prev_types(&self) -> impl Iterator<Item = Option<OutputType>>;
/// Per-input prev-out script-pubkey hash, in input order.
fn input_prev_script_hashes(&self) -> impl Iterator<Item = Option<ScriptPubkeyHash>>;
}

// --- bitcoin type impls ---

impl HasSequence for bitcoin::TxIn {
Expand Down
4 changes: 2 additions & 2 deletions src/crates/primitives/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ pub mod abstract_types;
pub mod graph_index;

pub use abstract_types::{
HasNLockTime, HasPrevOutput, HasScriptPubkey, HasSequence, HasValue, HasVersion,
HasWitnessData, InputCount,
HasBlockHeight, HasInputPrevOuts, HasNLockTime, HasPrevOutput, HasScriptPubkey, HasSequence,
HasValue, HasVersion, HasWitnessData, InputCount,
};

use crate::ScriptPubkeyHash;
Expand Down
3 changes: 1 addition & 2 deletions src/crates/primitives/src/unified/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,7 @@ impl TxIoIndex for UnifiedStorage {

fn input_sequence(&self, in_id: &AnyInId) -> u32 {
if in_id.is_loose() {
// TODO: loose transactions don't carry sequence data in the abstract model yet.
panic!("input_sequence not supported for loose transactions");
return self.loose().input_sequence(in_id);
}
let did = in_id.confirmed_id().expect("must be dense");
let ds = self.dense();
Expand Down
Loading