Skip to content

feat(chain)!: add Locked eligibility for timelocked outputs - #2321

Open
Dmenec wants to merge 2 commits into
bitcoindevkit:masterfrom
Dmenec:feat/eligibility-locked
Open

Dmenec wants to merge 2 commits into
bitcoindevkit:masterfrom
Dmenec:feat/eligibility-locked

Conversation

@Dmenec

@Dmenec Dmenec commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds Eligibility::Locked and Balance::locked for confirmed outputs whose timelock hasn't matured yet, so they aren't counted as spendable confirmed balance. classify_outpoints and balance take a new is_locked predicate, evaluated only on settled outputs.

Notes to the reviewers

The timelock lives in the output's descriptor, so CanonicalView can't evaluate it from chain data alone. The caller passes is_locked instead, the same way it passes does_taint. The wallet side will follow in a separate bdk_wallet PR.

This category is only meant for timelocked (CSV, CLTV) coins. Other locked coins that aren't enforced by consensus shouldn't use it, since is_locked is only evaluated on settled outputs. Frozen or reserved coins are expected to be handled in the wallet.

Changelog notice

Breaking: CanonicalView::classify_outpoints and balance take a new is_locked predicate. Added Eligibility::Locked and Balance::locked.

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

@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.04%. Comparing base (4dc5e5a) to head (698d67a).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
crates/chain/src/canonical.rs 87.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2321      +/-   ##
==========================================
+ Coverage   78.84%   79.04%   +0.20%     
==========================================
  Files          31       31              
  Lines        6060     6066       +6     
  Branches      288      289       +1     
==========================================
+ Hits         4778     4795      +17     
+ Misses       1203     1192      -11     
  Partials       79       79              
Flag Coverage Δ
rust 79.04% <90.00%> (+0.20%) ⬆️

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.

@nymius nymius left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cACK cf0edd7

This is a valuable feature. Implementation is missing more complex mainnet like tests.
Try to keep coverage percentage positive, i.e., >0%.

Comment thread crates/chain/src/canonical.rs Outdated
/// A settled output for which `is_locked` returns true is classified `Locked` and counted in
/// `Balance::locked` instead of `confirmed`.
#[test]
fn test_classify_locked() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would like a test with script introspection, i.e., verifying the actual use of OP_CSV or OP_CLTV. We cannot know is something else is needed from the graph if we only use the is_locked function as a boolean constant.

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.

Yes, would be nice to have them. Should I add them as a doctest? is_locked only works as a boolean here, so tests using different kinds of timelocks would probably fit better on the bdk_wallet side.

@nymius nymius Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why as a doctest? I think implementing them as a test is better. I would simulate a wallet here if needed, and test it here. This is a more complex unit test, but unit test in the end, it shouldn't live in a downstream dependency.

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.

I thought on doing some doctests as it would help callers to implement their balances with timelocks, and I was only thinking of the usage of is_locked as it is. But if some complex things have to be done on the callers side, as you said, maybe there's something else needed from the graph.I'll add them as a test and check.

@Dmenec Dmenec Sep 30, 2026 •

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.

Solved in 698d67a.

I think it's much clearer now how is_locked should be called for these cases.

I'm sure more complex timelock constructions would require a different is_locked signature, maybe the whole descriptor. I'm not sure whether adding coverage for those cases here would provide additional value or just introduce redundancy.

I'm not sure how we could achieve the time based timelocks, maybe the evaluation of the mtp should be done in chain?

Add an `Eligibility::Locked` variant and a `Balance::locked` field, plus an `is_locked` predicate on `classify_outpoints`/`balance` so the caller marks confirmed outputs whose timelock has not matured yet.
@Dmenec
Dmenec force-pushed the feat/eligibility-locked branch 3 times, most recently from c04bb52 to 833100b Compare September 30, 2026 13:24
@Dmenec
Dmenec force-pushed the feat/eligibility-locked branch from 833100b to 698d67a Compare October 1, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants