Context
propagate_taint() processes transactions in the order they appear in the input list, with the documented assumption that this is "roughly chronological, as in an exported graph." When a tx spends an output that appears later in the list than the spend, the taint carried by that input has not been accumulated yet, so downstream taint is under-counted.
Why it matters
Exports are not guaranteed to be topologically sorted. A graph delivered newest-first, or interleaved across addresses, can produce a lower dirty_value / taint fraction than the true value — a silent accuracy bug rather than a crash.
Repro sketch
from cryptotrace.core import propagate_taint, Transaction
SDN = "0x722122df12d4e14e13ac3b6895a86e84145b6967"
# t1 (B->C) listed BEFORE t0 (SDN->B): C should be tainted, but B has no taint yet
txs = [
Transaction("t1", ["B"], ["C"], "ETH", 10.0),
Transaction("t0", [SDN], ["B"], "ETH", 10.0),
]
print(propagate_taint(txs, {SDN})) # C's taint is understated vs a sorted graph
Possible fix (non-breaking)
Topologically sort by timestamp (falling back to a dependency sort on the input→output graph) before propagating, when timestamps are present. Keep the current single-pass behaviour as a fallback when ordering is unavailable, and document the guarantee.
Context
propagate_taint()processes transactions in the order they appear in the input list, with the documented assumption that this is "roughly chronological, as in an exported graph." When a tx spends an output that appears later in the list than the spend, the taint carried by that input has not been accumulated yet, so downstream taint is under-counted.Why it matters
Exports are not guaranteed to be topologically sorted. A graph delivered newest-first, or interleaved across addresses, can produce a lower
dirty_value/ taint fraction than the true value — a silent accuracy bug rather than a crash.Repro sketch
Possible fix (non-breaking)
Topologically sort by timestamp (falling back to a dependency sort on the input→output graph) before propagating, when timestamps are present. Keep the current single-pass behaviour as a fallback when ordering is unavailable, and document the guarantee.