-
Notifications
You must be signed in to change notification settings - Fork 403
feat(chain): add new list_ordered_canonical_txs
method
#2027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/chain-0.23.2
Are you sure you want to change the base?
feat(chain): add new list_ordered_canonical_txs
method
#2027
Conversation
6b76092
to
a28cfdf
Compare
Nice diagrams |
08e4ce8
to
70ee41b
Compare
This comment was marked as outdated.
This comment was marked as outdated.
6239c8f
to
0aab769
Compare
d57a580
to
3b79cba
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The internals have completely changed. I don't think a point release would make any sense (we also have already merged other breaking changes). I recommend to just break the API.
Get rid of CanonicalIter
. Just have a single function that returns impl Iterator<Item = ...>
.
impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> { | ||
type Item = Result<(Txid, Arc<Transaction>, CanonicalReason<A>), C::Error>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, we would want most of this called in CanonicalIter::new
. However, I see that it requires us to return an error which would be a breaking change. We also can't store the error in CanonicalIter
as the error type is a type parameter on ChainOracle
.
Edit: Let's break the API. CanonicalIter
no longer makes sense. We should just have a single function that returns TopologicalIter
/impl Iterator<Item = ...>
.
0a17091
to
71c3744
Compare
278d049
to
c2a266d
Compare
} | ||
} | ||
|
||
fn is_txs_in_topological_order(txs: Vec<Txid>, tx_graph: TxGraph<BlockId>) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thinking.
/// reorders the transactions based on their spending relationships. | ||
/// | ||
/// [`list_canonical_txs`]: Self::list_canonical_txs | ||
pub fn list_ordered_canonical_txs<'a, C: ChainOracle<Error = Infallible>>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For those reviewing, and wondering why we don't have a fallible try
version of this method, it's because we don't have a fallible ChainOracle
implementation - we will get rid of ChainOracle
trait soon anyway.
This PR is intended for a point release so that bdk_wallet 2.x
users can get a topologically sorted list of transactions (we need a point release on bdk_wallet 2.x
as well).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just a few nits.
crates/chain/src/canonical_iter.rs
Outdated
use crate::ChainPosition; | ||
use core::cmp::Ordering; | ||
|
||
match (&a_tx.chain_position, &b_tx.chain_position) { | ||
// Both confirmed: sort by confirmation height | ||
( | ||
ChainPosition::Confirmed { | ||
anchor: a_anchor, .. | ||
}, | ||
ChainPosition::Confirmed { | ||
anchor: b_anchor, .. | ||
}, | ||
) => { | ||
let a_height = a_anchor.confirmation_height_upper_bound(); | ||
let b_height = b_anchor.confirmation_height_upper_bound(); | ||
a_height.cmp(&b_height) | ||
} | ||
// Confirmed comes before unconfirmed | ||
(ChainPosition::Confirmed { .. }, ChainPosition::Unconfirmed { .. }) => { | ||
Ordering::Less | ||
} | ||
// Unconfirmed comes after confirmed | ||
(ChainPosition::Unconfirmed { .. }, ChainPosition::Confirmed { .. }) => { | ||
Ordering::Greater | ||
} | ||
// Both unconfirmed: sort by first_seen (earlier timestamp first) | ||
( | ||
ChainPosition::Unconfirmed { | ||
first_seen: a_first_seen, | ||
.. | ||
}, | ||
ChainPosition::Unconfirmed { | ||
first_seen: b_first_seen, | ||
.. | ||
}, | ||
) => { | ||
// Earlier timestamps come first | ||
a_first_seen.cmp(b_first_seen) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be easier? Also, shouldn't we be sorting by first-seen (not last-seen)? I think the ChainPosition
Ord impl already sorts by first-seen.
use crate::ChainPosition; | |
use core::cmp::Ordering; | |
match (&a_tx.chain_position, &b_tx.chain_position) { | |
// Both confirmed: sort by confirmation height | |
( | |
ChainPosition::Confirmed { | |
anchor: a_anchor, .. | |
}, | |
ChainPosition::Confirmed { | |
anchor: b_anchor, .. | |
}, | |
) => { | |
let a_height = a_anchor.confirmation_height_upper_bound(); | |
let b_height = b_anchor.confirmation_height_upper_bound(); | |
a_height.cmp(&b_height) | |
} | |
// Confirmed comes before unconfirmed | |
(ChainPosition::Confirmed { .. }, ChainPosition::Unconfirmed { .. }) => { | |
Ordering::Less | |
} | |
// Unconfirmed comes after confirmed | |
(ChainPosition::Unconfirmed { .. }, ChainPosition::Confirmed { .. }) => { | |
Ordering::Greater | |
} | |
// Both unconfirmed: sort by first_seen (earlier timestamp first) | |
( | |
ChainPosition::Unconfirmed { | |
first_seen: a_first_seen, | |
.. | |
}, | |
ChainPosition::Unconfirmed { | |
first_seen: b_first_seen, | |
.. | |
}, | |
) => { | |
// Earlier timestamps come first | |
a_first_seen.cmp(b_first_seen) | |
} | |
} | |
a_tx.cmp(&b_tx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it can work, but I mean it's already using the first_seen
.
0524b20
to
cc0f010
Compare
I think I'll keep this one for |
cc0f010
to
ac95bd7
Compare
ac95bd7
to
49bf2b3
Compare
It deprecates the existing `list_canonical_txs` and `try_list_canonical_txs` in favor of `list_ordered_canonical_txs` which guarantees topological ordering. The new `list_ordered_canonical_txs` ensures that spending transactions always appear after their inputs, topologically ordered in "spending order". - Add `TopologicalIterator` for level-based topological sorting - Use the `ChainPosition` for sorting within the same level - Add the new method to the canonicalization benchmarks - Update the new test to verify topological ordering correctness Co-Authored-By: Claude <noreply@anthropic.com>
49bf2b3
to
573915f
Compare
- Update the `TopologicalIter` to take an Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A> of canonical txs. - Update it's documentation to mention the Kahn's Algorithm. - Update it to use `a_tx.cmp(&b_tx)` instead.
573915f
to
d9b9318
Compare
I don't think you have to deprecate |
Description
It introduces a new method for topological ordering canonical transactions,
list_ordered_canonical_txs
. It now ensures the dependency-based transaction processing, guaranteeing that parent transactions always appears before their children transaction.The existing
list_canonical_txs
andtry_list_canonical_txs
methods have been deprecated in favor of the new ordered version.Notes to the reviewers
"[...] For those reviewing, and wondering why we don't have a fallible try version of this method, it's because we don't have a fallible ChainOracle implementation - we will get rid of ChainOracle trait soon anyway."
This PR is intended for a point release so that bdk_wallet 2.x users can get a topologically sorted list of transactions (we need a point release on bdk_wallet 2.x as well).
It might be useful to take a look at the new test scenarios that I've added, it shows some specific scenarios where the current implementation and output of
canonical_txs
didn't output the transactions in topological order.Let me know if you think the TopologicalIter algorithm and/or API could be improved.
Changelog notice
Checklists
All Submissions:
New Features: