Skip to content

fix(wallet): add support for bip-431 rule 2#478

Open
oleonardolima wants to merge 2 commits into
bitcoindevkit:masterfrom
oleonardolima:test/unconfirmed-truc-txs
Open

fix(wallet): add support for bip-431 rule 2#478
oleonardolima wants to merge 2 commits into
bitcoindevkit:masterfrom
oleonardolima:test/unconfirmed-truc-txs

Conversation

@oleonardolima

@oleonardolima oleonardolima commented May 4, 2026

Copy link
Copy Markdown
Collaborator

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 into filter_utxos in order to filter out unconfirmed UTXOs from non-TRUC/TRUC txs.

It also introduces the usage of bdk_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

### Fixed

- fix(wallet): filter unconfirmed UTXOs by BIP-431 (TRUC) rule-2

Checklists

All Submissions:

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@oleonardolima oleonardolima added this to the Wallet 3.1.0 milestone May 4, 2026
@oleonardolima oleonardolima requested a review from ValuedMammal May 4, 2026 20:30
@oleonardolima oleonardolima self-assigned this May 4, 2026
@oleonardolima oleonardolima added the bug Something isn't working label May 4, 2026
@oleonardolima

Copy link
Copy Markdown
Collaborator Author

cc @yan-pi as he already reviewed the other PR and has a reproducible test using bdk-cli too.

@oleonardolima

Copy link
Copy Markdown
Collaborator Author

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

codecov Bot commented May 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.00%. Comparing base (58fe631) to head (6145afc).

Files with missing lines Patch % Lines
src/wallet/mod.rs 90.00% 1 Missing and 1 partial ⚠️
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     
Flag Coverage Δ
rust 81.00% <90.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@yan-pi yan-pi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/wallet/mod.rs Outdated
Comment thread src/wallet/mod.rs Outdated
Comment thread src/wallet/mod.rs Outdated
@luisschwab luisschwab self-requested a review June 1, 2026 23:39
@oleonardolima oleonardolima force-pushed the test/unconfirmed-truc-txs branch 3 times, most recently from b8d9ae1 to 85143c2 Compare June 5, 2026 00:44

@lorenzolfm lorenzolfm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 85143c2

@ValuedMammal ValuedMammal marked this pull request as draft June 10, 2026 15:31
- 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.
@oleonardolima oleonardolima force-pushed the test/unconfirmed-truc-txs branch 2 times, most recently from 47bba04 to d6fca63 Compare June 15, 2026 19:08
- 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>
@oleonardolima oleonardolima force-pushed the test/unconfirmed-truc-txs branch from d6fca63 to 6145afc Compare June 15, 2026 19:23
@oleonardolima oleonardolima moved this from In Progress to Needs Review in BDK Wallet Jun 15, 2026
@oleonardolima oleonardolima marked this pull request as ready for review June 15, 2026 19:25

@lorenzolfm lorenzolfm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 6145afc

@oleonardolima oleonardolima requested review from notmandatory, nymius and yan-pi and removed request for yan-pi June 17, 2026 01:42
Comment thread src/wallet/mod.rs
Comment on lines +2071 to +2087
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),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think #442 had the right idea with the bidirectional filter.

Suggested change
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)))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the difference, it's filtering the same thing. However, having the predicate makes the code easier to follow.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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))

Comment thread src/wallet/mod.rs
Comment on lines +2973 to +2977
/// Check if the given [`transaction::Version`] is TRUC (Topologically Restricted Until
/// Confirmation).
fn is_truc(version: transaction::Version) -> bool {
version.eq(&Version(3))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If going for a private helper, I'd argue this is clearer.

Suggested change
/// 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)
}

@oleonardolima oleonardolima Jul 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs looks better, though we don't have the full transaction when creating it, only the version is available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

4 participants