Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions darepod/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep
exit-policy map (`recoveryExitPolicies`, built from the recovery store) and
re-admits each orphaned recovery target under its own vHTLC exit policy
rather than mislabeling it as a standard timeout.
- `recoverOrphanedUnrollJobs` recovers the exit *policy* but not the exit
*trigger*. A target force-exited under `TriggerFraudSpend` that crashed in
the gap between the VTXO status flip and the registry admission has no
registry record, so it re-admits as `TriggerRestart`. The only effect is
that its ready checkpoints broadcast immediately instead of deferring to
the recipient's fraud backstop window (`unroll.shouldSubmitReadyFrontier`):
earlier fees, same funds outcome, no missed deadline. A faithful fix would
stamp the trigger onto the VTXO row in the same transaction that flips it
to `UnilateralExit`; tracked as a separable follow-up in
darepo-client#914.
- The chain-resolver→unroll bridge (`ensureUnrollFromExpiring`) maps a VTXO
`ExpiringNotification`'s trigger and optional exit policy into the registry's
`EnsureUnrollRequest`. `unrollStartTrigger` converts the string-typed
Expand Down
10 changes: 10 additions & 0 deletions darepod/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darep
exit-policy map (`recoveryExitPolicies`, built from the recovery store) and
re-admits each orphaned recovery target under its own vHTLC exit policy
rather than mislabeling it as a standard timeout.
- `recoverOrphanedUnrollJobs` recovers the exit *policy* but not the exit
*trigger*. A target force-exited under `TriggerFraudSpend` that crashed in
the gap between the VTXO status flip and the registry admission has no
registry record, so it re-admits as `TriggerRestart`. The only effect is
that its ready checkpoints broadcast immediately instead of deferring to
the recipient's fraud backstop window (`unroll.shouldSubmitReadyFrontier`):
earlier fees, same funds outcome, no missed deadline. A faithful fix would
stamp the trigger onto the VTXO row in the same transaction that flips it
to `UnilateralExit`; tracked as a separable follow-up in
darepo-client#914.
- The chain-resolver→unroll bridge (`ensureUnrollFromExpiring`) maps a VTXO
`ExpiringNotification`'s trigger and optional exit policy into the registry's
`EnsureUnrollRequest`. `unrollStartTrigger` converts the string-typed
Expand Down
7 changes: 5 additions & 2 deletions db/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db.<S
client-tree queries).
- `RoundSummary` / `VTXOSummary` — lightweight projections for
paginated listing (avoids deserializing full trees).
- `VTXOPersistenceStore` — VTXO descriptor store
(`InsertClientVTXO`, `FetchByOutpoint`). Persists `ChainDepth`.
- `VTXOPersistenceStore` — VTXO descriptor store (`SaveVTXO`, `GetVTXO`).
Persists `ChainDepth`. `GetVTXO` translates a row miss into
`vtxo.ErrVTXONotFound` (keeping `sql.ErrNoRows` in the error chain for
call sites still migrating) so callers match the domain sentinel instead
of a persistence-layer error.
- `OORArtifactStore`, `OwnedReceiveScriptStore` — OOR session state
and locally owned receive-script metadata.
- `LedgerStoreDB` — implements `ledger.LedgerStore`. Wraps
Expand Down
7 changes: 5 additions & 2 deletions db/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db.<S
client-tree queries).
- `RoundSummary` / `VTXOSummary` — lightweight projections for
paginated listing (avoids deserializing full trees).
- `VTXOPersistenceStore` — VTXO descriptor store
(`InsertClientVTXO`, `FetchByOutpoint`). Persists `ChainDepth`.
- `VTXOPersistenceStore` — VTXO descriptor store (`SaveVTXO`, `GetVTXO`).
Persists `ChainDepth`. `GetVTXO` translates a row miss into
`vtxo.ErrVTXONotFound` (keeping `sql.ErrNoRows` in the error chain for
call sites still migrating) so callers match the domain sentinel instead
of a persistence-layer error.
- `OORArtifactStore`, `OwnedReceiveScriptStore` — OOR session state
and locally owned receive-script metadata.
- `LedgerStoreDB` — implements `ledger.LedgerStore`. Wraps
Expand Down
5 changes: 5 additions & 0 deletions vhtlcrecovery/coordinator/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ exit through the VTXO manager (`actormsg`) and reads status back from `unroll`.
if an existing unroll job already claimed the target under a different
`exit_policy_kind`, the recovery fails closed rather than exit under the
wrong policy.
- `UnrollRegistry.GetStatus` may return a nil status with a nil error — a
legitimate "no record yet" shape now that admission is asynchronous through
the VTXO manager, not an error. Both the post-force policy-conflict guard
and `GetRecoveryStatus`'s reconcile path treat a nil status the same as
`Found == false` and never dereference it directly.
- `EscalateRecovery` accepts an optional raw claim preimage, validates it
against the job's `preimage_hash`, then hands it to `Store.EscalateRecovery`
for persistence, but never logs it (`recoveryLogAttrs` omits `ClaimPreimage`
Expand Down
5 changes: 5 additions & 0 deletions vhtlcrecovery/coordinator/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ exit through the VTXO manager (`actormsg`) and reads status back from `unroll`.
if an existing unroll job already claimed the target under a different
`exit_policy_kind`, the recovery fails closed rather than exit under the
wrong policy.
- `UnrollRegistry.GetStatus` may return a nil status with a nil error — a
legitimate "no record yet" shape now that admission is asynchronous through
the VTXO manager, not an error. Both the post-force policy-conflict guard
and `GetRecoveryStatus`'s reconcile path treat a nil status the same as
`Found == false` and never dereference it directly.
- `EscalateRecovery` accepts an optional raw claim preimage, validates it
against the job's `preimage_hash`, then hands it to `Store.EscalateRecovery`
for persistence, but never logs it (`recoveryLogAttrs` omits `ClaimPreimage`
Expand Down
7 changes: 6 additions & 1 deletion vtxo/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ when the local wallet owns the receive script.
- `VTXOsMaterializedNotification` — Manager-facing notification carrying already-persisted descriptors; the manager spawns one actor per descriptor without performing another store write. Used by both the OOR receive path and the new incoming round VTXO handler.
- `LazyChainResolver` — Forwarding `TellOnlyRef[ExpiringNotification]` that buffers notifications until `Set()` wires the real chain-resolver target. Breaks the init-order dependency between the VTXO manager (which spawns `LazyChainResolver` at startup) and the unroll registry (which is wired after the VTXO manager starts). Buffered notifications are replayed in-order on `Set()`.
- `RefreshFeeQuoter` — Function type `func(ctx, amount btcutil.Amount, remainingBlocks uint32) btcutil.Amount`. Optional hook on `VTXOActorConfig`; invoked as an **advisory preview** before each auto-refresh emission to estimate the per-input operator fee for UX surfaces. Under the seal-time fee handshake (#270) the server is the binding fee authority — the quoter's return value is no longer attached to the wire intent. Nil quoter (legacy and test paths) yields `OperatorFee=0` on the harness-local `RefreshVTXORequest`, which has no effect on the round protocol.
- `ErrVTXONotFound` — Domain-level sentinel `VTXOStore.GetVTXO` returns when
the store has no record of the outpoint. Callers (e.g. the manager's
force-unroll path) match on this instead of a persistence-layer error like
`sql.ErrNoRows`, so the store decides how it signals a miss without the
manager depending on how it is backed.

## Relationships

Expand Down Expand Up @@ -139,7 +144,7 @@ when the local wallet owns the receive script.
- `ForceUnrollEvent` unifies every unilateral-exit trigger (manual `Unroll` RPC, fraud spend, vHTLC recovery) behind the manager's admission gate. It carries a `Trigger actormsg.UnrollTrigger` (zero value admits as critical expiry) and an `ExitPolicy fn.Option[actormsg.ExitPolicy]` (None selects the standard VTXO timeout policy); both ride through to the emitted `ExpiringNotification` so the chain-resolver bridge admits the registry job under the right `StartTrigger` and persists the correct exit-spend policy. It is accepted in `LiveState`, `PendingForfeitState`, `SpendingState`, and `ForfeitingState`: each transitions to `UnilateralExitState` and emits `ExpiringNotification` (trigger + exit policy threaded through) + `VTXOStatusUpdate{UnilateralExit}`. It does **not** emit `VTXOTerminatedNotification` on intent — `UnilateralExitState` is **non-terminal**, so the actor stays alive to observe the exit. Truly terminal states (`Spent`, `Forfeited`, `Failed`) self-loop; the manager maps that self-loop back to `ForceUnrollResponse{Accepted: false, Reason: "already terminal"}`. A `ForceUnrollEvent` on a VTXO already in `UnilateralExitState` is an idempotent re-admission, not a no-op: the actor stays in `UnilateralExitState`, does not re-persist the status, and **re-emits** the `ExpiringNotification` under the same trigger/policy so the chain-resolver bridge re-admits the job (the first admission's best-effort Tell can be lost to a crash before the registry writes its record; the registry dedups against a live record, so a redundant re-admit is a benign no-op).
- `UnilateralExitState` is **non-terminal** and observed, not fire-and-forget. The actor survives until the unroll job reports a terminal outcome via the manager's `ExitOutcomeNotification`: `ExitOutcomeRecoverable` (the unroll failed with no on-chain footprint) drives `ExitFailedEvent` → `LiveState` + `VTXOStatusUpdate{Live}`, while `ExitOutcomeConfirmed` (the exit confirmed on-chain) drives `ExitConfirmedEvent` → terminal `SpentState` + `VTXOTerminatedNotification` (the actor is reaped here, gated on a terminal on-chain event rather than the user's intent). A recoverable failure of a **recovery-only** target (`ExitOutcomeNotification.ExitPolicyKind.Valid()`, e.g. a vHTLC refund) is the exception: the manager holds the coin in `UnilateralExitState` rather than reliving it, since it is a swap-contract output, not spendable wallet liquidity, and reliving it would inflate balance and feed coin selection and sweep-all. This guard short-circuits before any store access; the owning recovery subsystem is responsible for retrying or terminal-failing the refund. When the actor is absent (e.g. a daemon restart, since exiting VTXOs are excluded from `ListLiveVTXOs` recovery) the manager re-materializes a live actor from the persisted descriptor (recover) or persists `VTXOStatusSpent` directly (confirm).
- `Manager.handleForceUnroll` uses `Ask` (not `Tell`) so FSM errors and self-loop no-ops surface as structured `ForceUnrollResponse{Accepted, Reason}` instead of a uniform `Accepted:true` that masks work that was never scheduled.
- When `handleForceUnroll` targets an outpoint with no live actor, `spawnForceUnrollActor` re-materializes an actor from the persisted descriptor so the manager still owns the exit rather than letting the caller admit the unroll behind its back. This is the common shape for the vHTLC recovery target (materialized directly in the store, never admitted through the manager) and any exiting VTXO a restart left out of the live-recovery set. It guards both ends: a missing descriptor returns `ForceUnrollResponse{Accepted: false, Reason: "no such vtxo"}`, and an already-terminal descriptor (`statusToState(...).IsTerminal()`) returns `Reason: "already terminal"` — neither spawns an actor that would immediately reap itself.
- When `handleForceUnroll` targets an outpoint with no live actor, `spawnForceUnrollActor` re-materializes an actor from the persisted descriptor so the manager still owns the exit rather than letting the caller admit the unroll behind its back. This is the common shape for the vHTLC recovery target (materialized directly in the store, never admitted through the manager) and any exiting VTXO a restart left out of the live-recovery set. It guards both ends: a `Store.GetVTXO` miss — signaled via `ErrVTXONotFound` or a nil descriptor, both treated identically — returns `ForceUnrollResponse{Accepted: false, Reason: "no such vtxo"}`, and an already-terminal descriptor (`statusToState(...).IsTerminal()`) returns `Reason: "already terminal"` — neither spawns an actor that would immediately reap itself. Any other `GetVTXO` error surfaces as a hard `"load vtxo for force-unroll"` failure, not a decline.
- Admission types (`SelectAndReserveSpendRequest`, `SelectAndReserveForfeitRequest`, `ReserveForfeitRequest`, etc.) are defined in `lib/actormsg` and re-exported as type aliases to avoid wallet → vtxo → round → wallet import cycles.
- `selectAndReserveVTXOs` is a shared helper parameterized by `reserveParams` that serves both the OOR spend and cooperative forfeit coin selection paths, avoiding code duplication.
- `IncomingVTXOHandler` only handles `VTXO_EVENT_TYPE_CREATED` events. Other event kinds, missing/short outpoints, empty pkScripts, oversized values (`> int64` or `> MaxSatoshi`), and tapscript derivation failures all return success without persisting — they cannot crash the actor or block the indexer push stream. Real DB lookup/save errors are surfaced.
Expand Down
Loading
Loading