fix(wallet): add support for bip-431 rule 2#478
Conversation
|
cc @yan-pi as he already reviewed the other PR and has a reproducible test using bdk-cli too. |
|
I noticed in CI I need to fix some bdk_testenv/electrs/esplora related issues. However, the fix in bc05f88 is ready for review/discussion. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #478 +/- ##
==========================================
+ Coverage 80.96% 81.00% +0.04%
==========================================
Files 24 24
Lines 5489 5507 +18
Branches 247 249 +2
==========================================
+ Hits 4444 4461 +17
Misses 968 968
- Partials 77 78 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
tACK 1d32117.
IMO The is_truc helper is more functional and reads cleaner than the XNOR in #442, and definitely is a good foundation for the rest of #477.
Tested it locally by checking out test/unconfirmed-truc-txs and rebuilding the rust-repro binaries from https://github.com/yan-pi/bdk-pr442-repro against the PR via [dependencies] bdk_wallet = { path = "../../bdk_wallet" }:
The inverse direction of Rule 2: the non-v3 unconfirmed UTXO was filtered correclty.
One concern not flagged inline: the integration test is great, but maybe the unit-level coverage from #442 is also worth keeping?
Plus two minor nits and typos.
b8d9ae1 to
85143c2
Compare
- introduces a private method `is_truc` to check if a given tx version is TRUC (e.g `transaction::Version(3)`). - add new step in `filter_utxos` to validate BIP-431 rule 2, which validates the proper usage of unconfirmed TRUC/non-TRUC ancestor in a TRUC/non-TRUC tx.
47bba04 to
d6fca63
Compare
- introduce `test_create_and_spend_from_tx` to exercise BIP-431 rule-2 filtering in-memory, not relying on `bdk_testenv`/`bdk_electrum`. NOTE: I asked Claude to remove the `bdk_testenv` dependency, keeping the test behavior in-memory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
d6fca63 to
6145afc
Compare
| if local_output.chain_position.is_confirmed() { | ||
| return true; | ||
| } | ||
|
|
||
| let Some(ancestor_tx) = self.tx_graph().get_tx(local_output.outpoint.txid) | ||
| // if we don't have the full tx available we can't assure the ancestor | ||
| // tx version it assumes it's a valid candidate. | ||
| else { | ||
| return true; | ||
| }; | ||
|
|
||
| match is_truc(version) { | ||
| // if building TRUC; filter out all unconfirmed non-TRUC. | ||
| true => is_truc(ancestor_tx.version), | ||
| // if building non-TRUC; filter out all unconfirmed TRUC. | ||
| false => !is_truc(ancestor_tx.version), | ||
| } |
There was a problem hiding this comment.
I think #442 had the right idea with the bidirectional filter.
| if local_output.chain_position.is_confirmed() { | |
| return true; | |
| } | |
| let Some(ancestor_tx) = self.tx_graph().get_tx(local_output.outpoint.txid) | |
| // if we don't have the full tx available we can't assure the ancestor | |
| // tx version it assumes it's a valid candidate. | |
| else { | |
| return true; | |
| }; | |
| match is_truc(version) { | |
| // if building TRUC; filter out all unconfirmed non-TRUC. | |
| true => is_truc(ancestor_tx.version), | |
| // if building non-TRUC; filter out all unconfirmed TRUC. | |
| false => !is_truc(ancestor_tx.version), | |
| } | |
| local_output.chain_position.is_confirmed() | |
| || self | |
| .tx_graph() | |
| .get_tx(local_output.outpoint.txid) | |
| .is_some_and(|tx| (tx.version == Version(3)) == (version == Version(3))) |
There was a problem hiding this comment.
I don't see the difference, it's filtering the same thing. However, having the predicate makes the code easier to follow.
There was a problem hiding this comment.
@ValuedMammal I'd say something like the code below is better, though it's does not read well for users not aware of what's happening.
local_output.chain_position.is_confirmed()
|| self
.tx_graph()
.get_tx(local_output.outpoint.txid)
.is_some_and(|tx| is_truc(tx.version) && is_truc(version))
| /// Check if the given [`transaction::Version`] is TRUC (Topologically Restricted Until | ||
| /// Confirmation). | ||
| fn is_truc(version: transaction::Version) -> bool { | ||
| version.eq(&Version(3)) | ||
| } |
There was a problem hiding this comment.
If going for a private helper, I'd argue this is clearer.
| /// Check if the given [`transaction::Version`] is TRUC (Topologically Restricted Until | |
| /// Confirmation). | |
| fn is_truc(version: transaction::Version) -> bool { | |
| version.eq(&Version(3)) | |
| } | |
| /// The transaction is considered to have opted in to v3 TRUC policy if the transaction version is 3 | |
| /// (BIP431). | |
| fn has_opted_into_v3_truc_policy(tx: &Transaction) -> bool { | |
| tx.version == Version(3) | |
| } |
There was a problem hiding this comment.
The docs looks better, though we don't have the full transaction when creating it, only the version is available.
partially addresses #477
supersedes #442
Description
I've tried this another approach in order to add support for BIP-431 rule 2. I think this way is clearer and more functional than the previously attempted in #442.
It introduces a new private helper method
is_truc, it's useful as this validation will be necessary for other BIP-431 rules. Also, it adds a new filter intofilter_utxosin order to filter out unconfirmed UTXOs from non-TRUC/TRUC txs.It also introduces the usage ofbdk_testenv, as there's some validation that is checked under node policy level.Notes to the reviewers
Let me know what you think about this one.
Changelog notice
Checklists
All Submissions:
just pbefore pushingNew Features:
Bugfixes: