Skip to content

round: separate VTXO activation from input maturity - #1199

Open
bhandras wants to merge 3 commits into
lightninglabs:mainfrom
bhandras:codex/reorg-live-vtxos-20260826
Open

round: separate VTXO activation from input maturity#1199
bhandras wants to merge 3 commits into
lightninglabs:mainfrom
bhandras:codex/reorg-live-vtxos-20260826

Conversation

@bhandras

@bhandras bhandras commented Aug 26, 2026

Copy link
Copy Markdown
Member

Summary

Advertise two independent confirmation policies:

  • min_confirmations: maturity required for on-chain boarding inputs;
  • vtxo_confirmations: depth at which a newly confirmed round VTXO becomes
    available for off-chain use.

The round client now watches a commitment to the second target. New clients
fall back to the old coupled behavior when an older operator omits the new
field.

This PR also preserves the reason for a retryable operator rejection while an
outgoing OOR session remains pending. That lets callers see why the transfer is
waiting without changing its automatic retry behavior.

Motivation

Input maturity and VTXO activation protect different boundaries.

An on-chain boarding input becomes an economic input to the next commitment.
Requiring it to be mature reduces the chance that a shallow reorganization can
replace that input and change the commitment transaction.

A VTXO created by an already-broadcast commitment is an off-chain output. It
can be useful after one confirmation even while the commitment's economic
inputs remain protected through a deeper wallet and server safety horizon.

Using one field for both concepts forces a product tradeoff that is not
required by the protocol:

  • raising the input-maturity policy also delays every new VTXO; or
  • keeping VTXOs fast also permits young boarding inputs.

This PR makes the two policies explicit. It keeps the client state model small:
the round still has one confirmation wait and one activation transition. Only
the target depth changes.

The retry-reason change serves a separate but related observability need. If an
operator temporarily rejects an OOR input because its root commitment is not
currently spendable, the client already keeps the same session pending and
retries it. Exposing the stored reason makes that wait visible to users and
integration tests.

Architecture

Confirmation policy flow

The policy flows in one direction:

  1. The operator includes vtxo_confirmations in arkrpc.GetInfoResponse.
  2. waved converts the RPC response into types.OperatorTerms.
  3. The daemon exposes both confirmation policies through its own GetInfo.
  4. The SDK exposes both values to applications.
  5. The round actor uses VTXOTargetConfirmations() when registering the
    commitment confirmation watch.
  6. The existing confirmed-round transition makes the VTXOs available when that
    watch fires.

No new persisted availability state, enum, lineage database, or reorg reducer
is added. The client continues to derive availability from its existing round
state and the operator's advertised confirmation target.

Retryable OOR rejection visibility

An outgoing OOR submit can receive a retryable rejection. The transition stays
in AwaitingSubmitAccepted, copies the retry delay and reason into that state,
and schedules the existing retry.

The outgoing-session snapshot writes those values into the existing v5
retry_after and fail_reason fields. Restore reads the same fields back into
the pending state. GetOORSession can therefore report the reason before and
after restart.

This does not add an RPC field or database migration. It does not make the
session terminal. A later retry uses the same session and proceeds through the
existing submit, finalize, and local VTXO update states.

Compatibility

Both confirmation RPC additions are optional protobuf fields.

  • New client with new operator: uses the split policy.
  • New client with old operator: a zero/absent vtxo_confirmations falls back to
    min_confirmations, preserving the old behavior.
  • Old client with new operator: ignores the unknown field and continues to use
    min_confirmations for both purposes.
  • Persisted/test OperatorTerms with a zero split field: the helper applies the
    same legacy fallback.

The OOR retry metadata reuses fields that already exist in the v5 snapshot.
Older snapshots remain readable. No wire compatibility changes are required.

This makes rollout order flexible. Operators may advertise the policy before
all clients upgrade without changing old-client behavior.

Why this shape

The change keeps policy ownership at the existing boundaries:

  • the operator chooses and advertises the confirmation depths;
  • the client owns the point at which a newly created VTXO becomes usable;
  • the OOR actor owns its pending retry state and public session projection;
  • deeper server-side lineage and wallet rules remain server concerns.

Separating two numbers is enough to preserve one-confirmation VTXO activity.
The client does not need to model the operator's wallet leases or duplicate a
server-side canonicality state machine.

User-visible behavior

With an operator policy such as:

min_confirmations  = 6
vtxo_confirmations = 1

boarding funds must be six confirmations deep before they can enter a round,
while VTXOs produced by that round become available after one confirmation.

The daemon and SDK report both values, so applications can explain boarding
latency separately from VTXO activation latency.

If a pending OOR send receives a retryable INPUT_NOT_SPENDABLE rejection, its
session remains pending and now reports the operator reason. The existing retry
loop clears the condition by progressing the same session once the operator
accepts the input again.

Testing

Passed locally:

  • make rpc
  • go test ./lib/types ./round
  • go test ./waved -run 'Test(OperatorTermsVTXOConfirmations|GetInfoIncludesServerInfo)'
  • go test ./sdk/ark -run '^TestDialRemoteGetInfo$'
  • go test ./oor ./waved
  • make lint-changed-local

The OOR tests cover retry reason propagation through the in-memory state and
the v5 outgoing snapshot. The confirmation-policy tests cover explicit split
terms and old-server fallback.

The dependent Lumos daemon E2E uses real bitcoind, LND, Lumos, Bob, and Carol.
It removes the root commitment from the best chain, observes the typed pending
reason through GetOORSession, re-mines the same commitment txid, and proves
that the same OOR session automatically completes. That test is kept in the
server PR because it owns the reorg trigger and lineage admission policy.

Commits

  1. rpc: advertise VTXO activation depth separately
  2. round: use advertised VTXO activation depth
  3. oor: expose pending submit rejection reason

@bhandras
bhandras force-pushed the codex/reorg-live-vtxos-20260826 branch from 3711536 to e023692 Compare August 26, 2026 10:10
Add an optional GetInfo field for the confirmation depth at which new
round VTXOs become available. Keep the existing minimum-confirmations
field focused on boarding-input maturity.

New clients fall back to the legacy coupled policy when an older server
omits the field. The daemon and SDK expose both values so callers can
explain the different safety and availability horizons.
Use the separate VTXO confirmation target when watching a new round
commitment. This allows one-confirmation off-chain activity without
weakening the operator's input maturity policy.

The helper retains the legacy minimum-confirmations fallback. Existing
persisted and test terms keep their previous behavior.
A retryable rejection kept the OOR session pending.

Operation status lost the classified reason.

A chain pause looked like an ordinary in-flight transfer.

Carry the existing retry fields on AwaitingSubmitAccepted.

Persist them through the existing v5 snapshot records.

The session stays retryable.

GetOORSession now exposes the typed cause.
@bhandras
bhandras force-pushed the codex/reorg-live-vtxos-20260826 branch from e023692 to 7a4ffff Compare August 26, 2026 13:41
@bhandras
bhandras marked this pull request as ready for review August 26, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant