Skip to content

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

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

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

Conversation

@oleonardolima
Copy link
Copy Markdown
Contributor

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

TBD

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

- 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.
- updates the previously added to reproduce the issue, to assert for the
  expected behavior instead of using the workaround.
@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
Contributor 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
Contributor 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
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 95.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 80.64%. Comparing base (a9ad3b9) to head (1d32117).

Files with missing lines Patch % Lines
src/wallet/mod.rs 95.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #478      +/-   ##
==========================================
+ Coverage   80.05%   80.64%   +0.58%     
==========================================
  Files          24       24              
  Lines        5360     5378      +18     
  Branches      244      247       +3     
==========================================
+ Hits         4291     4337      +46     
+ Misses        990      962      -28     
  Partials       79       79              
Flag Coverage Δ
rust 80.64% <95.00%> (+0.58%) ⬆️

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

☔ View full report in Codecov by Sentry.
📢 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.

Copy link
Copy Markdown

@yan-pi yan-pi left a comment

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
}

/// Check if the given [`transaction::Version`] is TRUC (Topologically Restricted Until
/// Confirmation)s.
Copy link
Copy Markdown

@yan-pi yan-pi May 6, 2026

Choose a reason for hiding this comment

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

Nit: (Topologically Restricted Until Confirmation)s has a stray s after the closing parenthesis.

Comment thread src/wallet/mod.rs
// if building TRUC; filter out all unconfirmed non-TRUC.
true => {
is_truc(ancestor_tx.version)
&& local_output.chain_position.is_unconfirmed()
Copy link
Copy Markdown

@yan-pi yan-pi May 6, 2026

Choose a reason for hiding this comment

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

Nit: local_output.chain_position.is_unconfirmed() is always true here ?

The is_confirmed() check on line 2020 short-circuits the keep case via ||, so by the time the match runs, local_output is guaranteed unconfirmed.

Maybe same occurs on line 2031.
I think you can drop both checks.

Comment thread src/wallet/mod.rs
// only add to optional UTXOs those that follows BIP-431 (TRUC) specification.
// see https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki#specification
.filter(|local_output| {
local_output.chain_position.is_confirmed()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

i think this logic can be simplified.

Maybe something like

                    if local_output.chain_position.is_confirmed() {
                        return true;
                    }

                    let Some(anscestor_tx) = self.tx_graph().get_tx(local_output.outpoint.txid)
                    else {
                        return true;
                    };

                    is_truc(version) && is_truc(anscestor_tx.version)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks! We can't use the && because we're filtering out different things, though it looks much better like this:

if local_output.chain_position.is_confirmed() {
    return true;
}

let Some(ancestor_tx) = self.tx_graph().get_tx(local_output.outpoint.txid)
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),
}

@yan-pi yan-pi mentioned this pull request Jun 1, 2026
8 tasks
@luisschwab luisschwab self-requested a review June 1, 2026 23:39
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: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants