From 46b4a30b7edbc0db342fcdb146a0cc45bcc88e7f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 9 Jul 2026 15:38:40 -0700 Subject: [PATCH 1/4] docs: correct stale commands and paths in root guide Fix the highest-traffic doc: the Quick Commands table referenced a nonexistent 'make itest' target (integration tests run under 'make systest'); the Critical Rules and Code Generation sections pointed at db/queries/ and db/schema/ instead of the real db/sqlc/queries/ and db/sqlc/migrations/; and the style summary cited a .editorconfig that does not exist in this repo. --- AGENTS.md | 8 ++++---- CLAUDE.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index be5ccc364..00f7c2ccc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ This file is a **map**, not a manual. Follow links for details. | `make fmt-changed-check` | Verify changed Go source files are formatted | | `make unit pkg= case=` | Run unit tests | | `make unit log="stdlog trace" pkg= case=` | Unit tests with debug logs | -| `make itest icase=` | Integration test | +| `make systest` | System integration tests (use `db=postgres` for PostgreSQL) | | `make tidy-module-check` | Verify module files are tidy | | `make rpc` | Regenerate protobuf stubs | | `make sqlc` | Regenerate type-safe DB queries | @@ -27,7 +27,7 @@ This file is a **map**, not a manual. Follow links for details. ## Code Style (Summary) -- **8-space tabs** (see `.editorconfig`), 80-char line limit (best effort). +- **8-space tabs**, 80-char line limit (best effort). - Every function/method gets a comment starting with its name. - Exported identifiers need GoDoc comments wrapped to 80 columns. - Organize code into logical stanzas with explanatory comments between them. @@ -54,7 +54,7 @@ Body wrapped at 72 characters. Explain WHY, not just WHAT. ## Critical Rules 1. **Never edit generated code** — regenerate via `make rpc` or `make sqlc`. -2. **Never write raw SQL in Go** — add queries to `db/queries/`, use sqlc. +2. **Never write raw SQL in Go** — add queries to `db/sqlc/queries/`, use sqlc. 3. **Run `make fmt-changed` before every commit.** This applies `goimports` and `llformat` to changed handwritten Go files. Use `make fmt` instead when you intentionally need a full-tree format pass. @@ -130,7 +130,7 @@ and navigate into the package relevant to your task. ## Code Generation Workflow 1. **Protobuf**: edit `.proto` → `make rpc` → commit generated code separately. -2. **Database**: edit `db/schema/` or `db/queries/` → `make sqlc` → commit separately. +2. **Database**: edit `db/sqlc/migrations/` or `db/sqlc/queries/` → `make sqlc` → commit separately. 3. **Never edit generated code manually.** ## Dependencies diff --git a/CLAUDE.md b/CLAUDE.md index be5ccc364..00f7c2ccc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,7 @@ This file is a **map**, not a manual. Follow links for details. | `make fmt-changed-check` | Verify changed Go source files are formatted | | `make unit pkg= case=` | Run unit tests | | `make unit log="stdlog trace" pkg= case=` | Unit tests with debug logs | -| `make itest icase=` | Integration test | +| `make systest` | System integration tests (use `db=postgres` for PostgreSQL) | | `make tidy-module-check` | Verify module files are tidy | | `make rpc` | Regenerate protobuf stubs | | `make sqlc` | Regenerate type-safe DB queries | @@ -27,7 +27,7 @@ This file is a **map**, not a manual. Follow links for details. ## Code Style (Summary) -- **8-space tabs** (see `.editorconfig`), 80-char line limit (best effort). +- **8-space tabs**, 80-char line limit (best effort). - Every function/method gets a comment starting with its name. - Exported identifiers need GoDoc comments wrapped to 80 columns. - Organize code into logical stanzas with explanatory comments between them. @@ -54,7 +54,7 @@ Body wrapped at 72 characters. Explain WHY, not just WHAT. ## Critical Rules 1. **Never edit generated code** — regenerate via `make rpc` or `make sqlc`. -2. **Never write raw SQL in Go** — add queries to `db/queries/`, use sqlc. +2. **Never write raw SQL in Go** — add queries to `db/sqlc/queries/`, use sqlc. 3. **Run `make fmt-changed` before every commit.** This applies `goimports` and `llformat` to changed handwritten Go files. Use `make fmt` instead when you intentionally need a full-tree format pass. @@ -130,7 +130,7 @@ and navigate into the package relevant to your task. ## Code Generation Workflow 1. **Protobuf**: edit `.proto` → `make rpc` → commit generated code separately. -2. **Database**: edit `db/schema/` or `db/queries/` → `make sqlc` → commit separately. +2. **Database**: edit `db/sqlc/migrations/` or `db/sqlc/queries/` → `make sqlc` → commit separately. 3. **Never edit generated code manually.** ## Dependencies From 29b7c3683d0e5ccdfb540b8fa1543b600a0c9f33 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 9 Jul 2026 15:38:40 -0700 Subject: [PATCH 2/4] docs: document 16 previously-undocumented packages Add per-package CLAUDE.md/AGENTS.md for packages that had no agent doc (credit, coinselect, chainfees, rpcauth, chainbackends/lndsubmitter, internal/sqlbase, sdk/walletdk/mobile, cmd/walletdk-wasm, and the generated proto/sqlc stubs). Slot the substantive packages into the ARCHITECTURE.md layer tables; generated stubs stay covered by the existing rpc/mailbox rollup rows. --- ARCHITECTURE.md | 9 ++++ chainbackends/lndsubmitter/AGENTS.md | 51 ++++++++++++++++++++++ chainbackends/lndsubmitter/CLAUDE.md | 51 ++++++++++++++++++++++ chainfees/AGENTS.md | 41 ++++++++++++++++++ chainfees/CLAUDE.md | 41 ++++++++++++++++++ cmd/walletdk-wasm/AGENTS.md | 46 ++++++++++++++++++++ cmd/walletdk-wasm/CLAUDE.md | 46 ++++++++++++++++++++ coinselect/AGENTS.md | 41 ++++++++++++++++++ coinselect/CLAUDE.md | 41 ++++++++++++++++++ credit/AGENTS.md | 62 ++++++++++++++++++++++++++ credit/CLAUDE.md | 62 ++++++++++++++++++++++++++ db/actordelivery/sqlc/AGENTS.md | 34 +++++++++++++++ db/actordelivery/sqlc/CLAUDE.md | 34 +++++++++++++++ db/sqlc/AGENTS.md | 39 +++++++++++++++++ db/sqlc/CLAUDE.md | 39 +++++++++++++++++ internal/sqlbase/AGENTS.md | 50 +++++++++++++++++++++ internal/sqlbase/CLAUDE.md | 50 +++++++++++++++++++++ mailbox/pb/AGENTS.md | 39 +++++++++++++++++ mailbox/pb/CLAUDE.md | 39 +++++++++++++++++ rpc/oorpb/AGENTS.md | 51 ++++++++++++++++++++++ rpc/oorpb/CLAUDE.md | 51 ++++++++++++++++++++++ rpc/swapclientrpc/AGENTS.md | 31 +++++++++++++ rpc/swapclientrpc/CLAUDE.md | 31 +++++++++++++ rpcauth/AGENTS.md | 38 ++++++++++++++++ rpcauth/CLAUDE.md | 38 ++++++++++++++++ sdk/swaps/sqlc/AGENTS.md | 31 +++++++++++++ sdk/swaps/sqlc/CLAUDE.md | 31 +++++++++++++ sdk/walletdk/mobile/AGENTS.md | 65 ++++++++++++++++++++++++++++ sdk/walletdk/mobile/CLAUDE.md | 65 ++++++++++++++++++++++++++++ serverconn/hellotestpb/AGENTS.md | 47 ++++++++++++++++++++ serverconn/hellotestpb/CLAUDE.md | 47 ++++++++++++++++++++ swaprpc/AGENTS.md | 58 +++++++++++++++++++++++++ swaprpc/CLAUDE.md | 58 +++++++++++++++++++++++++ 33 files changed, 1457 insertions(+) create mode 100644 chainbackends/lndsubmitter/AGENTS.md create mode 100644 chainbackends/lndsubmitter/CLAUDE.md create mode 100644 chainfees/AGENTS.md create mode 100644 chainfees/CLAUDE.md create mode 100644 cmd/walletdk-wasm/AGENTS.md create mode 100644 cmd/walletdk-wasm/CLAUDE.md create mode 100644 coinselect/AGENTS.md create mode 100644 coinselect/CLAUDE.md create mode 100644 credit/AGENTS.md create mode 100644 credit/CLAUDE.md create mode 100644 db/actordelivery/sqlc/AGENTS.md create mode 100644 db/actordelivery/sqlc/CLAUDE.md create mode 100644 db/sqlc/AGENTS.md create mode 100644 db/sqlc/CLAUDE.md create mode 100644 internal/sqlbase/AGENTS.md create mode 100644 internal/sqlbase/CLAUDE.md create mode 100644 mailbox/pb/AGENTS.md create mode 100644 mailbox/pb/CLAUDE.md create mode 100644 rpc/oorpb/AGENTS.md create mode 100644 rpc/oorpb/CLAUDE.md create mode 100644 rpc/swapclientrpc/AGENTS.md create mode 100644 rpc/swapclientrpc/CLAUDE.md create mode 100644 rpcauth/AGENTS.md create mode 100644 rpcauth/CLAUDE.md create mode 100644 sdk/swaps/sqlc/AGENTS.md create mode 100644 sdk/swaps/sqlc/CLAUDE.md create mode 100644 sdk/walletdk/mobile/AGENTS.md create mode 100644 sdk/walletdk/mobile/CLAUDE.md create mode 100644 serverconn/hellotestpb/AGENTS.md create mode 100644 serverconn/hellotestpb/CLAUDE.md create mode 100644 swaprpc/AGENTS.md create mode 100644 swaprpc/CLAUDE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ad59aaf2b..162039472 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -28,6 +28,8 @@ package may import from a higher layer. | [`lib/recovery`](lib/recovery/) | Immutable recovery proof graph, session state machine, TLV codec for unilateral exit | | [`unrollplan`](unrollplan/) | Pure dependency-resolution planner driving unilateral-exit broadcast/sweep ordering | | [`vhtlcrecovery`](vhtlcrecovery/) | Durable control-plane types for vHTLC on-chain recovery jobs (action, state, script parameters, swap linkage) | +| [`credit`](credit/) | Client-side credit subsystem: supervisor/per-operation-actor pair driving fault-tolerant sub-dust pay, credit-receive, and redeem flows against the authoritative server ledger | +| [`coinselect`](coinselect/) | Single coin-type-agnostic coin-selection algorithm shared across wallet backends | ### Layer 2: Infrastructure (Chain, Storage, Messaging) @@ -36,6 +38,8 @@ package may import from a higher layer. | [`baselib`](baselib/) | Actor framework (`baselib/actor`) and protofsm state machine engine (`baselib/protofsm`) | | [`chainsource`](chainsource/) | `ChainBackend` interface: fee estimation, block/conf/spend notifications | | [`chainbackends`](chainbackends/) | LND-backed `ChainBackend` implementation plus lndclient adapters (`TxBroadcaster`, `PackageSubmitter`) | +| [`chainbackends/lndsubmitter`](chainbackends/lndsubmitter/) | `chainbackends.PackageSubmitter` over lnd's WalletKit; the default LND package-relay submitter | +| [`chainfees`](chainfees/) | Reusable `chainfee.Estimator` implementations and combinators for pricing transactions | | [`chain`](chain/) | Bitcoind RPC utilities (package relay, `SubmitPackage`) | | [`txconfirm`](txconfirm/) | Generic "broadcast + CPFP fee-bump + notify on confirm" actor with per-parent fee-input reservations and BIP-125 Rule 3/4 enforcement | | [`unroll`](unroll/) | Durable per-target unilateral-exit actor + thin registry: owns proof assembly, materialization, CSV maturity, final sweep build, persist-before-broadcast, and control-plane record persistence | @@ -51,6 +55,8 @@ package may import from a higher layer. | [`mailbox`](mailbox/) | Mailbox protocol primitives across three sub-packages (pb, rpc, conn) | | [`serverconn`](serverconn/) | Unified server connector: durable egress, ingress polling, unary RPC facade | | [`serverconn/mailboxpull`](serverconn/mailboxpull/) | Shared exponential-backoff retry primitives for mailbox pull loops (used by serverconn ingress and SDK swap consumers) | +| [`rpcauth`](rpcauth/) | Shared macaroon and TLS helpers securing gRPC/REST connections | +| [`internal/sqlbase`](internal/sqlbase/) | `walletdb`-compatible key/value backend over `database/sql` (js/wasm walletdb storage for `lwwallet` browser builds) | ### Layer 3: Application & Orchestration @@ -61,10 +67,12 @@ package may import from a higher layer. | [`sdk/ark`](sdk/ark/) | Consumer-facing Go SDK facade: remote or embedded daemon access with typed models | | [`sdk/swaps`](sdk/swaps/) | Lightning-to-Ark / Ark-to-Lightning atomic swap SDK with durable FSM flows | | [`sdk/walletdk`](sdk/walletdk/) | Wallet-shaped SDK facade for host apps: embeds the daemon in-process, dials it over a private bufconn transport, exposes typed methods for the seven core wallet verbs (create, unlock, send, recv, list, balance, exit). The highest-level layer in the stack; wraps `walletdkrpc.WalletService`. Wallet RPC methods gated behind `walletdkrpc` (which transitively requires `swapruntime`) | +| [`sdk/walletdk/mobile`](sdk/walletdk/mobile/) | Gomobile-safe facade over `sdk/walletdk` for Android/iOS host apps: drives an embedded in-process wallet over the private bufconn transport | | [`swapwallet`](swapwallet/) | Optional daemon-side `walletdkrpc.WalletService` implementation (build tags `walletdkrpc swapruntime`): composes the swap subsystem, cooperative leave, boarding, ledger, and unilateral-exit registry behind one flat, swap-vocabulary-free wallet API | | [`swapclientserver`](swapclientserver/) | Optional daemon-side swap subserver (build tag `swapruntime`): translates `swapclientrpc` RPCs into `sdk/swaps` operations and manages the daemon-local worker registry | | [`cmd/darepod`](cmd/darepod/) | Daemon entry point | | [`cmd/darepocli`](cmd/darepocli/) | CLI client | +| [`cmd/walletdk-wasm`](cmd/walletdk-wasm/) | Command compiling the embedded walletdk runtime to a browser WASM binary | | [`timeout`](timeout/) | Generic timeout scheduling actor | | [`indexer`](indexer/) | Server indexing client for receive script registration | | [`arkrpc`](arkrpc/) | Server-side gRPC service definitions (ArkService, IndexerService) | @@ -73,6 +81,7 @@ package may import from a higher layer. | [`rpc/walletdkrpc`](rpc/walletdkrpc/) | Highest-level gRPC surface: `WalletService` with the seven core wallet verbs. Composes `daemonrpc` and `rpc/swapclientrpc` server-side via `swapwallet` | | [`rpc/restclient`](rpc/restclient/) | HTTP/protoJSON transport adapter: `Client`, `StreamClient[T]`, and per-service factory functions implementing the same gRPC stub interfaces over REST | | [`daemonrpc`](daemonrpc/) | Daemon gRPC API definitions | +| [`swaprpc`](swaprpc/) | Generated gRPC/REST/mailbox-RPC stubs for the external `SwapService` | ### Layer 4: Testing & Tooling diff --git a/chainbackends/lndsubmitter/AGENTS.md b/chainbackends/lndsubmitter/AGENTS.md new file mode 100644 index 000000000..ce8aa468a --- /dev/null +++ b/chainbackends/lndsubmitter/AGENTS.md @@ -0,0 +1,51 @@ +# chainbackends/lndsubmitter + +## Purpose + +Implements `chainbackends.PackageSubmitter` on top of lnd's +`WalletKit.SubmitPackage` RPC. It lets an lnd-backed darepod relay its +zero-fee unilateral-exit v3/TRUC packages through lnd's own chain +connection, so no separate bitcoind RPC or Esplora endpoint is required +for CPFP fee bumping. + +## Key Types + +- `Submitter` — Relays parent+child packages through lnd's WalletKit. + Constructed via `New(walletKit)`. +- `New(walletKit)` — Builds a `Submitter` from anything satisfying the + package's narrow `walletKitSubmitter` interface (the subset of + `lndclient.WalletKitClient` used here); `lndclient.WalletKitClient` + satisfies it directly, and the narrow surface keeps the submitter easy + to fake in tests. + +## Relationships + +- **Depends on**: `chainbackends` (implements its `PackageSubmitter` + interface and returns its `btcjson.SubmitPackageResult` shape), + `lndclient` (`WalletKitClient.SubmitPackage`, `SubmitPackageResult`). +- **Depended on by**: `darepod` (`server.go` wires `lndsubmitter.New` as + the default `PackageSubmitter` for `WalletTypeLnd` whenever + `cfg.PackageSubmitter` is not explicitly injected, i.e. an explicit + bitcoind-backed submitter always takes precedence). + +## Invariants + +- `chainbackends.PackageSubmitter.SubmitPackage`'s `maxFeeRate` is a + `*float64` in BTC/kvB (bitcoind's `maxfeerate` shape); lnd's RPC wants + an integer sat/vByte. The conversion rounds to the nearest sat/vByte + rather than truncating — truncation would silently lower the ceiling + (e.g. 12.5 -> 12), making it stricter than the caller asked for. A nil + `maxFeeRate` passes through unchanged as the node default. +- `parents` must be topologically sorted (unconfirmed parents first) with + `child` last; `SubmitPackage` assembles them in that order before + calling lnd. +- Nil `child` or any nil entry in `parents` is rejected up front with an + error instead of being forwarded, since a nil `*wire.MsgTx` would + otherwise panic deep in lndclient/wire serialization. +- `mapResult` treats an empty `TxResults[wtxid].Err` string as acceptance; + only a non-empty reject reason is surfaced as `Error` on the mapped + `btcjson.SubmitPackageTxResult`. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/chainbackends/lndsubmitter/CLAUDE.md b/chainbackends/lndsubmitter/CLAUDE.md new file mode 100644 index 000000000..ce8aa468a --- /dev/null +++ b/chainbackends/lndsubmitter/CLAUDE.md @@ -0,0 +1,51 @@ +# chainbackends/lndsubmitter + +## Purpose + +Implements `chainbackends.PackageSubmitter` on top of lnd's +`WalletKit.SubmitPackage` RPC. It lets an lnd-backed darepod relay its +zero-fee unilateral-exit v3/TRUC packages through lnd's own chain +connection, so no separate bitcoind RPC or Esplora endpoint is required +for CPFP fee bumping. + +## Key Types + +- `Submitter` — Relays parent+child packages through lnd's WalletKit. + Constructed via `New(walletKit)`. +- `New(walletKit)` — Builds a `Submitter` from anything satisfying the + package's narrow `walletKitSubmitter` interface (the subset of + `lndclient.WalletKitClient` used here); `lndclient.WalletKitClient` + satisfies it directly, and the narrow surface keeps the submitter easy + to fake in tests. + +## Relationships + +- **Depends on**: `chainbackends` (implements its `PackageSubmitter` + interface and returns its `btcjson.SubmitPackageResult` shape), + `lndclient` (`WalletKitClient.SubmitPackage`, `SubmitPackageResult`). +- **Depended on by**: `darepod` (`server.go` wires `lndsubmitter.New` as + the default `PackageSubmitter` for `WalletTypeLnd` whenever + `cfg.PackageSubmitter` is not explicitly injected, i.e. an explicit + bitcoind-backed submitter always takes precedence). + +## Invariants + +- `chainbackends.PackageSubmitter.SubmitPackage`'s `maxFeeRate` is a + `*float64` in BTC/kvB (bitcoind's `maxfeerate` shape); lnd's RPC wants + an integer sat/vByte. The conversion rounds to the nearest sat/vByte + rather than truncating — truncation would silently lower the ceiling + (e.g. 12.5 -> 12), making it stricter than the caller asked for. A nil + `maxFeeRate` passes through unchanged as the node default. +- `parents` must be topologically sorted (unconfirmed parents first) with + `child` last; `SubmitPackage` assembles them in that order before + calling lnd. +- Nil `child` or any nil entry in `parents` is rejected up front with an + error instead of being forwarded, since a nil `*wire.MsgTx` would + otherwise panic deep in lndclient/wire serialization. +- `mapResult` treats an empty `TxResults[wtxid].Err` string as acceptance; + only a non-empty reject reason is surfaced as `Error` on the mapped + `btcjson.SubmitPackageTxResult`. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/chainfees/AGENTS.md b/chainfees/AGENTS.md new file mode 100644 index 000000000..fa57dd5cb --- /dev/null +++ b/chainfees/AGENTS.md @@ -0,0 +1,41 @@ +# chainfees + +## Purpose + +Reusable `chainfee.Estimator` implementations and combinators used to price +on-chain transactions from wallet and daemon chain backends. + +## Key Types + +- `WalletKitEstimator` — proxies fee estimates to an `lndclient.WalletKitClient`; + fail-fast by default, optional `FallbackOnError` to serve the last + successful rate instead of propagating errors. +- `MempoolSpaceEstimator` — fetches recommended fees from the mempool.space + HTTP API, with a TTL cache and network-default endpoint selection. +- `MinEstimator` — composes multiple `NamedEstimator` children and returns the + lowest successful estimate, logging when providers diverge. +- `NamedEstimator` — pairs a `chainfee.Estimator` with a stable name for logs. + +## Relationships + +- **Depends on**: `lndclient` (WalletKit RPC client), `lnd/lnwallet/chainfee` + (the `Estimator` interface and `SatPerKWeight`/`FeePerKwFloor` types). +- **Depended on by**: `chainbackends` (wires these estimators into chain + backend adapters), `darepod` (server wiring and logging subsystem + registration). + +## Invariants + +- Only `NewWalletKitEstimator` (fail-fast) belongs inside `MinEstimator`; + `NewFallbackWalletKitEstimator` must only back a standalone estimator, since + a stale/floor fallback could otherwise incorrectly beat another provider's + live estimate. +- All estimates are clamped to `chainfee.FeePerKwFloor` before being cached or + returned; a cached rate below the floor is the sentinel for "no successful + estimate yet" (see `WalletKitEstimator.cachedRate`). +- `MempoolSpaceEstimator` rejects non-HTTPS URLs except for loopback hosts, to + avoid tampering with fee data in transit. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/chainfees/CLAUDE.md b/chainfees/CLAUDE.md new file mode 100644 index 000000000..fa57dd5cb --- /dev/null +++ b/chainfees/CLAUDE.md @@ -0,0 +1,41 @@ +# chainfees + +## Purpose + +Reusable `chainfee.Estimator` implementations and combinators used to price +on-chain transactions from wallet and daemon chain backends. + +## Key Types + +- `WalletKitEstimator` — proxies fee estimates to an `lndclient.WalletKitClient`; + fail-fast by default, optional `FallbackOnError` to serve the last + successful rate instead of propagating errors. +- `MempoolSpaceEstimator` — fetches recommended fees from the mempool.space + HTTP API, with a TTL cache and network-default endpoint selection. +- `MinEstimator` — composes multiple `NamedEstimator` children and returns the + lowest successful estimate, logging when providers diverge. +- `NamedEstimator` — pairs a `chainfee.Estimator` with a stable name for logs. + +## Relationships + +- **Depends on**: `lndclient` (WalletKit RPC client), `lnd/lnwallet/chainfee` + (the `Estimator` interface and `SatPerKWeight`/`FeePerKwFloor` types). +- **Depended on by**: `chainbackends` (wires these estimators into chain + backend adapters), `darepod` (server wiring and logging subsystem + registration). + +## Invariants + +- Only `NewWalletKitEstimator` (fail-fast) belongs inside `MinEstimator`; + `NewFallbackWalletKitEstimator` must only back a standalone estimator, since + a stale/floor fallback could otherwise incorrectly beat another provider's + live estimate. +- All estimates are clamped to `chainfee.FeePerKwFloor` before being cached or + returned; a cached rate below the floor is the sentinel for "no successful + estimate yet" (see `WalletKitEstimator.cachedRate`). +- `MempoolSpaceEstimator` rejects non-HTTPS URLs except for loopback hosts, to + avoid tampering with fee data in transit. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/cmd/walletdk-wasm/AGENTS.md b/cmd/walletdk-wasm/AGENTS.md new file mode 100644 index 000000000..eb8dce793 --- /dev/null +++ b/cmd/walletdk-wasm/AGENTS.md @@ -0,0 +1,46 @@ +# cmd/walletdk-wasm + +## Purpose + +Command that compiles the embedded walletdk runtime to a browser WASM binary +and exposes it to JavaScript as a single `walletdkCall(method, req)` entry +point, so the daemon, swap, and OOR machinery run in-process in the browser +VM with no separate gateway. + +## Key Types + +- `main` (`js && wasm` build) — installs `walletdkCall` on the JS global and + parks the Go runtime so exported callbacks stay live for the page lifetime. +- `main` (stub, `!js || !wasm`) — keeps `go build ./...` green on native + toolchains; exits with a message that the real binary needs + `GOOS=js GOARCH=wasm -tags "mobile walletdkrpc swapruntime"`. +- `walletCall` — dispatches a JS method name to the matching + `sdk/walletdk/mobile` verb and returns a JS `Promise`. +- `subscriptionHandle` — wraps a pull-based `mobile.Subscription` as a JS + object with `next()`/`close()`. + +## Relationships + +- **Depends on**: `sdk/walletdk/mobile` (the single source of truth JSON + facade this bridge calls into; it never reaches into `walletdk.Client` + directly so it cannot drift from the gomobile bindings). +- **Depended on by**: nothing in-repo; this is a leaf binary target consumed + by a browser build pipeline outside this module. + +## Invariants + +- Every verb takes a JS request object and resolves/rejects a JS `Promise`; + never call back into JS synchronously from a goroutine without going + through `promise`, or a panic can escape and kill the Go runtime. +- `data_dir` must default to `browserDataDir` (`/darepo`) when unset, because + the embedded daemon's config validation calls `os.UserHomeDir` for the + default `~/.darepod`, which fails under `wasm_exec.js` (no `$HOME`). +- The `executor` `js.Func` passed to `Promise.New` must be released right + after construction (the executor runs synchronously), otherwise every + wallet call leaks a Go callback handle for the life of the page. +- Build tags must stay in sync between `main.go` (`js && wasm`) and + `stub.go` (`!js || !wasm`) so exactly one `main` compiles per target. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/cmd/walletdk-wasm/CLAUDE.md b/cmd/walletdk-wasm/CLAUDE.md new file mode 100644 index 000000000..eb8dce793 --- /dev/null +++ b/cmd/walletdk-wasm/CLAUDE.md @@ -0,0 +1,46 @@ +# cmd/walletdk-wasm + +## Purpose + +Command that compiles the embedded walletdk runtime to a browser WASM binary +and exposes it to JavaScript as a single `walletdkCall(method, req)` entry +point, so the daemon, swap, and OOR machinery run in-process in the browser +VM with no separate gateway. + +## Key Types + +- `main` (`js && wasm` build) — installs `walletdkCall` on the JS global and + parks the Go runtime so exported callbacks stay live for the page lifetime. +- `main` (stub, `!js || !wasm`) — keeps `go build ./...` green on native + toolchains; exits with a message that the real binary needs + `GOOS=js GOARCH=wasm -tags "mobile walletdkrpc swapruntime"`. +- `walletCall` — dispatches a JS method name to the matching + `sdk/walletdk/mobile` verb and returns a JS `Promise`. +- `subscriptionHandle` — wraps a pull-based `mobile.Subscription` as a JS + object with `next()`/`close()`. + +## Relationships + +- **Depends on**: `sdk/walletdk/mobile` (the single source of truth JSON + facade this bridge calls into; it never reaches into `walletdk.Client` + directly so it cannot drift from the gomobile bindings). +- **Depended on by**: nothing in-repo; this is a leaf binary target consumed + by a browser build pipeline outside this module. + +## Invariants + +- Every verb takes a JS request object and resolves/rejects a JS `Promise`; + never call back into JS synchronously from a goroutine without going + through `promise`, or a panic can escape and kill the Go runtime. +- `data_dir` must default to `browserDataDir` (`/darepo`) when unset, because + the embedded daemon's config validation calls `os.UserHomeDir` for the + default `~/.darepod`, which fails under `wasm_exec.js` (no `$HOME`). +- The `executor` `js.Func` passed to `Promise.New` must be released right + after construction (the executor runs synchronously), otherwise every + wallet call leaks a Go callback handle for the life of the page. +- Build tags must stay in sync between `main.go` (`js && wasm`) and + `stub.go` (`!js || !wasm`) so exactly one `main` compiles per target. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/coinselect/AGENTS.md b/coinselect/AGENTS.md new file mode 100644 index 000000000..ede27609c --- /dev/null +++ b/coinselect/AGENTS.md @@ -0,0 +1,41 @@ +# coinselect + +## Purpose + +A single, coin-type-agnostic coin-selection algorithm shared across the +client, so every layer that must pick a covering subset of coins (the VTXO +manager's reservation path, the swap wallet's send preview) selects through +the same code instead of growing parallel implementations. It holds no +wallet, actor, or RPC dependencies. + +## Key Types + +- `LargestFirst[T]` — largest-first selection: sorts candidates by descending + amount, accumulates until `Request.Target` is covered, honors `MinChange` + and `SweepAll`. +- `Request` — selection parameters: `Target`, `MinChange`, `SweepAll`. +- `Result[T]` — outcome: `Selected`, `Total`, `Change`. +- `AmountFunc[T]` — caller-supplied extractor of a candidate's satoshi value, + keeping the selector agnostic to the concrete coin type. +- `ErrSelectionShortfall` / `ErrChangeBelowMin` / `ErrNoCandidates` / + `ErrInvalidTarget` — typed selection failures; the selector stays + policy-free and leaves diagnostics to callers. + +## Relationships + +- **Depends on**: `btcutil` (`btcutil.Amount`) only; no internal repo + dependencies. +- **Depended on by**: `vtxo` (reservation/admission coin selection over VTXO + descriptors), `swapwallet` (send-preview routing over candidate coins). + +## Invariants + +- `LargestFirst` never mutates the caller's candidate slice; it sorts a copy. +- An exact-fit (zero-change) selection is always accepted regardless of + `MinChange`; a zero `MinChange` disables the dust-change check entirely. +- `SweepAll` takes precedence over `Target`/`MinChange` and selects every + candidate in input order (not sorted). + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/coinselect/CLAUDE.md b/coinselect/CLAUDE.md new file mode 100644 index 000000000..ede27609c --- /dev/null +++ b/coinselect/CLAUDE.md @@ -0,0 +1,41 @@ +# coinselect + +## Purpose + +A single, coin-type-agnostic coin-selection algorithm shared across the +client, so every layer that must pick a covering subset of coins (the VTXO +manager's reservation path, the swap wallet's send preview) selects through +the same code instead of growing parallel implementations. It holds no +wallet, actor, or RPC dependencies. + +## Key Types + +- `LargestFirst[T]` — largest-first selection: sorts candidates by descending + amount, accumulates until `Request.Target` is covered, honors `MinChange` + and `SweepAll`. +- `Request` — selection parameters: `Target`, `MinChange`, `SweepAll`. +- `Result[T]` — outcome: `Selected`, `Total`, `Change`. +- `AmountFunc[T]` — caller-supplied extractor of a candidate's satoshi value, + keeping the selector agnostic to the concrete coin type. +- `ErrSelectionShortfall` / `ErrChangeBelowMin` / `ErrNoCandidates` / + `ErrInvalidTarget` — typed selection failures; the selector stays + policy-free and leaves diagnostics to callers. + +## Relationships + +- **Depends on**: `btcutil` (`btcutil.Amount`) only; no internal repo + dependencies. +- **Depended on by**: `vtxo` (reservation/admission coin selection over VTXO + descriptors), `swapwallet` (send-preview routing over candidate coins). + +## Invariants + +- `LargestFirst` never mutates the caller's candidate slice; it sorts a copy. +- An exact-fit (zero-change) selection is always accepted regardless of + `MinChange`; a zero `MinChange` disables the dust-change check entirely. +- `SweepAll` takes precedence over `Target`/`MinChange` and selects every + candidate in input order (not sorted). + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/credit/AGENTS.md b/credit/AGENTS.md new file mode 100644 index 000000000..21244176b --- /dev/null +++ b/credit/AGENTS.md @@ -0,0 +1,62 @@ +# credit + +## Purpose + +Client-side credit subsystem: a supervisor/per-operation-actor pair that drives +sub-dust pays (with optional Ark top-up), server-owned Lightning receives, and +credit redemptions against the swap-server credit ledger, as a crash-safe +`protofsm` state machine per operation. + +## Key Types + +- `Registry` — non-durable supervisor actor; admits operations by writing the + control-plane row, spawns/reaps per-operation children, restores in-flight + ops on boot, arbitrates auto-redeem. +- `OpActor` — durable per-operation actor (`opBehavior` on the + Read/Stage/Commit path) that owns one operation's crash-safe FSM execution. +- `State` / `CreditState` (`protofsm.State[CreditEvent, CreditOutMsg, + *opBehavior]`) — the per-operation FSM state, persisted in + `credit_operations.state`. +- `CreditTransitions` (`CreditTransitionTable`) — static table documenting + every valid state transition, mirrored by hand alongside the live + `ProcessEvent` dispatch in transitions.go. +- `CreditServer` / `CreditDaemon` / `Store` — external surfaces: the + swap-server credit/pay RPCs, the wallet/daemon (OOR send, receive-script + allocation, VTXO lookup), and the durable control-plane store. +- `StartCreditPayRequest` / `StartCreditReceiveRequest` / `RedeemRequest` — + admission messages for the three operation kinds (`KindPay`, `KindReceive`, + `KindRedeem`). + +## Relationships + +- **Depends on**: `baselib/actor` (durable/plain actor framework, TLV + mailbox), `baselib/protofsm` (state/transition/emitted-event generics), + `db` (`CreditOperationRecord`/`CreditOpKind` control-plane schema), + `db/actordelivery`, `timeout` (poll-timer scheduling). +- **Depended on by**: `swapwallet` (credit-aware pay/receive routing, the + credit projector reading terminal ops), `swapclientserver` (bridges the + swap-server credit RPCs into `CreditServer`), `darepod` (registry wiring, + config, service startup). + +## Invariants + +- Every `CreditDurableMsg` (crossing a per-operation durable mailbox) must + satisfy `actor.TLVMessage`; `ResumeCreditOpRequest` is the only application + message that does, encoded via a local TLV type in the `0x71xx` range. +- A transition must flush a `stageRecord` checkpoint before the next state + runs a side effect that depends on a server identifier just recorded + (persist-before-effect); `runFSM` enforces this ordering via `ax.Stage`. +- `applyState`'s persisted state string must exactly match the `State` + constants in state.go; an unrecognized string durably fails the operation + (`failCorrupt`) rather than wedging it non-terminal forever. +- Every external call the behavior makes (`CreateCredit`, `SendOOR`, + `StartPay`, `RedeemCredit`) must stay idempotent by op key or payment hash, + since a redelivered message or a reload-after-`commitFailed` re-runs it. +- Auto-redeem is receive-triggered, not a periodic sweep (except a single + boot-time reconcile); `triggerRedeem` fires only after the settled receive's + terminal snapshot commits, so a crash before that leaves no half-applied + redeem. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/credit/CLAUDE.md b/credit/CLAUDE.md new file mode 100644 index 000000000..21244176b --- /dev/null +++ b/credit/CLAUDE.md @@ -0,0 +1,62 @@ +# credit + +## Purpose + +Client-side credit subsystem: a supervisor/per-operation-actor pair that drives +sub-dust pays (with optional Ark top-up), server-owned Lightning receives, and +credit redemptions against the swap-server credit ledger, as a crash-safe +`protofsm` state machine per operation. + +## Key Types + +- `Registry` — non-durable supervisor actor; admits operations by writing the + control-plane row, spawns/reaps per-operation children, restores in-flight + ops on boot, arbitrates auto-redeem. +- `OpActor` — durable per-operation actor (`opBehavior` on the + Read/Stage/Commit path) that owns one operation's crash-safe FSM execution. +- `State` / `CreditState` (`protofsm.State[CreditEvent, CreditOutMsg, + *opBehavior]`) — the per-operation FSM state, persisted in + `credit_operations.state`. +- `CreditTransitions` (`CreditTransitionTable`) — static table documenting + every valid state transition, mirrored by hand alongside the live + `ProcessEvent` dispatch in transitions.go. +- `CreditServer` / `CreditDaemon` / `Store` — external surfaces: the + swap-server credit/pay RPCs, the wallet/daemon (OOR send, receive-script + allocation, VTXO lookup), and the durable control-plane store. +- `StartCreditPayRequest` / `StartCreditReceiveRequest` / `RedeemRequest` — + admission messages for the three operation kinds (`KindPay`, `KindReceive`, + `KindRedeem`). + +## Relationships + +- **Depends on**: `baselib/actor` (durable/plain actor framework, TLV + mailbox), `baselib/protofsm` (state/transition/emitted-event generics), + `db` (`CreditOperationRecord`/`CreditOpKind` control-plane schema), + `db/actordelivery`, `timeout` (poll-timer scheduling). +- **Depended on by**: `swapwallet` (credit-aware pay/receive routing, the + credit projector reading terminal ops), `swapclientserver` (bridges the + swap-server credit RPCs into `CreditServer`), `darepod` (registry wiring, + config, service startup). + +## Invariants + +- Every `CreditDurableMsg` (crossing a per-operation durable mailbox) must + satisfy `actor.TLVMessage`; `ResumeCreditOpRequest` is the only application + message that does, encoded via a local TLV type in the `0x71xx` range. +- A transition must flush a `stageRecord` checkpoint before the next state + runs a side effect that depends on a server identifier just recorded + (persist-before-effect); `runFSM` enforces this ordering via `ax.Stage`. +- `applyState`'s persisted state string must exactly match the `State` + constants in state.go; an unrecognized string durably fails the operation + (`failCorrupt`) rather than wedging it non-terminal forever. +- Every external call the behavior makes (`CreateCredit`, `SendOOR`, + `StartPay`, `RedeemCredit`) must stay idempotent by op key or payment hash, + since a redelivered message or a reload-after-`commitFailed` re-runs it. +- Auto-redeem is receive-triggered, not a periodic sweep (except a single + boot-time reconcile); `triggerRedeem` fires only after the settled receive's + terminal snapshot commits, so a crash before that leaves no half-applied + redeem. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/db/actordelivery/sqlc/AGENTS.md b/db/actordelivery/sqlc/AGENTS.md new file mode 100644 index 000000000..35036803a --- /dev/null +++ b/db/actordelivery/sqlc/AGENTS.md @@ -0,0 +1,34 @@ +# db/actordelivery/sqlc + +## Purpose + +Generated type-safe query layer for the durable actor mailbox schema +(mailboxes, outbox, ask results, FSM checkpoints, dead letters). Generated +by `sqlc` from `db/actordelivery/queries/mailbox.sql`; regenerate with +`make sqlc`. Do not edit by hand. + +## Key Types + +- `Queries` / `Querier` — generated query struct and interface (enqueue, + lease, peek, ack/nack, extend, expire, outbox claim/complete/fail, + dedup, FSM checkpoints, dead letters). +- `MailboxMessage`, `OutboxMessage`, `AskResult`, `FsmCheckpoint`, + `DeadLetter`, `ProcessedMessage` — row models for the actor-delivery + tables. + +## Relationships + +- **Depends on**: none (leaf generated package; only `database/sql`). +- **Depended on by**: `db/actordelivery` (wraps `Queries`/`Querier` in + `ActorDeliveryQueries` / `BatchedActorDeliveryQueries`). + +## Invariants + +- Generated by `sqlc` — regenerate via `make sqlc`, never edit manually. +- Source of truth is `db/actordelivery/queries/mailbox.sql` and the + migrations in `db/actordelivery/migrations`. + +## Deep Docs + +- [db/actordelivery/CLAUDE.md](../CLAUDE.md) — Parent package overview. +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/db/actordelivery/sqlc/CLAUDE.md b/db/actordelivery/sqlc/CLAUDE.md new file mode 100644 index 000000000..35036803a --- /dev/null +++ b/db/actordelivery/sqlc/CLAUDE.md @@ -0,0 +1,34 @@ +# db/actordelivery/sqlc + +## Purpose + +Generated type-safe query layer for the durable actor mailbox schema +(mailboxes, outbox, ask results, FSM checkpoints, dead letters). Generated +by `sqlc` from `db/actordelivery/queries/mailbox.sql`; regenerate with +`make sqlc`. Do not edit by hand. + +## Key Types + +- `Queries` / `Querier` — generated query struct and interface (enqueue, + lease, peek, ack/nack, extend, expire, outbox claim/complete/fail, + dedup, FSM checkpoints, dead letters). +- `MailboxMessage`, `OutboxMessage`, `AskResult`, `FsmCheckpoint`, + `DeadLetter`, `ProcessedMessage` — row models for the actor-delivery + tables. + +## Relationships + +- **Depends on**: none (leaf generated package; only `database/sql`). +- **Depended on by**: `db/actordelivery` (wraps `Queries`/`Querier` in + `ActorDeliveryQueries` / `BatchedActorDeliveryQueries`). + +## Invariants + +- Generated by `sqlc` — regenerate via `make sqlc`, never edit manually. +- Source of truth is `db/actordelivery/queries/mailbox.sql` and the + migrations in `db/actordelivery/migrations`. + +## Deep Docs + +- [db/actordelivery/CLAUDE.md](../CLAUDE.md) — Parent package overview. +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/db/sqlc/AGENTS.md b/db/sqlc/AGENTS.md new file mode 100644 index 000000000..7dc81a2de --- /dev/null +++ b/db/sqlc/AGENTS.md @@ -0,0 +1,39 @@ +# db/sqlc + +## Purpose + +Generated type-safe query layer for the main client database schema: +chain info, internal keys, macaroons, boarding, rounds, VTXOs, OOR +artifacts/session registry, ledger accounting, UTXO audit, unilateral +exit, vHTLC recovery, pending intents, spending reservations, credit +operations, and the activity log. Generated by `sqlc` from +`db/sqlc/queries/*.sql`; regenerate with `make sqlc`. Do not edit by +hand, except `db_custom.go`. + +## Key Types + +- `Queries` / `Querier` — generated query struct and interface covering + all tables listed above. +- `BackendType` (`db_custom.go`, hand-maintained) — `Sqlite` / + `Postgres` tag stored on the wrapped `DBTX`; `NewSqlite` / `NewPostgres` + construct a `*Queries` carrying it, `(*Queries).Backend()` reads it back. + +## Relationships + +- **Depends on**: none (leaf generated package; only `database/sql`). +- **Depended on by**: `db` (wraps `Queries`/`Querier` in its domain + stores, e.g. `RoundStore`, `VTXOPersistenceStore`, `LedgerStoreDB`). + +## Invariants + +- Generated by `sqlc` — regenerate via `make sqlc`, never edit manually. + `db_custom.go` is the one hand-maintained exception (backend-type + wrapper) and survives regeneration. +- Source of truth is `db/sqlc/queries/*.sql` and the migrations in + `db/sqlc/migrations`. + +## Deep Docs + +- [db/CLAUDE.md](../CLAUDE.md) — Parent package overview and migration + baseline. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/db/sqlc/CLAUDE.md b/db/sqlc/CLAUDE.md new file mode 100644 index 000000000..7dc81a2de --- /dev/null +++ b/db/sqlc/CLAUDE.md @@ -0,0 +1,39 @@ +# db/sqlc + +## Purpose + +Generated type-safe query layer for the main client database schema: +chain info, internal keys, macaroons, boarding, rounds, VTXOs, OOR +artifacts/session registry, ledger accounting, UTXO audit, unilateral +exit, vHTLC recovery, pending intents, spending reservations, credit +operations, and the activity log. Generated by `sqlc` from +`db/sqlc/queries/*.sql`; regenerate with `make sqlc`. Do not edit by +hand, except `db_custom.go`. + +## Key Types + +- `Queries` / `Querier` — generated query struct and interface covering + all tables listed above. +- `BackendType` (`db_custom.go`, hand-maintained) — `Sqlite` / + `Postgres` tag stored on the wrapped `DBTX`; `NewSqlite` / `NewPostgres` + construct a `*Queries` carrying it, `(*Queries).Backend()` reads it back. + +## Relationships + +- **Depends on**: none (leaf generated package; only `database/sql`). +- **Depended on by**: `db` (wraps `Queries`/`Querier` in its domain + stores, e.g. `RoundStore`, `VTXOPersistenceStore`, `LedgerStoreDB`). + +## Invariants + +- Generated by `sqlc` — regenerate via `make sqlc`, never edit manually. + `db_custom.go` is the one hand-maintained exception (backend-type + wrapper) and survives regeneration. +- Source of truth is `db/sqlc/queries/*.sql` and the migrations in + `db/sqlc/migrations`. + +## Deep Docs + +- [db/CLAUDE.md](../CLAUDE.md) — Parent package overview and migration + baseline. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/internal/sqlbase/AGENTS.md b/internal/sqlbase/AGENTS.md new file mode 100644 index 000000000..4aa373ca3 --- /dev/null +++ b/internal/sqlbase/AGENTS.md @@ -0,0 +1,50 @@ +# internal/sqlbase + +## Purpose + +A `walletdb`-compatible key/value backend implemented over `database/sql`, +built only for `js && wasm` (every file carries that build tag). It emulates +`btcwallet/walletdb` buckets/cursors on top of a relational table so +`lwwallet` can run the wallet stack in the browser (SQLite via +`go-wasmsqlite`), where the native BoltDB/`kvdb` backends are unavailable. + +## Key Types + +- `db` (unexported) — implements `walletdb.DB`. Constructed via + `NewSqlBackend(ctx, cfg *Config)`. `View`/`Update` drive read/read-write + transactions with retry via a caller-supplied `reset` func. +- `Config` — driver name, DSN, timeout, schema, table-name prefix, per-backend + `SQLiteCmdReplacements`, and `WithTxLevelLock` (forces a single-writer + in-process lock). +- `readWriteTx` / `readWriteBucket` / `readWriteCursor` (unexported) — + transaction, bucket, and cursor implementations backing `walletdb.ReadTx` / + `walletdb.ReadWriteBucket` / `walletdb.ReadWriteCursor`; buckets and nested + buckets are simulated as rows in a single `_kv` table, with each + row's `parent_id` self-referencing the row of the bucket it belongs to + (`NULL` for the top-level bucket). +- `Init(maxConnections int)` — initializes the process-global connection + pool (`dbConnSet`) that dedups connections by DSN across callers. + +## Relationships + +- **Depends on**: `btcwallet/walletdb` (interface being implemented), + `lnd/sqldb` (shared SQL error classification). +- **Depended on by**: `lwwallet` (wasm builds only, via `internal/sqlbase`). + +## Invariants + +- Every file is `//go:build js && wasm`; this package does not build (and + cannot be exercised) on native `GOOS`/`GOARCH` — use + `GOOS=js GOARCH=wasm go build ./internal/sqlbase` or `go doc` to inspect it. +- `DefaultNumTxRetries = 50`: `Update`/`View` retry on transaction errors that + permit repetition, calling the caller's `reset` before each retry. +- `WithTxLevelLock` serializes all read-write transactions through a single + in-process lock; omit it only for backends that tolerate concurrent writers. +- Buckets/cursors are simulated over SQL tables, not a native KV store — key + ordering and cursor semantics must match `walletdb`'s contract exactly, or + callers built against `walletdb` (e.g. the wallet's key-derivation state) + silently misbehave. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/internal/sqlbase/CLAUDE.md b/internal/sqlbase/CLAUDE.md new file mode 100644 index 000000000..4aa373ca3 --- /dev/null +++ b/internal/sqlbase/CLAUDE.md @@ -0,0 +1,50 @@ +# internal/sqlbase + +## Purpose + +A `walletdb`-compatible key/value backend implemented over `database/sql`, +built only for `js && wasm` (every file carries that build tag). It emulates +`btcwallet/walletdb` buckets/cursors on top of a relational table so +`lwwallet` can run the wallet stack in the browser (SQLite via +`go-wasmsqlite`), where the native BoltDB/`kvdb` backends are unavailable. + +## Key Types + +- `db` (unexported) — implements `walletdb.DB`. Constructed via + `NewSqlBackend(ctx, cfg *Config)`. `View`/`Update` drive read/read-write + transactions with retry via a caller-supplied `reset` func. +- `Config` — driver name, DSN, timeout, schema, table-name prefix, per-backend + `SQLiteCmdReplacements`, and `WithTxLevelLock` (forces a single-writer + in-process lock). +- `readWriteTx` / `readWriteBucket` / `readWriteCursor` (unexported) — + transaction, bucket, and cursor implementations backing `walletdb.ReadTx` / + `walletdb.ReadWriteBucket` / `walletdb.ReadWriteCursor`; buckets and nested + buckets are simulated as rows in a single `_kv` table, with each + row's `parent_id` self-referencing the row of the bucket it belongs to + (`NULL` for the top-level bucket). +- `Init(maxConnections int)` — initializes the process-global connection + pool (`dbConnSet`) that dedups connections by DSN across callers. + +## Relationships + +- **Depends on**: `btcwallet/walletdb` (interface being implemented), + `lnd/sqldb` (shared SQL error classification). +- **Depended on by**: `lwwallet` (wasm builds only, via `internal/sqlbase`). + +## Invariants + +- Every file is `//go:build js && wasm`; this package does not build (and + cannot be exercised) on native `GOOS`/`GOARCH` — use + `GOOS=js GOARCH=wasm go build ./internal/sqlbase` or `go doc` to inspect it. +- `DefaultNumTxRetries = 50`: `Update`/`View` retry on transaction errors that + permit repetition, calling the caller's `reset` before each retry. +- `WithTxLevelLock` serializes all read-write transactions through a single + in-process lock; omit it only for backends that tolerate concurrent writers. +- Buckets/cursors are simulated over SQL tables, not a native KV store — key + ordering and cursor semantics must match `walletdb`'s contract exactly, or + callers built against `walletdb` (e.g. the wallet's key-derivation state) + silently misbehave. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/mailbox/pb/AGENTS.md b/mailbox/pb/AGENTS.md new file mode 100644 index 000000000..8ae23bb9e --- /dev/null +++ b/mailbox/pb/AGENTS.md @@ -0,0 +1,39 @@ +# mailbox/pb + +## Purpose + +Generated protobuf/gRPC/REST-gateway stubs for the `mailbox.v1.MailboxService` +wire format (`mailbox.proto`): `Envelope`, `RpcMeta`, and the +Send/Pull/AckUpTo RPCs. Generated via `make rpc` +(`scripts/gen_protos_docker.sh`); do not edit the `*.pb.go` files by hand. + +## Key Types + +- `Envelope` — wire message: `msg_id`, `idempotency_key`, `sender`, + `recipient`, `body`, `RpcMeta`, `event_seq`, headers. +- `RpcMeta` / `RpcMeta_Kind` — RPC overlay (`REQUEST`/`RESPONSE`/`EVENT`) and + correlation metadata. +- `MailboxServiceClient` / `MailboxServiceServer` — generated client/server + interfaces for `Send`, `Pull`, `AckUpTo`. +- `MailboxProtocolVersionV1` (`version.go`, hand-maintained, not generated) — + stable mailbox transport version constant; a breaking transport change gets + a new endpoint/proto package, not a bump of this constant. + +## Relationships + +- **Depends on**: none beyond `google.golang.org/protobuf` and + `grpc-gateway` runtime. +- **Depended on by**: `mailbox/conn`, `serverconn`, `darepod`. + +## Invariants + +- `*.pb.go`, `*_grpc.pb.go`, `*.pb.gw.go` are generated — regenerate via + `make rpc`, never edit manually. Only `version.go` is hand-maintained. +- New fields must be additive (proto field-number append-only) so older + envelopes decode cleanly under newer generated code; see + `version_compat_test.go` for the compatibility contract this protects. + +## Deep Docs + +- [mailbox/CLAUDE.md](../CLAUDE.md) — Parent package (pb/rpc/conn) overview. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/mailbox/pb/CLAUDE.md b/mailbox/pb/CLAUDE.md new file mode 100644 index 000000000..8ae23bb9e --- /dev/null +++ b/mailbox/pb/CLAUDE.md @@ -0,0 +1,39 @@ +# mailbox/pb + +## Purpose + +Generated protobuf/gRPC/REST-gateway stubs for the `mailbox.v1.MailboxService` +wire format (`mailbox.proto`): `Envelope`, `RpcMeta`, and the +Send/Pull/AckUpTo RPCs. Generated via `make rpc` +(`scripts/gen_protos_docker.sh`); do not edit the `*.pb.go` files by hand. + +## Key Types + +- `Envelope` — wire message: `msg_id`, `idempotency_key`, `sender`, + `recipient`, `body`, `RpcMeta`, `event_seq`, headers. +- `RpcMeta` / `RpcMeta_Kind` — RPC overlay (`REQUEST`/`RESPONSE`/`EVENT`) and + correlation metadata. +- `MailboxServiceClient` / `MailboxServiceServer` — generated client/server + interfaces for `Send`, `Pull`, `AckUpTo`. +- `MailboxProtocolVersionV1` (`version.go`, hand-maintained, not generated) — + stable mailbox transport version constant; a breaking transport change gets + a new endpoint/proto package, not a bump of this constant. + +## Relationships + +- **Depends on**: none beyond `google.golang.org/protobuf` and + `grpc-gateway` runtime. +- **Depended on by**: `mailbox/conn`, `serverconn`, `darepod`. + +## Invariants + +- `*.pb.go`, `*_grpc.pb.go`, `*.pb.gw.go` are generated — regenerate via + `make rpc`, never edit manually. Only `version.go` is hand-maintained. +- New fields must be additive (proto field-number append-only) so older + envelopes decode cleanly under newer generated code; see + `version_compat_test.go` for the compatibility contract this protects. + +## Deep Docs + +- [mailbox/CLAUDE.md](../CLAUDE.md) — Parent package (pb/rpc/conn) overview. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/rpc/oorpb/AGENTS.md b/rpc/oorpb/AGENTS.md new file mode 100644 index 000000000..9e55881ec --- /dev/null +++ b/rpc/oorpb/AGENTS.md @@ -0,0 +1,51 @@ +# rpc/oorpb + +## Purpose + +Generated gRPC/mailbox-RPC stubs for the OOR (out-of-round transfer) +client/server wire protocol (`oorwire.proto`), plus hand-written helpers that +convert between the proto wire types and domain types (`psbt.Packet`, +`chainhash.Hash`, `oortx.RecipientOutput`). + +## Key Types + +- `SubmitPackageRequest` / `SubmitPackageResponse` — wire request/response for + submitting an Ark package and checkpoint PSBTs for co-signing. +- `FinalizePackageRequest` / `FinalizePackageResponse` — wire request/response + for submitting finalized checkpoint PSBTs. +- `SigningDescriptor` — domain-side signing metadata (outpoint, VTXO policy + template, spend path, owner-leaf policy) for one checkpoint input; encoded + to/from `OORSigningDescriptor` on the wire. +- `FlowVersion` — permanent per-session OOR choreography version + (`FlowVersionV1` is the only value understood today); validated with + `ValidateFlowVersion`. +- `SubmitRejectedError` — typed error carrying `OORRejectCode` + reason, + returned by `ParseSubmitPackageResponse` on a rejection branch. +- `OORMailboxServiceMailboxClient` / `RegisterOORMailboxServiceMailboxServer` + — durable-mailbox transport bindings (`mailbox/rpc.Router`/`RPCClient`), + alongside the standard grpc client/server interfaces. + +## Relationships + +- **Depends on**: `lib/tx/oor` (RecipientOutput domain type), `lib/tx/psbtutil` + (PSBT serialize/parse), `mailbox/rpc` (durable mailbox transport used by the + generated `*Mailbox*` client/server). +- **Depended on by**: `oor` (session actor, outbox messages, errors — the OOR + client/server FSM), `db` (`oor_session_registry_store.go` persists + `FlowVersion`), `darepod` (server wiring), `systest` (end-to-end OOR tests). + +## Invariants + +- `FlowVersion` is zero-indexed so the Go zero value and an omitted wire field + both read as `FlowVersionV1`; never renumber existing version constants. +- `ValidateFlowVersion` must reject any version this build does not + understand — fail closed on unknown values arriving from a counterparty. +- `co_signed_ark_psbt` in `SubmitPackageSuccess` is additive: treat empty + bytes as "operator hasn't upgraded yet," not a parse error, to keep rolling + upgrades working. +- Regenerate wire stubs from `oorwire.proto` via `make rpc`; hand-edit only + `payloads.go` and `version.go`, never the generated `*.pb.go` files. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/rpc/oorpb/CLAUDE.md b/rpc/oorpb/CLAUDE.md new file mode 100644 index 000000000..9e55881ec --- /dev/null +++ b/rpc/oorpb/CLAUDE.md @@ -0,0 +1,51 @@ +# rpc/oorpb + +## Purpose + +Generated gRPC/mailbox-RPC stubs for the OOR (out-of-round transfer) +client/server wire protocol (`oorwire.proto`), plus hand-written helpers that +convert between the proto wire types and domain types (`psbt.Packet`, +`chainhash.Hash`, `oortx.RecipientOutput`). + +## Key Types + +- `SubmitPackageRequest` / `SubmitPackageResponse` — wire request/response for + submitting an Ark package and checkpoint PSBTs for co-signing. +- `FinalizePackageRequest` / `FinalizePackageResponse` — wire request/response + for submitting finalized checkpoint PSBTs. +- `SigningDescriptor` — domain-side signing metadata (outpoint, VTXO policy + template, spend path, owner-leaf policy) for one checkpoint input; encoded + to/from `OORSigningDescriptor` on the wire. +- `FlowVersion` — permanent per-session OOR choreography version + (`FlowVersionV1` is the only value understood today); validated with + `ValidateFlowVersion`. +- `SubmitRejectedError` — typed error carrying `OORRejectCode` + reason, + returned by `ParseSubmitPackageResponse` on a rejection branch. +- `OORMailboxServiceMailboxClient` / `RegisterOORMailboxServiceMailboxServer` + — durable-mailbox transport bindings (`mailbox/rpc.Router`/`RPCClient`), + alongside the standard grpc client/server interfaces. + +## Relationships + +- **Depends on**: `lib/tx/oor` (RecipientOutput domain type), `lib/tx/psbtutil` + (PSBT serialize/parse), `mailbox/rpc` (durable mailbox transport used by the + generated `*Mailbox*` client/server). +- **Depended on by**: `oor` (session actor, outbox messages, errors — the OOR + client/server FSM), `db` (`oor_session_registry_store.go` persists + `FlowVersion`), `darepod` (server wiring), `systest` (end-to-end OOR tests). + +## Invariants + +- `FlowVersion` is zero-indexed so the Go zero value and an omitted wire field + both read as `FlowVersionV1`; never renumber existing version constants. +- `ValidateFlowVersion` must reject any version this build does not + understand — fail closed on unknown values arriving from a counterparty. +- `co_signed_ark_psbt` in `SubmitPackageSuccess` is additive: treat empty + bytes as "operator hasn't upgraded yet," not a parse error, to keep rolling + upgrades working. +- Regenerate wire stubs from `oorwire.proto` via `make rpc`; hand-edit only + `payloads.go` and `version.go`, never the generated `*.pb.go` files. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/rpc/swapclientrpc/AGENTS.md b/rpc/swapclientrpc/AGENTS.md new file mode 100644 index 000000000..bf40a3b8e --- /dev/null +++ b/rpc/swapclientrpc/AGENTS.md @@ -0,0 +1,31 @@ +# rpc/swapclientrpc + +## Purpose + +Generated gRPC/REST/mailbox-RPC stubs for `SwapClientService`, the +daemon-owned Lightning/Ark swap execution API (quote/start pay, receive, +credit funding/redemption/listing). Registered only in swapruntime builds. + +## Key Types + +- `SwapClientServiceClient` / `SwapClientServiceServer` — standard gRPC + client/server interfaces. +- `SwapClientServiceMailboxServer` — durable-mailbox transport binding. +- Request/response messages (`QuotePayRequest`, `StartPayRequest`, + `CreateCreditRequest`, `ListCreditsRequest`, etc.) and enums + (`SwapState`, `SwapDirection`, `CreditOperationState`, ...). + +## Relationships + +- **Depended on by**: `swapclientserver` (implements the server), `swapwallet` + (constructs/normalizes RPC types), `cmd/darepocli` (CLI + MCP bindings), + `rpc/restclient`, `sdk/walletdk`. + +## Invariants + +- Generated from `swap_client.proto` via `make rpc`; do not hand-edit any + `.pb.go` / `.pb.gw.go` file. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/rpc/swapclientrpc/CLAUDE.md b/rpc/swapclientrpc/CLAUDE.md new file mode 100644 index 000000000..bf40a3b8e --- /dev/null +++ b/rpc/swapclientrpc/CLAUDE.md @@ -0,0 +1,31 @@ +# rpc/swapclientrpc + +## Purpose + +Generated gRPC/REST/mailbox-RPC stubs for `SwapClientService`, the +daemon-owned Lightning/Ark swap execution API (quote/start pay, receive, +credit funding/redemption/listing). Registered only in swapruntime builds. + +## Key Types + +- `SwapClientServiceClient` / `SwapClientServiceServer` — standard gRPC + client/server interfaces. +- `SwapClientServiceMailboxServer` — durable-mailbox transport binding. +- Request/response messages (`QuotePayRequest`, `StartPayRequest`, + `CreateCreditRequest`, `ListCreditsRequest`, etc.) and enums + (`SwapState`, `SwapDirection`, `CreditOperationState`, ...). + +## Relationships + +- **Depended on by**: `swapclientserver` (implements the server), `swapwallet` + (constructs/normalizes RPC types), `cmd/darepocli` (CLI + MCP bindings), + `rpc/restclient`, `sdk/walletdk`. + +## Invariants + +- Generated from `swap_client.proto` via `make rpc`; do not hand-edit any + `.pb.go` / `.pb.gw.go` file. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/rpcauth/AGENTS.md b/rpcauth/AGENTS.md new file mode 100644 index 000000000..cbd67aa37 --- /dev/null +++ b/rpcauth/AGENTS.md @@ -0,0 +1,38 @@ +# rpcauth + +## Purpose + +Shared macaroon and TLS helpers for securing gRPC/REST connections between +darepod, its CLI, and SDK clients: loading/serving macaroons and +generating/loading self-signed TLS cert/key pairs. + +## Key Types + +- `MacaroonMetadataKey` — gRPC metadata/HTTP header key carrying the + serialized macaroon. +- `DialOptionFromFile` / `HexFromFile` — build a macaroon `grpc.DialOption` + or hex-encode a macaroon file for client use. +- `EnsureTLSCert` — loads an existing TLS cert/key pair or generates a + self-signed one if neither exists. +- `ServerTLSCredentials` / `ClientTLSCredentials` / `HTTPClientForCert` — + build gRPC/HTTP transport credentials from a cert/key pair. + +## Relationships + +- **Depends on**: `github.com/lightningnetwork/lnd/macaroons` (macaroon + credentials), `github.com/lightningnetwork/lnd/cert` (self-signed cert + generation). +- **Depended on by**: `darepod` (server + gateway TLS/macaroon wiring), + `cmd/darepocli` (client auth), `sdk/walletdk` (SDK gRPC client connection). + +## Invariants + +- `EnsureTLSCert` errors on a partial keypair (only one of cert/key present) + rather than silently regenerating — a missing key file must never trigger + a fresh cert that invalidates a still-present key, or vice versa. +- TLS transport config always pins `MinVersion: tls.VersionTLS12`. +- Cert/key files are written `0o600` and their parent dirs `0o700`. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/rpcauth/CLAUDE.md b/rpcauth/CLAUDE.md new file mode 100644 index 000000000..cbd67aa37 --- /dev/null +++ b/rpcauth/CLAUDE.md @@ -0,0 +1,38 @@ +# rpcauth + +## Purpose + +Shared macaroon and TLS helpers for securing gRPC/REST connections between +darepod, its CLI, and SDK clients: loading/serving macaroons and +generating/loading self-signed TLS cert/key pairs. + +## Key Types + +- `MacaroonMetadataKey` — gRPC metadata/HTTP header key carrying the + serialized macaroon. +- `DialOptionFromFile` / `HexFromFile` — build a macaroon `grpc.DialOption` + or hex-encode a macaroon file for client use. +- `EnsureTLSCert` — loads an existing TLS cert/key pair or generates a + self-signed one if neither exists. +- `ServerTLSCredentials` / `ClientTLSCredentials` / `HTTPClientForCert` — + build gRPC/HTTP transport credentials from a cert/key pair. + +## Relationships + +- **Depends on**: `github.com/lightningnetwork/lnd/macaroons` (macaroon + credentials), `github.com/lightningnetwork/lnd/cert` (self-signed cert + generation). +- **Depended on by**: `darepod` (server + gateway TLS/macaroon wiring), + `cmd/darepocli` (client auth), `sdk/walletdk` (SDK gRPC client connection). + +## Invariants + +- `EnsureTLSCert` errors on a partial keypair (only one of cert/key present) + rather than silently regenerating — a missing key file must never trigger + a fresh cert that invalidates a still-present key, or vice versa. +- TLS transport config always pins `MinVersion: tls.VersionTLS12`. +- Cert/key files are written `0o600` and their parent dirs `0o700`. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/swaps/sqlc/AGENTS.md b/sdk/swaps/sqlc/AGENTS.md new file mode 100644 index 000000000..93eceee93 --- /dev/null +++ b/sdk/swaps/sqlc/AGENTS.md @@ -0,0 +1,31 @@ +# sdk/swaps/sqlc + +## Purpose + +Generated type-safe SQL query bindings (via sqlc) for the `sdk/swaps` pay/ +receive swap session store, backed by `sdk/swaps/queries/swaps.sql` and the +`sdk/swaps/migrations` schema. + +## Key Types + +- `Queries` — generated query struct built via `New(db DBTX)`. +- `Querier` — interface listing every generated query method. +- `PaySwap` / `ReceiveSwap` — row types for the pay/receive swap tables. +- `UpsertPaySwapParams` / `UpsertReceiveSwapParams` — params for the + upsert queries. + +## Relationships + +- **Depends on**: `database/sql` only (schema lives in + `sdk/swaps/migrations/`, queries in `sdk/swaps/queries/swaps.sql`). +- **Depended on by**: `sdk/swaps` (`store.go`, `store_sessions.go` wrap + `*Queries` as the durable pay/receive swap session store). + +## Invariants + +- Generated by `make sqlc` from `sdk/swaps/queries/swaps.sql` and the + migrations directory; do not hand-edit any file here. + +## Deep Docs + +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/swaps/sqlc/CLAUDE.md b/sdk/swaps/sqlc/CLAUDE.md new file mode 100644 index 000000000..93eceee93 --- /dev/null +++ b/sdk/swaps/sqlc/CLAUDE.md @@ -0,0 +1,31 @@ +# sdk/swaps/sqlc + +## Purpose + +Generated type-safe SQL query bindings (via sqlc) for the `sdk/swaps` pay/ +receive swap session store, backed by `sdk/swaps/queries/swaps.sql` and the +`sdk/swaps/migrations` schema. + +## Key Types + +- `Queries` — generated query struct built via `New(db DBTX)`. +- `Querier` — interface listing every generated query method. +- `PaySwap` / `ReceiveSwap` — row types for the pay/receive swap tables. +- `UpsertPaySwapParams` / `UpsertReceiveSwapParams` — params for the + upsert queries. + +## Relationships + +- **Depends on**: `database/sql` only (schema lives in + `sdk/swaps/migrations/`, queries in `sdk/swaps/queries/swaps.sql`). +- **Depended on by**: `sdk/swaps` (`store.go`, `store_sessions.go` wrap + `*Queries` as the durable pay/receive swap session store). + +## Invariants + +- Generated by `make sqlc` from `sdk/swaps/queries/swaps.sql` and the + migrations directory; do not hand-edit any file here. + +## Deep Docs + +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/walletdk/mobile/AGENTS.md b/sdk/walletdk/mobile/AGENTS.md new file mode 100644 index 000000000..b79b05239 --- /dev/null +++ b/sdk/walletdk/mobile/AGENTS.md @@ -0,0 +1,65 @@ +# sdk/walletdk/mobile + +## Purpose + +Gomobile-safe facade over `sdk/walletdk` for Android/iOS host apps. It flattens +the wallet SDK into free functions using only gomobile-legal types (no +`context.Context`, channels, maps, or non-`[]byte` slices), with JSON +bytes-in/bytes-out RPC verbs plus a few scalar convenience methods. Built only +via `gomobile bind` under the `mobile`, `walletdkrpc`, and `swapruntime` build +tags; without those tags this package has no exported API at all (there is no +committed stub source in this directory, unlike `cmd/walletdk-wasm/stub.go`). + +## Key Types + +- `Start(cfgJSON string) error` / `Stop() error` — singleton lifecycle for the + embedded daemon; a four-state machine (`stopped`/`starting`/`started`/ + `stopping`) plus a generation counter guards a `Stop` racing an in-progress + `Start`. +- `Subscription` — pull-based handle (`Next`/`Close`) over a wallet activity + stream, replacing the callback interfaces gomobile would otherwise require. +- `mobileConfig` — unexported flat JSON config decoded by `parseConfig`/ + `applyMobileConfig` into a `walletdk.Config`; validated (non-negative + durations/counts, `uint32`-safe recovery window) before merging onto + `walletdk.DefaultConfig()`. +- RPC verbs (`GetInfo`, `CreateWallet`, `UnlockWallet`, `Balance`, `Deposit`, + `Receive`, `PrepareSend`, `SendPrepared`, `List`, `Exit`, `ExitStatus`, + `ExitSummary`, `GetExitPlan`, `SweepWallet`, `Status`, `Subscribe`, + `OpenWalletFromPasskey`) — each dereferences the singleton `walletdk.Client` + via `activeClient()`, decodes a JSON request into the matching + `walletdk.*Request`, and marshals the `walletdk.*Result` response. +- Scalar conveniences (`ConfirmedBalanceSat`, `PendingInboundSat`, + `WalletReady`, `IsRunning`) — avoid a JSON round trip for hot-path reads; + `IsRunning` never blocks on an RPC. + +## Relationships + +- **Depends on**: `sdk/walletdk` (wraps `walletdk.Client`/`Config`/DTOs + directly; this package owns no wallet logic of its own). +- **Depended on by**: nothing in-tree; consumed externally as a `gomobile + bind` output (Android `.aar` / iOS `.xcframework`) built by + `gen_bindings.sh`. + +## Invariants + +- All package state lives in the unexported singleton `state`; a second + `Start` before `Stop` returns an error instead of booting a second daemon. +- `Stop` is idempotent and always resets the singleton so a subsequent `Start` + can succeed (e.g. after OS suspend/resume). +- Only `Start` (mobile.go:98) and `Subscription.Next` (wallet.go:365-371) + recover panics into a returned `error`; those are the two entry points + documented to survive a panic without crossing the gomobile boundary and + killing the host process. +- `Subscribe`'s updates/close path is driven by a context derived from the + wrapper-owned call context, not the caller's; `Stop` cancelling that context + is what unblocks an in-flight `Subscription.Next`. +- `mobileConfig` validation must reject negative durations/counts before they + reach `walletdk.Config`, since a negative `WalletPollIntervalSeconds` in + particular panics the lwwallet tip poller's ticker in a background + goroutine after startup. + +## Deep Docs + +- [sdk/walletdk/CLAUDE.md](../CLAUDE.md) — Wrapped SDK; see for full DTO and + RPC method detail. +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/sdk/walletdk/mobile/CLAUDE.md b/sdk/walletdk/mobile/CLAUDE.md new file mode 100644 index 000000000..b79b05239 --- /dev/null +++ b/sdk/walletdk/mobile/CLAUDE.md @@ -0,0 +1,65 @@ +# sdk/walletdk/mobile + +## Purpose + +Gomobile-safe facade over `sdk/walletdk` for Android/iOS host apps. It flattens +the wallet SDK into free functions using only gomobile-legal types (no +`context.Context`, channels, maps, or non-`[]byte` slices), with JSON +bytes-in/bytes-out RPC verbs plus a few scalar convenience methods. Built only +via `gomobile bind` under the `mobile`, `walletdkrpc`, and `swapruntime` build +tags; without those tags this package has no exported API at all (there is no +committed stub source in this directory, unlike `cmd/walletdk-wasm/stub.go`). + +## Key Types + +- `Start(cfgJSON string) error` / `Stop() error` — singleton lifecycle for the + embedded daemon; a four-state machine (`stopped`/`starting`/`started`/ + `stopping`) plus a generation counter guards a `Stop` racing an in-progress + `Start`. +- `Subscription` — pull-based handle (`Next`/`Close`) over a wallet activity + stream, replacing the callback interfaces gomobile would otherwise require. +- `mobileConfig` — unexported flat JSON config decoded by `parseConfig`/ + `applyMobileConfig` into a `walletdk.Config`; validated (non-negative + durations/counts, `uint32`-safe recovery window) before merging onto + `walletdk.DefaultConfig()`. +- RPC verbs (`GetInfo`, `CreateWallet`, `UnlockWallet`, `Balance`, `Deposit`, + `Receive`, `PrepareSend`, `SendPrepared`, `List`, `Exit`, `ExitStatus`, + `ExitSummary`, `GetExitPlan`, `SweepWallet`, `Status`, `Subscribe`, + `OpenWalletFromPasskey`) — each dereferences the singleton `walletdk.Client` + via `activeClient()`, decodes a JSON request into the matching + `walletdk.*Request`, and marshals the `walletdk.*Result` response. +- Scalar conveniences (`ConfirmedBalanceSat`, `PendingInboundSat`, + `WalletReady`, `IsRunning`) — avoid a JSON round trip for hot-path reads; + `IsRunning` never blocks on an RPC. + +## Relationships + +- **Depends on**: `sdk/walletdk` (wraps `walletdk.Client`/`Config`/DTOs + directly; this package owns no wallet logic of its own). +- **Depended on by**: nothing in-tree; consumed externally as a `gomobile + bind` output (Android `.aar` / iOS `.xcframework`) built by + `gen_bindings.sh`. + +## Invariants + +- All package state lives in the unexported singleton `state`; a second + `Start` before `Stop` returns an error instead of booting a second daemon. +- `Stop` is idempotent and always resets the singleton so a subsequent `Start` + can succeed (e.g. after OS suspend/resume). +- Only `Start` (mobile.go:98) and `Subscription.Next` (wallet.go:365-371) + recover panics into a returned `error`; those are the two entry points + documented to survive a panic without crossing the gomobile boundary and + killing the host process. +- `Subscribe`'s updates/close path is driven by a context derived from the + wrapper-owned call context, not the caller's; `Stop` cancelling that context + is what unblocks an in-flight `Subscription.Next`. +- `mobileConfig` validation must reject negative durations/counts before they + reach `walletdk.Config`, since a negative `WalletPollIntervalSeconds` in + particular panics the lwwallet tip poller's ticker in a background + goroutine after startup. + +## Deep Docs + +- [sdk/walletdk/CLAUDE.md](../CLAUDE.md) — Wrapped SDK; see for full DTO and + RPC method detail. +- [ARCHITECTURE.md](../../../ARCHITECTURE.md) — System-wide package map. diff --git a/serverconn/hellotestpb/AGENTS.md b/serverconn/hellotestpb/AGENTS.md new file mode 100644 index 000000000..4692b6098 --- /dev/null +++ b/serverconn/hellotestpb/AGENTS.md @@ -0,0 +1,47 @@ +# serverconn/hellotestpb + +## Purpose + +Generated protobuf/mailbox-RPC stubs for `HelloService`, a test-only fixture +service used to exercise `serverconn`'s mailbox unary-RPC facade and event +router in `serverconn`'s own tests (`e2e_test.go`, `event_router_test.go`). +Proto source: `serverconn/testdata/hello.proto`. Not part of the production +API surface. + +## Key Types + +- `HelloServiceMailboxClient` — typed mailbox RPC client wrapping a + `mailbox/rpc.RPCClient`; exposes `SayHello`/`SayGoodbye` as ordinary Go + methods over `KIND_REQUEST`/`KIND_RESPONSE` envelope pairs. +- `HelloRequest`/`HelloResponse`, `GoodbyeRequest`/`GoodbyeResponse` — unary + request/response messages for the two RPCs. +- `JoinGreetingRequest` — client-to-server fire-and-forget `KIND_EVENT` + message (no response expected). +- `HelloStartedEvent` / `HelloFinalizedEvent` — server-to-client push + notifications dispatched through `serverconn`'s `EventRouter` keyed on + `"hellotest.v1.HelloService"` + method name (`HelloStarted`/ + `HelloFinalized`). + +## Relationships + +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types consumed by the + generated mailbox client). +- **Depended on by**: `serverconn` tests only (`e2e_test.go`, + `event_router_test.go`); no non-test package imports this. + +## Invariants + +- **Never edit generated code** — both files are generated (`protoc-gen-go` + for `hello.pb.go`, `protoc-gen-mailboxrpc` for `hello_mailboxrpc.pb.go`); + edit `serverconn/testdata/hello.proto` instead. No make target or script + covers this fixture; regenerate via a manual `protoc` invocation with + `protoc-gen-go` and `protoc-gen-mailboxrpc`. +- This package exists solely to give `serverconn` tests a concrete service to + drive; do not wire it into any production RPC surface. + +## Deep Docs + +- [docs/mailbox_architecture.md](../../docs/mailbox_architecture.md) — + Mailbox RPC architecture; documents the `protoc-gen-mailboxrpc` output + shape using `HelloService` as the worked example. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/serverconn/hellotestpb/CLAUDE.md b/serverconn/hellotestpb/CLAUDE.md new file mode 100644 index 000000000..4692b6098 --- /dev/null +++ b/serverconn/hellotestpb/CLAUDE.md @@ -0,0 +1,47 @@ +# serverconn/hellotestpb + +## Purpose + +Generated protobuf/mailbox-RPC stubs for `HelloService`, a test-only fixture +service used to exercise `serverconn`'s mailbox unary-RPC facade and event +router in `serverconn`'s own tests (`e2e_test.go`, `event_router_test.go`). +Proto source: `serverconn/testdata/hello.proto`. Not part of the production +API surface. + +## Key Types + +- `HelloServiceMailboxClient` — typed mailbox RPC client wrapping a + `mailbox/rpc.RPCClient`; exposes `SayHello`/`SayGoodbye` as ordinary Go + methods over `KIND_REQUEST`/`KIND_RESPONSE` envelope pairs. +- `HelloRequest`/`HelloResponse`, `GoodbyeRequest`/`GoodbyeResponse` — unary + request/response messages for the two RPCs. +- `JoinGreetingRequest` — client-to-server fire-and-forget `KIND_EVENT` + message (no response expected). +- `HelloStartedEvent` / `HelloFinalizedEvent` — server-to-client push + notifications dispatched through `serverconn`'s `EventRouter` keyed on + `"hellotest.v1.HelloService"` + method name (`HelloStarted`/ + `HelloFinalized`). + +## Relationships + +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types consumed by the + generated mailbox client). +- **Depended on by**: `serverconn` tests only (`e2e_test.go`, + `event_router_test.go`); no non-test package imports this. + +## Invariants + +- **Never edit generated code** — both files are generated (`protoc-gen-go` + for `hello.pb.go`, `protoc-gen-mailboxrpc` for `hello_mailboxrpc.pb.go`); + edit `serverconn/testdata/hello.proto` instead. No make target or script + covers this fixture; regenerate via a manual `protoc` invocation with + `protoc-gen-go` and `protoc-gen-mailboxrpc`. +- This package exists solely to give `serverconn` tests a concrete service to + drive; do not wire it into any production RPC surface. + +## Deep Docs + +- [docs/mailbox_architecture.md](../../docs/mailbox_architecture.md) — + Mailbox RPC architecture; documents the `protoc-gen-mailboxrpc` output + shape using `HelloService` as the worked example. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/swaprpc/AGENTS.md b/swaprpc/AGENTS.md new file mode 100644 index 000000000..d5a206d86 --- /dev/null +++ b/swaprpc/AGENTS.md @@ -0,0 +1,58 @@ +# swaprpc + +## Purpose + +Generated gRPC/REST/mailbox-RPC stubs for `SwapService`, the external swap +server API consumed by the client SDK: Lightning<->Ark swaps (in-swap/ +out-swap), channel-ID allocation for Lightning-to-Ark receives, and durable +credit funding/redemption/listing. Proto source: `swaprpc/swap.proto` +(REST-gateway rules in `swap.yaml`). Fully generated; no hand-written Go +files in this package. + +## Key Types + +- `SwapServiceClient` / `SwapServiceServer` — standard gRPC client/server + interfaces. +- `SwapServiceMailboxClient` / `SwapServiceMailboxServer` — durable-mailbox + transport bindings (`mailbox/rpc.RPCClient`/`Router`) for the same RPCs. +- `SettlementType` — which path backs a swap: `LIGHTNING`, `IN_ARK`, + `CREDIT`, or `MIXED` (funded by both a vHTLC and reserved credit). +- `SwapMailboxEvent` — oneof wrapper for server-pushed mailbox events + (`OutSwapHtlcEvent`, and others as added) delivered outside the + request/response RPCs. +- `CreditOperationState` / `CreditOperationType` — externally visible FSM + states/kinds for durable credit funding, pay, redemption, and receive + operations. +- Request/response messages per RPC (`CreateInSwapRequest/Response`, + `QuoteInSwapRequest/Response`, `CreateCreditRequest/Response`, + `RedeemCreditRequest/Response`, `ListCreditsRequest/Response`, + `AuthorizeInSwapRefundRequest/Response`, + `AcknowledgeOutSwapHtlcRequest/Response`, + `SignInSwapForfeitRequest/Response`, + `SubmitOutSwapForfeitSignatureRequest/Response`, + `RequestChannelIdRequest/Response`). + +## Relationships + +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the + generated mailbox client/server), `google.golang.org/grpc`, + `grpc-ecosystem/grpc-gateway/v2` (REST gateway in `swap.pb.gw.go`). +- **Depended on by**: `sdk/swaps` (`grpc_conn.go`, `out_swap_mailbox.go` — + typed clients for the swap FSM), `rpc/restclient` (REST transport + adapter). `swapclientserver` implements `rpc/swapclientrpc`, not this + package — only its tests import `swaprpc`. + +## Invariants + +- **Never edit generated code** (`swap.pb.go`, `swap_grpc.pb.go`, + `swap.pb.gw.go`, `swap_mailboxrpc.pb.go`) — regenerate via `make rpc` after + editing `swap.proto` or `swap.yaml`. +- `SettlementType.SETTLEMENT_TYPE_UNSPECIFIED` (0) is treated as Lightning + for backward compatibility with older server responses; do not repurpose + the zero value. +- `SwapMailboxEvent` is a proto oneof: read the populated variant, don't + assume `OutSwapHtlcEvent` is the only case as new event kinds are added. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/swaprpc/CLAUDE.md b/swaprpc/CLAUDE.md new file mode 100644 index 000000000..d5a206d86 --- /dev/null +++ b/swaprpc/CLAUDE.md @@ -0,0 +1,58 @@ +# swaprpc + +## Purpose + +Generated gRPC/REST/mailbox-RPC stubs for `SwapService`, the external swap +server API consumed by the client SDK: Lightning<->Ark swaps (in-swap/ +out-swap), channel-ID allocation for Lightning-to-Ark receives, and durable +credit funding/redemption/listing. Proto source: `swaprpc/swap.proto` +(REST-gateway rules in `swap.yaml`). Fully generated; no hand-written Go +files in this package. + +## Key Types + +- `SwapServiceClient` / `SwapServiceServer` — standard gRPC client/server + interfaces. +- `SwapServiceMailboxClient` / `SwapServiceMailboxServer` — durable-mailbox + transport bindings (`mailbox/rpc.RPCClient`/`Router`) for the same RPCs. +- `SettlementType` — which path backs a swap: `LIGHTNING`, `IN_ARK`, + `CREDIT`, or `MIXED` (funded by both a vHTLC and reserved credit). +- `SwapMailboxEvent` — oneof wrapper for server-pushed mailbox events + (`OutSwapHtlcEvent`, and others as added) delivered outside the + request/response RPCs. +- `CreditOperationState` / `CreditOperationType` — externally visible FSM + states/kinds for durable credit funding, pay, redemption, and receive + operations. +- Request/response messages per RPC (`CreateInSwapRequest/Response`, + `QuoteInSwapRequest/Response`, `CreateCreditRequest/Response`, + `RedeemCreditRequest/Response`, `ListCreditsRequest/Response`, + `AuthorizeInSwapRefundRequest/Response`, + `AcknowledgeOutSwapHtlcRequest/Response`, + `SignInSwapForfeitRequest/Response`, + `SubmitOutSwapForfeitSignatureRequest/Response`, + `RequestChannelIdRequest/Response`). + +## Relationships + +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the + generated mailbox client/server), `google.golang.org/grpc`, + `grpc-ecosystem/grpc-gateway/v2` (REST gateway in `swap.pb.gw.go`). +- **Depended on by**: `sdk/swaps` (`grpc_conn.go`, `out_swap_mailbox.go` — + typed clients for the swap FSM), `rpc/restclient` (REST transport + adapter). `swapclientserver` implements `rpc/swapclientrpc`, not this + package — only its tests import `swaprpc`. + +## Invariants + +- **Never edit generated code** (`swap.pb.go`, `swap_grpc.pb.go`, + `swap.pb.gw.go`, `swap_mailboxrpc.pb.go`) — regenerate via `make rpc` after + editing `swap.proto` or `swap.yaml`. +- `SettlementType.SETTLEMENT_TYPE_UNSPECIFIED` (0) is treated as Lightning + for backward compatibility with older server responses; do not repurpose + the zero value. +- `SwapMailboxEvent` is a proto oneof: read the populated variant, don't + assume `OutSwapHtlcEvent` is the only case as new event kinds are added. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. From c4599aff7905890cffc1bff942ba8ea65d9a16ba Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 9 Jul 2026 15:38:41 -0700 Subject: [PATCH 3/4] docs: re-audit existing per-package agent docs against source Sweep every existing per-package CLAUDE.md/AGENTS.md against current source: correct drifted types, message directions, and dependency edges; restore load-bearing invariants that an over-eager tightening had cut (notably the oor session deadlock, dedup, and lineage-bound rules); and strip leaked tool-output artifacts from a prior automated sweep. --- baselib/AGENTS.md | 9 +- baselib/CLAUDE.md | 9 +- baselib/actor/AGENTS.md | 4 +- baselib/actor/CLAUDE.md | 4 +- baselib/example/AGENTS.md | 6 +- baselib/example/CLAUDE.md | 6 +- baselib/protofsm/AGENTS.md | 26 +- baselib/protofsm/CLAUDE.md | 26 +- chain/AGENTS.md | 23 +- chain/CLAUDE.md | 23 +- chainbackends/AGENTS.md | 22 +- chainbackends/CLAUDE.md | 22 +- chainbackends/bitcoindrpc/AGENTS.md | 22 +- chainbackends/bitcoindrpc/CLAUDE.md | 22 +- chainsource/AGENTS.md | 57 +- chainsource/CLAUDE.md | 57 +- cmd/AGENTS.md | 9 +- cmd/CLAUDE.md | 9 +- cmd/darepocli/AGENTS.md | 16 +- cmd/darepocli/CLAUDE.md | 16 +- cmd/darepocli/darepoclicommands/AGENTS.md | 57 +- cmd/darepocli/darepoclicommands/CLAUDE.md | 57 +- cmd/darepocli/internal/gen-devrpc/AGENTS.md | 23 +- cmd/darepocli/internal/gen-devrpc/CLAUDE.md | 23 +- cmd/darepod/AGENTS.md | 42 +- cmd/darepod/CLAUDE.md | 42 +- .../internal/gen/AGENTS.md | 4 +- .../internal/gen/CLAUDE.md | 4 +- daemonrpc/AGENTS.md | 36 +- daemonrpc/CLAUDE.md | 36 +- darepod/AGENTS.md | 413 +++------------ darepod/CLAUDE.md | 413 +++------------ db/AGENTS.md | 10 +- db/CLAUDE.md | 10 +- db/actordelivery/AGENTS.md | 4 + db/actordelivery/CLAUDE.md | 4 + db/actordelivery/migrations/AGENTS.md | 14 +- db/actordelivery/migrations/CLAUDE.md | 14 +- db/migrate/AGENTS.md | 20 +- db/migrate/CLAUDE.md | 20 +- gateway/AGENTS.md | 17 +- gateway/CLAUDE.md | 17 +- harness/AGENTS.md | 78 +-- harness/CLAUDE.md | 78 +-- indexer/AGENTS.md | 12 +- indexer/CLAUDE.md | 12 +- internal/AGENTS.md | 8 +- internal/CLAUDE.md | 8 +- internal/actortest/AGENTS.md | 17 +- internal/actortest/CLAUDE.md | 17 +- internal/indexerlimits/AGENTS.md | 4 +- internal/indexerlimits/CLAUDE.md | 4 +- internal/testutils/AGENTS.md | 28 +- internal/testutils/CLAUDE.md | 28 +- ledger/AGENTS.md | 2 - ledger/CLAUDE.md | 2 - lib/AGENTS.md | 13 +- lib/CLAUDE.md | 13 +- lib/arkscript/AGENTS.md | 8 +- lib/arkscript/CLAUDE.md | 8 +- lib/bip322/AGENTS.md | 4 +- lib/bip322/CLAUDE.md | 4 +- lib/recovery/AGENTS.md | 11 +- lib/recovery/CLAUDE.md | 11 +- lib/scripts/AGENTS.md | 2 - lib/scripts/CLAUDE.md | 2 - lib/tree/AGENTS.md | 9 +- lib/tree/CLAUDE.md | 9 +- lib/tx/AGENTS.md | 5 +- lib/tx/CLAUDE.md | 5 +- lib/tx/arktx/AGENTS.md | 13 +- lib/tx/arktx/CLAUDE.md | 13 +- lib/tx/checkpoint/AGENTS.md | 3 +- lib/tx/checkpoint/CLAUDE.md | 3 +- lib/tx/oor/AGENTS.md | 23 +- lib/tx/oor/CLAUDE.md | 23 +- lib/tx/psbtutil/AGENTS.md | 5 +- lib/tx/psbtutil/CLAUDE.md | 5 +- lib/types/AGENTS.md | 37 +- lib/types/CLAUDE.md | 37 +- lndbackend/AGENTS.md | 49 +- lndbackend/CLAUDE.md | 49 +- lwwallet/AGENTS.md | 35 +- lwwallet/CLAUDE.md | 35 +- mailbox/AGENTS.md | 9 +- mailbox/CLAUDE.md | 9 +- mailbox/conn/AGENTS.md | 8 + mailbox/conn/CLAUDE.md | 8 + mailbox/rpc/AGENTS.md | 7 + mailbox/rpc/CLAUDE.md | 7 + metrics/AGENTS.md | 110 ++-- metrics/CLAUDE.md | 110 ++-- oor/AGENTS.md | 496 ++++-------------- oor/CLAUDE.md | 496 ++++-------------- p-models/AGENTS.md | 61 ++- p-models/CLAUDE.md | 61 ++- p-models/durableactor/AGENTS.md | 107 ++-- p-models/durableactor/CLAUDE.md | 107 ++-- round/AGENTS.md | 11 +- round/CLAUDE.md | 11 +- rpc/AGENTS.md | 8 +- rpc/CLAUDE.md | 8 +- rpc/roundpb/AGENTS.md | 34 +- rpc/roundpb/CLAUDE.md | 34 +- rpc/walletdkrpc/AGENTS.md | 50 +- rpc/walletdkrpc/CLAUDE.md | 50 +- scripts/AGENTS.md | 14 +- scripts/CLAUDE.md | 14 +- scripts/check-sample-darepod-conf/AGENTS.md | 44 +- scripts/check-sample-darepod-conf/CLAUDE.md | 44 +- scripts/verify-schema-registry/AGENTS.md | 26 +- scripts/verify-schema-registry/CLAUDE.md | 26 +- sdk/ark/AGENTS.md | 103 ++-- sdk/ark/CLAUDE.md | 103 ++-- sdk/swaps/AGENTS.md | 54 +- sdk/swaps/CLAUDE.md | 54 +- sdk/walletdk/AGENTS.md | 59 ++- sdk/walletdk/CLAUDE.md | 37 +- serverconn/AGENTS.md | 28 +- serverconn/CLAUDE.md | 25 +- swapclientserver/AGENTS.md | 52 +- swapclientserver/CLAUDE.md | 52 +- swapwallet/AGENTS.md | 94 +++- swapwallet/CLAUDE.md | 66 ++- systest/AGENTS.md | 45 +- systest/CLAUDE.md | 45 +- timeout/AGENTS.md | 115 ++-- timeout/CLAUDE.md | 115 ++-- tools/AGENTS.md | 38 +- tools/CLAUDE.md | 38 +- txconfirm/AGENTS.md | 19 +- txconfirm/CLAUDE.md | 19 +- unroll/AGENTS.md | 22 +- unroll/CLAUDE.md | 22 +- vhtlcrecovery/AGENTS.md | 2 - vhtlcrecovery/CLAUDE.md | 2 - vhtlcrecovery/coordinator/AGENTS.md | 17 +- vhtlcrecovery/coordinator/CLAUDE.md | 17 +- vhtlcrecovery/unrollpolicy/AGENTS.md | 2 - vhtlcrecovery/unrollpolicy/CLAUDE.md | 2 - vtxo/AGENTS.md | 46 +- vtxo/CLAUDE.md | 46 +- wallet/AGENTS.md | 70 ++- wallet/CLAUDE.md | 70 ++- walletcore/AGENTS.md | 13 +- walletcore/CLAUDE.md | 7 +- 146 files changed, 2990 insertions(+), 2879 deletions(-) diff --git a/baselib/AGENTS.md b/baselib/AGENTS.md index aa7b5d0da..78c2fa2b8 100644 --- a/baselib/AGENTS.md +++ b/baselib/AGENTS.md @@ -22,9 +22,16 @@ build on. - `StateTransition[InternalEvent, OutboxEvent, Env]` — Next state + emitted events from a transition. - `EmittedEvent[InternalEvent, OutboxEvent]` — Internal events (recursive) + outbox events (external). +### baselib/example +- Runnable reference wiring a `protofsm` state machine to `actor` services; + not used by production code. See `baselib/example/CLAUDE.md`. + ## Relationships -- **Depends on**: nothing (pure abstraction layer). +- **Depends on**: `lnd/tlv`, `lnd/fn/v2`, `lnd/clock` (external, no + darepo-client-specific logic); `darepo-client/build` (context-scoped logger + helper only) is the one root-module import, otherwise this is a pure + abstraction layer. - **Depended on by**: every domain package (`round`, `vtxo`, `oor`, `wallet`), `chainsource`, `serverconn`, `db`, `darepod`. ## Invariants diff --git a/baselib/CLAUDE.md b/baselib/CLAUDE.md index aa7b5d0da..78c2fa2b8 100644 --- a/baselib/CLAUDE.md +++ b/baselib/CLAUDE.md @@ -22,9 +22,16 @@ build on. - `StateTransition[InternalEvent, OutboxEvent, Env]` — Next state + emitted events from a transition. - `EmittedEvent[InternalEvent, OutboxEvent]` — Internal events (recursive) + outbox events (external). +### baselib/example +- Runnable reference wiring a `protofsm` state machine to `actor` services; + not used by production code. See `baselib/example/CLAUDE.md`. + ## Relationships -- **Depends on**: nothing (pure abstraction layer). +- **Depends on**: `lnd/tlv`, `lnd/fn/v2`, `lnd/clock` (external, no + darepo-client-specific logic); `darepo-client/build` (context-scoped logger + helper only) is the one root-module import, otherwise this is a pure + abstraction layer. - **Depended on by**: every domain package (`round`, `vtxo`, `oor`, `wallet`), `chainsource`, `serverconn`, `db`, `darepod`. ## Invariants diff --git a/baselib/actor/AGENTS.md b/baselib/actor/AGENTS.md index d80da8897..93de43ec8 100644 --- a/baselib/actor/AGENTS.md +++ b/baselib/actor/AGENTS.md @@ -75,8 +75,8 @@ crash-safe at-least-once delivery with exactly-once deduplication. ## Relationships -- **Depends on**: `lnd/tlv` (message serialization). -- **Depended on by**: All domain actors (`round`, `vtxo`, `oor`, `wallet`, `serverconn`, `timeout`, `indexer`), `baselib/protofsm` (FSM-to-actor bridge), `db/actordelivery` (persistence implementation). +- **Depends on**: `lnd/tlv` (message serialization), `lnd/fn/v2` (Result/Option/Either types), `lnd/clock` (testable time), `build` (logger-from-context helper). +- **Depended on by**: All domain actors (`round`, `vtxo`, `oor`, `wallet`, `serverconn`, `timeout`), `baselib/protofsm` (FSM-to-actor bridge), `db/actordelivery` (persistence implementation). ## Invariants diff --git a/baselib/actor/CLAUDE.md b/baselib/actor/CLAUDE.md index d80da8897..93de43ec8 100644 --- a/baselib/actor/CLAUDE.md +++ b/baselib/actor/CLAUDE.md @@ -75,8 +75,8 @@ crash-safe at-least-once delivery with exactly-once deduplication. ## Relationships -- **Depends on**: `lnd/tlv` (message serialization). -- **Depended on by**: All domain actors (`round`, `vtxo`, `oor`, `wallet`, `serverconn`, `timeout`, `indexer`), `baselib/protofsm` (FSM-to-actor bridge), `db/actordelivery` (persistence implementation). +- **Depends on**: `lnd/tlv` (message serialization), `lnd/fn/v2` (Result/Option/Either types), `lnd/clock` (testable time), `build` (logger-from-context helper). +- **Depended on by**: All domain actors (`round`, `vtxo`, `oor`, `wallet`, `serverconn`, `timeout`), `baselib/protofsm` (FSM-to-actor bridge), `db/actordelivery` (persistence implementation). ## Invariants diff --git a/baselib/example/AGENTS.md b/baselib/example/AGENTS.md index 36e043525..bedd1dc3f 100644 --- a/baselib/example/AGENTS.md +++ b/baselib/example/AGENTS.md @@ -18,9 +18,9 @@ reference and learning aid; not used by production code. `StateApproved`, `StateRejected`. - `DocEnvironment` — Immutable FSM context holding an actor reference; implements `protofsm.TellRefEnv[DocEvent]`. -- `ReviewService` / `ReviewServiceBehavior` — Actor that performs async review - and sends the result event back to the FSM. -- `NotificationService` / `NotificationServiceBehavior` — Actor that delivers +- `ReviewServiceBehavior` — `ActorBehavior` that performs async document + review and sends the result event back to the FSM. +- `NotificationServiceBehavior` — `ActorBehavior` that delivers approval/rejection notifications. ## Relationships diff --git a/baselib/example/CLAUDE.md b/baselib/example/CLAUDE.md index 36e043525..bedd1dc3f 100644 --- a/baselib/example/CLAUDE.md +++ b/baselib/example/CLAUDE.md @@ -18,9 +18,9 @@ reference and learning aid; not used by production code. `StateApproved`, `StateRejected`. - `DocEnvironment` — Immutable FSM context holding an actor reference; implements `protofsm.TellRefEnv[DocEvent]`. -- `ReviewService` / `ReviewServiceBehavior` — Actor that performs async review - and sends the result event back to the FSM. -- `NotificationService` / `NotificationServiceBehavior` — Actor that delivers +- `ReviewServiceBehavior` — `ActorBehavior` that performs async document + review and sends the result event back to the FSM. +- `NotificationServiceBehavior` — `ActorBehavior` that delivers approval/rejection notifications. ## Relationships diff --git a/baselib/protofsm/AGENTS.md b/baselib/protofsm/AGENTS.md index 40050976f..34f07651a 100644 --- a/baselib/protofsm/AGENTS.md +++ b/baselib/protofsm/AGENTS.md @@ -4,17 +4,21 @@ Protocol-style finite state machine engine that separates pure state transitions from side effects. Business logic lives in `(State, Event) → (State, []OutboxEvent)` -transition functions; the runtime dispatches outbox messages after state is -durably persisted. +transition functions; the runtime commits the new state, then dispatches the +transition's outbox events. ## Key Types -- `State[E, O, Env]` — Interface for FSM states: `ProcessEvent` returns the next state and outbox events. +- `State[E, O, Env]` — Interface for FSM states: `ProcessEvent` returns a + `StateTransition`, iterated until a terminal state or no further internal + events are emitted. - `StateMachine[E, O, Env]` — Non-actor FSM runner (for testing or embedded use). - `StateMachineCfg[E, O, Env]` — Configuration for state machines (initial state, environment, transition table). - `ActorStateMachine[E, O, Env]` — FSM wrapped as an actor behavior for use with `baselib/actor`. -- `EmittedEvent[E, O]` — Pair of (next state, outbox events) returned by transitions. -- `StateTransition[E, O, Env]` — Single transition result (new state + emitted events). +- `EmittedEvent[E, O]` — Internal events (recursive, routed back into the + FSM) plus outbox events (dispatched externally), emitted by a transition. +- `StateTransition[E, O, Env]` — Single transition result: next state plus an + optional `EmittedEvent`. - `TransitionTable[S, E, M]` — Declarative transition table mapping (State, Event) → handler. - `TransitionEntry[S, E, M]` — Single entry in a transition table. - `RoutedOutboxEvent[M, R]` — Outbox event that targets a specific actor via `ServiceKey` (Tell or Ask delivery). @@ -31,12 +35,18 @@ durably persisted. ## Invariants - Transition functions must be pure: no I/O, no network calls, no database writes. All side effects are expressed as outbox events. -- Outbox events are dispatched only after the new state is durably persisted (prevents message-before-state bugs). -- `TransitionTable` enforces exhaustive (State, Event) coverage at compile time via type constraints. +- `ActorStateMachine.Receive` commits `currentState` in memory before + dispatching any outbox events for that turn (prevents dispatching a side + effect for a transition the FSM hasn't "moved into" yet). Protofsm itself + has no persistence layer; durability, if any, comes from the surrounding + `DurableActor`/`baselib/actor` wiring, not from this package. +- `TransitionTable` is a declarative, introspectable description of valid + (State, Event) transitions used for documentation/rendering + (`RenderMarkdown`) and test validation; it is not compiler-enforced and + does not by itself guarantee exhaustive coverage. - `RoutedOutboxEvent` captures the target `ServiceKey` so the runtime can dispatch to the correct actor without the FSM knowing about actor references. ## Deep Docs - [baselib/CLAUDE.md](../CLAUDE.md) — Parent baselib package overview. -- [docs/durable_actor_architecture.md](../../docs/durable_actor_architecture.md) — Durable actor internals. - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/baselib/protofsm/CLAUDE.md b/baselib/protofsm/CLAUDE.md index 40050976f..34f07651a 100644 --- a/baselib/protofsm/CLAUDE.md +++ b/baselib/protofsm/CLAUDE.md @@ -4,17 +4,21 @@ Protocol-style finite state machine engine that separates pure state transitions from side effects. Business logic lives in `(State, Event) → (State, []OutboxEvent)` -transition functions; the runtime dispatches outbox messages after state is -durably persisted. +transition functions; the runtime commits the new state, then dispatches the +transition's outbox events. ## Key Types -- `State[E, O, Env]` — Interface for FSM states: `ProcessEvent` returns the next state and outbox events. +- `State[E, O, Env]` — Interface for FSM states: `ProcessEvent` returns a + `StateTransition`, iterated until a terminal state or no further internal + events are emitted. - `StateMachine[E, O, Env]` — Non-actor FSM runner (for testing or embedded use). - `StateMachineCfg[E, O, Env]` — Configuration for state machines (initial state, environment, transition table). - `ActorStateMachine[E, O, Env]` — FSM wrapped as an actor behavior for use with `baselib/actor`. -- `EmittedEvent[E, O]` — Pair of (next state, outbox events) returned by transitions. -- `StateTransition[E, O, Env]` — Single transition result (new state + emitted events). +- `EmittedEvent[E, O]` — Internal events (recursive, routed back into the + FSM) plus outbox events (dispatched externally), emitted by a transition. +- `StateTransition[E, O, Env]` — Single transition result: next state plus an + optional `EmittedEvent`. - `TransitionTable[S, E, M]` — Declarative transition table mapping (State, Event) → handler. - `TransitionEntry[S, E, M]` — Single entry in a transition table. - `RoutedOutboxEvent[M, R]` — Outbox event that targets a specific actor via `ServiceKey` (Tell or Ask delivery). @@ -31,12 +35,18 @@ durably persisted. ## Invariants - Transition functions must be pure: no I/O, no network calls, no database writes. All side effects are expressed as outbox events. -- Outbox events are dispatched only after the new state is durably persisted (prevents message-before-state bugs). -- `TransitionTable` enforces exhaustive (State, Event) coverage at compile time via type constraints. +- `ActorStateMachine.Receive` commits `currentState` in memory before + dispatching any outbox events for that turn (prevents dispatching a side + effect for a transition the FSM hasn't "moved into" yet). Protofsm itself + has no persistence layer; durability, if any, comes from the surrounding + `DurableActor`/`baselib/actor` wiring, not from this package. +- `TransitionTable` is a declarative, introspectable description of valid + (State, Event) transitions used for documentation/rendering + (`RenderMarkdown`) and test validation; it is not compiler-enforced and + does not by itself guarantee exhaustive coverage. - `RoutedOutboxEvent` captures the target `ServiceKey` so the runtime can dispatch to the correct actor without the FSM knowing about actor references. ## Deep Docs - [baselib/CLAUDE.md](../CLAUDE.md) — Parent baselib package overview. -- [docs/durable_actor_architecture.md](../../docs/durable_actor_architecture.md) — Durable actor internals. - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/chain/AGENTS.md b/chain/AGENTS.md index 7e47eeb91..57c47a3d0 100644 --- a/chain/AGENTS.md +++ b/chain/AGENTS.md @@ -6,7 +6,26 @@ Bitcoind/Bitcoin Core RPC utilities, including `BitcoindRPCClient` wrapping btcd rpcclient with extended methods like `SubmitPackage` for v3 transaction package relay. +## Key Types + +- `BitcoindRPCClient` — Wraps an `rpcclient.Client` and adds `SubmitPackage`, + a v3 parents+child package-relay call not yet exposed by the standard btcd + RPC client. + ## Relationships -- **Depends on**: nothing (low-level RPC wrapper). -- **Depended on by**: `harness` (test environment), `chainbackends` (chain integration). +- **Depends on**: `btcd/rpcclient`, `btcd/btcjson`, `btcd/wire` (RPC plumbing + only; no other repo packages). +- **Depended on by**: `harness` (test environment's `BitcoindClient` helper). + +## Invariants + +- `SubmitPackage` requires at least one parent transaction and a non-nil + child; it errors immediately otherwise rather than forwarding a malformed + request to bitcoind. +- `maxFeeRateBTCPerVByte` is converted to BTC/kvB before being sent, matching + bitcoind's `submitpackage` RPC parameter units. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/chain/CLAUDE.md b/chain/CLAUDE.md index 7e47eeb91..57c47a3d0 100644 --- a/chain/CLAUDE.md +++ b/chain/CLAUDE.md @@ -6,7 +6,26 @@ Bitcoind/Bitcoin Core RPC utilities, including `BitcoindRPCClient` wrapping btcd rpcclient with extended methods like `SubmitPackage` for v3 transaction package relay. +## Key Types + +- `BitcoindRPCClient` — Wraps an `rpcclient.Client` and adds `SubmitPackage`, + a v3 parents+child package-relay call not yet exposed by the standard btcd + RPC client. + ## Relationships -- **Depends on**: nothing (low-level RPC wrapper). -- **Depended on by**: `harness` (test environment), `chainbackends` (chain integration). +- **Depends on**: `btcd/rpcclient`, `btcd/btcjson`, `btcd/wire` (RPC plumbing + only; no other repo packages). +- **Depended on by**: `harness` (test environment's `BitcoindClient` helper). + +## Invariants + +- `SubmitPackage` requires at least one parent transaction and a non-nil + child; it errors immediately otherwise rather than forwarding a malformed + request to bitcoind. +- `maxFeeRateBTCPerVByte` is converted to BTC/kvB before being sent, matching + bitcoind's `submitpackage` RPC parameter units. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/chainbackends/AGENTS.md b/chainbackends/AGENTS.md index 3ea8f2727..83e03e94a 100644 --- a/chainbackends/AGENTS.md +++ b/chainbackends/AGENTS.md @@ -46,22 +46,24 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. ## Relationships -- **Depends on**: `chainsource` (implements `ChainBackend` interface). -- **Depended on by**: `darepod` (instantiates backend and wires a - `PackageSubmitter` from operator config: production uses - `chainbackends/bitcoindrpc.PackageSubmitter` directly, itests inject the - same submitter from the harness). +- **Depends on**: `chainsource` (implements `ChainBackend` interface), + `chainfees` (fee estimator types). +- **Depended on by**: `darepod` (instantiates `LNDBackend` and wires a + `PackageSubmitter` from operator config), `systest` (constructs + `LNDBackend` via lndclient for system tests), `btcwbackend` / `lwwallet` / + `txconfirm` (reuse `PackageSubmitter`, `PackageTxError`, and + `WalkPackageTxErrors` to classify per-tx package-relay results). ## Invariants - `LNDBackend` requires an lnd instance (local or remote via lndclient). - Provides real-time notifications via lnd's chainntnfs package. - `PackageSubmitter` is optional; package-capable backends return an error - from `SubmitPackage` when no submitter is set. In production `cmd/darepod` - injects - `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are - configured; the itest harness injects the same type via - `darepod.Config.PackageSubmitter`. + from `SubmitPackage` when no submitter is set. `darepod` selects one at + startup: an explicit `darepod.Config.PackageSubmitter` wins (bitcoind flags + inject `chainbackends/bitcoindrpc.PackageSubmitter`, and the itest harness + sets the same field); otherwise, for an LND wallet it falls back to + `chainbackends/lndsubmitter.New(lndSvc.WalletKit)` as the default. - `LndClientChainNotifier` enforces a 15-second timeout on registration to prevent hanging under LND block load. - Log messages use canonical txid strings (not reversed byte slices). diff --git a/chainbackends/CLAUDE.md b/chainbackends/CLAUDE.md index 3ea8f2727..83e03e94a 100644 --- a/chainbackends/CLAUDE.md +++ b/chainbackends/CLAUDE.md @@ -46,22 +46,24 @@ estimation, and optional v3 package relay via a pluggable `PackageSubmitter`. ## Relationships -- **Depends on**: `chainsource` (implements `ChainBackend` interface). -- **Depended on by**: `darepod` (instantiates backend and wires a - `PackageSubmitter` from operator config: production uses - `chainbackends/bitcoindrpc.PackageSubmitter` directly, itests inject the - same submitter from the harness). +- **Depends on**: `chainsource` (implements `ChainBackend` interface), + `chainfees` (fee estimator types). +- **Depended on by**: `darepod` (instantiates `LNDBackend` and wires a + `PackageSubmitter` from operator config), `systest` (constructs + `LNDBackend` via lndclient for system tests), `btcwbackend` / `lwwallet` / + `txconfirm` (reuse `PackageSubmitter`, `PackageTxError`, and + `WalkPackageTxErrors` to classify per-tx package-relay results). ## Invariants - `LNDBackend` requires an lnd instance (local or remote via lndclient). - Provides real-time notifications via lnd's chainntnfs package. - `PackageSubmitter` is optional; package-capable backends return an error - from `SubmitPackage` when no submitter is set. In production `cmd/darepod` - injects - `chainbackends/bitcoindrpc.PackageSubmitter` when bitcoind flags are - configured; the itest harness injects the same type via - `darepod.Config.PackageSubmitter`. + from `SubmitPackage` when no submitter is set. `darepod` selects one at + startup: an explicit `darepod.Config.PackageSubmitter` wins (bitcoind flags + inject `chainbackends/bitcoindrpc.PackageSubmitter`, and the itest harness + sets the same field); otherwise, for an LND wallet it falls back to + `chainbackends/lndsubmitter.New(lndSvc.WalletKit)` as the default. - `LndClientChainNotifier` enforces a 15-second timeout on registration to prevent hanging under LND block load. - Log messages use canonical txid strings (not reversed byte slices). diff --git a/chainbackends/bitcoindrpc/AGENTS.md b/chainbackends/bitcoindrpc/AGENTS.md index e64afa04e..51afc1eea 100644 --- a/chainbackends/bitcoindrpc/AGENTS.md +++ b/chainbackends/bitcoindrpc/AGENTS.md @@ -14,17 +14,23 @@ bitcoind node instead of through LND's `WalletKit`. Sibling to `lnd.go` in POSTs a `submitpackage` JSON-RPC call to bitcoind. Uses a dedicated `*http.Client` with a 30s backstop timeout so a wedged node can't stall the caller for the full parent context. -- `New(host, user, password)` — Constructs a `PackageSubmitter`. `host` is - the `host:port` form; the submitter prefixes `http://` because bitcoind's - JSON-RPC server speaks plain HTTP by default. TLS termination (when - present) is expected to be handled by an external reverse proxy. +- `New(host, user, password)` — Legacy no-error constructor for the bare + `host:port` form; always defaults to `http://`. +- `NewWithOptions(host, user, password, opts...)` — Preferred constructor; + surfaces URL-parse and TLS-config errors, and defaults to `https://` when + `WithTLSCertPath` is set. +- `WithTLSCertPath(path)` — Option to trust a custom CA when bitcoind's RPC + is fronted by a local TLS reverse proxy; augments (not replaces) the + system trust store. ## Relationships -- **Depends on**: `btcd/btcjson` (SubmitPackageResult), `btcd/wire` (MsgTx), - standard library `net/http`. -- **Depended on by**: `cmd/darepod` (wires via `bitcoind.{host,user,pass}` - flags), `harness` (itest injection into `darepod.Config.PackageSubmitter`). +- **Depends on**: `btcd/btcjson` (SubmitPackageResult), `btcd/wire` + (MsgTx), standard library `net/http`, `crypto/tls`. +- **Depended on by**: `cmd/darepod` (wires via `bitcoindrpc.NewWithOptions` + with the `bitcoind.{host,user,pass,rpccookie,tlscertpath}` config keys + into `darepod.Config.PackageSubmitter`, implementing + `chainbackends.PackageSubmitter`). ## Invariants diff --git a/chainbackends/bitcoindrpc/CLAUDE.md b/chainbackends/bitcoindrpc/CLAUDE.md index e64afa04e..51afc1eea 100644 --- a/chainbackends/bitcoindrpc/CLAUDE.md +++ b/chainbackends/bitcoindrpc/CLAUDE.md @@ -14,17 +14,23 @@ bitcoind node instead of through LND's `WalletKit`. Sibling to `lnd.go` in POSTs a `submitpackage` JSON-RPC call to bitcoind. Uses a dedicated `*http.Client` with a 30s backstop timeout so a wedged node can't stall the caller for the full parent context. -- `New(host, user, password)` — Constructs a `PackageSubmitter`. `host` is - the `host:port` form; the submitter prefixes `http://` because bitcoind's - JSON-RPC server speaks plain HTTP by default. TLS termination (when - present) is expected to be handled by an external reverse proxy. +- `New(host, user, password)` — Legacy no-error constructor for the bare + `host:port` form; always defaults to `http://`. +- `NewWithOptions(host, user, password, opts...)` — Preferred constructor; + surfaces URL-parse and TLS-config errors, and defaults to `https://` when + `WithTLSCertPath` is set. +- `WithTLSCertPath(path)` — Option to trust a custom CA when bitcoind's RPC + is fronted by a local TLS reverse proxy; augments (not replaces) the + system trust store. ## Relationships -- **Depends on**: `btcd/btcjson` (SubmitPackageResult), `btcd/wire` (MsgTx), - standard library `net/http`. -- **Depended on by**: `cmd/darepod` (wires via `bitcoind.{host,user,pass}` - flags), `harness` (itest injection into `darepod.Config.PackageSubmitter`). +- **Depends on**: `btcd/btcjson` (SubmitPackageResult), `btcd/wire` + (MsgTx), standard library `net/http`, `crypto/tls`. +- **Depended on by**: `cmd/darepod` (wires via `bitcoindrpc.NewWithOptions` + with the `bitcoind.{host,user,pass,rpccookie,tlscertpath}` config keys + into `darepod.Config.PackageSubmitter`, implementing + `chainbackends.PackageSubmitter`). ## Invariants diff --git a/chainsource/AGENTS.md b/chainsource/AGENTS.md index bb2cd94a1..7e0b38dfa 100644 --- a/chainsource/AGENTS.md +++ b/chainsource/AGENTS.md @@ -10,46 +10,35 @@ communication alongside the raw registration API. ## Key Types - `ChainBackend` — Interface: `EstimateFee`, `BestBlock`, `BroadcastTx`, - `TestMempoolAccept`, `RegisterConf/Spend/Blocks`, `SubmitPackage`, `Start/Stop`. -- `ChainSourceActor` — Factory actor spawning sub-actors for each monitoring - request. Registered under `ChainSourceKey`. -- `ChainSourceConfig` — Config struct: `Backend ChainBackend`, `System - *actor.ActorSystem`, `Log fn.Option[btclog.Logger]`. -- `ChainSourceMsg` / `ChainSourceResp` — Sealed actor message interfaces for - requests and responses sent to the `ChainSourceActor`. -- `FeeEstimateRequest/Response`, `BestHeightRequest/Response`, - `BroadcastTxRequest/Response`, `TestMempoolAcceptRequest/Response`, - `SubmitPackageRequest/Response` — Request/response pairs implementing - `ChainSourceMsg`/`ChainSourceResp`. -- `ConfMsg` / `ConfResp` — Sealed interfaces for confirmation sub-actor messages. -- `RegisterConfRequest/Response`, `UnregisterConfRequest/Response` — Request - types for conf-actor lifecycle. `RegisterConfRequest` carries an optional - `NotifyActor fn.Option[actor.TellOnlyRef[ConfirmationEvent]]` for async-mode - notification without blocking on a Future. -- `SpendMsg` / `SpendResp` — Sealed interfaces for spend sub-actor messages. -- `RegisterSpendRequest/Response`, `UnregisterSpendRequest/Response` — Spend - monitoring lifecycle. -- `EpochMsg` / `EpochResp` — Sealed interfaces for block-epoch sub-actor. -- `SubscribeBlocksRequest/Response`, `UnsubscribeBlocksRequest/Response` — - Block subscription lifecycle. -- `ConfRegistration` / `SpendRegistration` / `BlockRegistration` — Structs with - buffered notification channels and a `Cancel()` function. -- `ConfirmationEvent`, `SpendEvent`, `BlockEpoch` — Notification payload types. -- `MapBlockEpoch`, `MapConfirmationEvent`, `MapSpendEvent` — Generic helpers - that wrap a target `TellOnlyRef[Out]` and a mapping function, producing a - `TellOnlyRef` of the source event type for actor-to-actor notification wiring. + `TestMempoolAccept`, `RegisterConf/Spend/Blocks`, `SubmitPackage`, + `Start/Stop`. Implemented by `chainbackends`, `btcwbackend`, `lwwallet`. +- `ChainSourceActor` — Actor registered under `ChainSourceKey`; dispatches + each RegisterConf/RegisterSpend/SubscribeBlocks request to a dedicated + sub-actor (`ConfActor`, `SpendActor`, `BlockEpochActor`). +- `ChainSourceMsg`/`ChainSourceResp`, `ConfMsg`/`ConfResp`, + `SpendMsg`/`SpendResp`, `EpochMsg`/`EpochResp` — Sealed request/response + interfaces for the top-level actor and its three sub-actor kinds + (e.g. `RegisterConfRequest`, `SubscribeBlocksRequest`, + `SubmitPackageRequest`, each with a matching `...Response`). +- `ConfirmationEvent`, `SpendEvent`, `BlockEpoch` — Notification payloads, + delivered via a buffered channel on the returned `*Registration` or, when + `NotifyActor` is set on the request, an actor `Tell`. +- `MapConfirmationEvent`/`MapSpendEvent`/`MapBlockEpoch` — Generic adapters + wrapping a target `TellOnlyRef[Out]` so callers can subscribe with their + own event type instead of the chainsource one. +- `IsIgnorableBroadcastError`, `IsIgnorableMempoolRejectReason` — Classify + "already known/confirmed" rebroadcast errors/reject reasons as non-fatal. ## Relationships -- **Depends on**: `baselib/actor` (ActorSystem, ActorBehavior, ServiceKey). -- **Depended on by**: `round`, `vtxo`, `wallet` (monitoring), `chainbackends` - (implements `ChainBackend`), `btcwbackend`, `lwwallet` (also implement - `ChainBackend`), `darepod` (wiring). +- **Depends on**: `baselib/actor` (ActorSystem, ActorBehavior, ServiceKey), + `build` (logger-from-context helper). +- **Depended on by**: `round`, `vtxo`, `wallet`, `fraud`, `txconfirm`, + `unroll` (monitoring), `chainbackends`, `btcwbackend`, `lwwallet` (each + implements `ChainBackend`), `darepod` (wiring). ## Invariants -- `ChainBackend` is an interface; implementations live in `chainbackends`, - `btcwbackend`, and `lwwallet`. - Each monitoring request spawns a dedicated sub-actor (no shared state between monitors). - Registration channels are buffered. diff --git a/chainsource/CLAUDE.md b/chainsource/CLAUDE.md index bb2cd94a1..7e0b38dfa 100644 --- a/chainsource/CLAUDE.md +++ b/chainsource/CLAUDE.md @@ -10,46 +10,35 @@ communication alongside the raw registration API. ## Key Types - `ChainBackend` — Interface: `EstimateFee`, `BestBlock`, `BroadcastTx`, - `TestMempoolAccept`, `RegisterConf/Spend/Blocks`, `SubmitPackage`, `Start/Stop`. -- `ChainSourceActor` — Factory actor spawning sub-actors for each monitoring - request. Registered under `ChainSourceKey`. -- `ChainSourceConfig` — Config struct: `Backend ChainBackend`, `System - *actor.ActorSystem`, `Log fn.Option[btclog.Logger]`. -- `ChainSourceMsg` / `ChainSourceResp` — Sealed actor message interfaces for - requests and responses sent to the `ChainSourceActor`. -- `FeeEstimateRequest/Response`, `BestHeightRequest/Response`, - `BroadcastTxRequest/Response`, `TestMempoolAcceptRequest/Response`, - `SubmitPackageRequest/Response` — Request/response pairs implementing - `ChainSourceMsg`/`ChainSourceResp`. -- `ConfMsg` / `ConfResp` — Sealed interfaces for confirmation sub-actor messages. -- `RegisterConfRequest/Response`, `UnregisterConfRequest/Response` — Request - types for conf-actor lifecycle. `RegisterConfRequest` carries an optional - `NotifyActor fn.Option[actor.TellOnlyRef[ConfirmationEvent]]` for async-mode - notification without blocking on a Future. -- `SpendMsg` / `SpendResp` — Sealed interfaces for spend sub-actor messages. -- `RegisterSpendRequest/Response`, `UnregisterSpendRequest/Response` — Spend - monitoring lifecycle. -- `EpochMsg` / `EpochResp` — Sealed interfaces for block-epoch sub-actor. -- `SubscribeBlocksRequest/Response`, `UnsubscribeBlocksRequest/Response` — - Block subscription lifecycle. -- `ConfRegistration` / `SpendRegistration` / `BlockRegistration` — Structs with - buffered notification channels and a `Cancel()` function. -- `ConfirmationEvent`, `SpendEvent`, `BlockEpoch` — Notification payload types. -- `MapBlockEpoch`, `MapConfirmationEvent`, `MapSpendEvent` — Generic helpers - that wrap a target `TellOnlyRef[Out]` and a mapping function, producing a - `TellOnlyRef` of the source event type for actor-to-actor notification wiring. + `TestMempoolAccept`, `RegisterConf/Spend/Blocks`, `SubmitPackage`, + `Start/Stop`. Implemented by `chainbackends`, `btcwbackend`, `lwwallet`. +- `ChainSourceActor` — Actor registered under `ChainSourceKey`; dispatches + each RegisterConf/RegisterSpend/SubscribeBlocks request to a dedicated + sub-actor (`ConfActor`, `SpendActor`, `BlockEpochActor`). +- `ChainSourceMsg`/`ChainSourceResp`, `ConfMsg`/`ConfResp`, + `SpendMsg`/`SpendResp`, `EpochMsg`/`EpochResp` — Sealed request/response + interfaces for the top-level actor and its three sub-actor kinds + (e.g. `RegisterConfRequest`, `SubscribeBlocksRequest`, + `SubmitPackageRequest`, each with a matching `...Response`). +- `ConfirmationEvent`, `SpendEvent`, `BlockEpoch` — Notification payloads, + delivered via a buffered channel on the returned `*Registration` or, when + `NotifyActor` is set on the request, an actor `Tell`. +- `MapConfirmationEvent`/`MapSpendEvent`/`MapBlockEpoch` — Generic adapters + wrapping a target `TellOnlyRef[Out]` so callers can subscribe with their + own event type instead of the chainsource one. +- `IsIgnorableBroadcastError`, `IsIgnorableMempoolRejectReason` — Classify + "already known/confirmed" rebroadcast errors/reject reasons as non-fatal. ## Relationships -- **Depends on**: `baselib/actor` (ActorSystem, ActorBehavior, ServiceKey). -- **Depended on by**: `round`, `vtxo`, `wallet` (monitoring), `chainbackends` - (implements `ChainBackend`), `btcwbackend`, `lwwallet` (also implement - `ChainBackend`), `darepod` (wiring). +- **Depends on**: `baselib/actor` (ActorSystem, ActorBehavior, ServiceKey), + `build` (logger-from-context helper). +- **Depended on by**: `round`, `vtxo`, `wallet`, `fraud`, `txconfirm`, + `unroll` (monitoring), `chainbackends`, `btcwbackend`, `lwwallet` (each + implements `ChainBackend`), `darepod` (wiring). ## Invariants -- `ChainBackend` is an interface; implementations live in `chainbackends`, - `btcwbackend`, and `lwwallet`. - Each monitoring request spawns a dedicated sub-actor (no shared state between monitors). - Registration channels are buffered. diff --git a/cmd/AGENTS.md b/cmd/AGENTS.md index 1b212b976..2ffe383f1 100644 --- a/cmd/AGENTS.md +++ b/cmd/AGENTS.md @@ -2,11 +2,16 @@ ## Purpose -Entry points for the daemon (`cmd/darepod`) and CLI client (`cmd/darepocli`). +Entry points for the daemon (`cmd/darepod`), CLI client (`cmd/darepocli`), +and supporting build tools: `cmd/merge-sql-schemas` (concatenates sqlc +migration files for embedding), `cmd/protoc-gen-mailboxrpc` (protoc plugin +generating the mailbox-actor RPC glue), and `cmd/walletdk-wasm` (js/wasm +build target exposing `sdk/walletdk` to browser JS). ## Relationships -- **Depends on**: `darepod` (daemon orchestrator), `daemonrpc` (gRPC API definitions). +- **Depends on**: `darepod` (daemon orchestrator), `daemonrpc` (gRPC API + definitions), `sdk/walletdk` (wasm target only). - **Depended on by**: nothing (top-level binaries). ## Deep Docs diff --git a/cmd/CLAUDE.md b/cmd/CLAUDE.md index 1b212b976..2ffe383f1 100644 --- a/cmd/CLAUDE.md +++ b/cmd/CLAUDE.md @@ -2,11 +2,16 @@ ## Purpose -Entry points for the daemon (`cmd/darepod`) and CLI client (`cmd/darepocli`). +Entry points for the daemon (`cmd/darepod`), CLI client (`cmd/darepocli`), +and supporting build tools: `cmd/merge-sql-schemas` (concatenates sqlc +migration files for embedding), `cmd/protoc-gen-mailboxrpc` (protoc plugin +generating the mailbox-actor RPC glue), and `cmd/walletdk-wasm` (js/wasm +build target exposing `sdk/walletdk` to browser JS). ## Relationships -- **Depends on**: `darepod` (daemon orchestrator), `daemonrpc` (gRPC API definitions). +- **Depends on**: `darepod` (daemon orchestrator), `daemonrpc` (gRPC API + definitions), `sdk/walletdk` (wasm target only). - **Depended on by**: nothing (top-level binaries). ## Deep Docs diff --git a/cmd/darepocli/AGENTS.md b/cmd/darepocli/AGENTS.md index 5c902b10e..25f0b8326 100644 --- a/cmd/darepocli/AGENTS.md +++ b/cmd/darepocli/AGENTS.md @@ -2,13 +2,25 @@ ## Purpose -CLI client for interacting with a running darepod instance via gRPC. +Binary entry point for the `darepocli` CLI. `main` is a thin wrapper: it +builds the root cobra command from `darepoclicommands`, executes it, and +maps any returned error onto a semantic process exit code (2=invalid args, +3=auth, 4=not found, 10=dry-run) so scripting agents can branch on failure +category without parsing stderr prose. ## Relationships -- **Depends on**: `daemonrpc` (gRPC client stubs). +- **Depends on**: `cmd/darepocli/darepoclicommands` (root command, all + subcommands, exit-code table); `cmd/darepocli/internal/gen-devrpc` + (code-gen tool, not linked into the binary). - **Depended on by**: nothing (binary entry point). +## Invariants + +- Any error already printed via `darepoclicommands.PrintError` (checked + with `ErrorWasPrinted`) must not be printed again here; `main` only + renders the fallback envelope for errors that reach it unprinted. + ## Deep Docs - [docs/daemon_cli_guide.md](../../docs/daemon_cli_guide.md) — CLI reference. diff --git a/cmd/darepocli/CLAUDE.md b/cmd/darepocli/CLAUDE.md index 5c902b10e..25f0b8326 100644 --- a/cmd/darepocli/CLAUDE.md +++ b/cmd/darepocli/CLAUDE.md @@ -2,13 +2,25 @@ ## Purpose -CLI client for interacting with a running darepod instance via gRPC. +Binary entry point for the `darepocli` CLI. `main` is a thin wrapper: it +builds the root cobra command from `darepoclicommands`, executes it, and +maps any returned error onto a semantic process exit code (2=invalid args, +3=auth, 4=not found, 10=dry-run) so scripting agents can branch on failure +category without parsing stderr prose. ## Relationships -- **Depends on**: `daemonrpc` (gRPC client stubs). +- **Depends on**: `cmd/darepocli/darepoclicommands` (root command, all + subcommands, exit-code table); `cmd/darepocli/internal/gen-devrpc` + (code-gen tool, not linked into the binary). - **Depended on by**: nothing (binary entry point). +## Invariants + +- Any error already printed via `darepoclicommands.PrintError` (checked + with `ErrorWasPrinted`) must not be printed again here; `main` only + renders the fallback envelope for errors that reach it unprinted. + ## Deep Docs - [docs/daemon_cli_guide.md](../../docs/daemon_cli_guide.md) — CLI reference. diff --git a/cmd/darepocli/darepoclicommands/AGENTS.md b/cmd/darepocli/darepoclicommands/AGENTS.md index 45ad83e87..9734b9d0f 100644 --- a/cmd/darepocli/darepoclicommands/AGENTS.md +++ b/cmd/darepocli/darepoclicommands/AGENTS.md @@ -8,14 +8,16 @@ embed the same command tree. ## CLI Surface -The CLI surface is split into three tiers: +The CLI surface is split into four tiers: -1. **Top-level wallet verbs (implicit, no parent)** — the seven everyday - commands that map 1:1 to what a user does day-to-day. All seven are +1. **Top-level wallet verbs (implicit, no parent)** — the everyday + commands that map 1:1 to what a user does day-to-day. All are walletdkrpc-backed. 2. **Daemon introspection at root** — getinfo, schema, mcp, dev. 3. **Advanced subtrees (`ark`, `swap`)** — raw daemonrpc/swapclientrpc commands for power users and operator runbooks. +4. **`recovery` subtree** — manual operator control of daemon-owned + vHTLC recovery rows; not exposed via `schema`/MCP. ### Top-level wallet verbs @@ -23,12 +25,16 @@ The CLI surface is split into three tiers: |---------|-----|-------------| | `create` | `walletdkrpc.Create` | Initialize a new wallet (proxies GenSeed + InitWallet). Password from stdin / DAREPOD_WALLET_PASSWORD / --wallet_password_file | | `unlock` | `walletdkrpc.Unlock` | Unlock an existing wallet (proxies UnlockWallet) | -| `send ` | `walletdkrpc.Send` | Outbound payment. `--offchain` (default) for Lightning invoice, `--onchain` for cooperative leave. No prefix sniff | +| `send ` | `walletdkrpc.Send` | Outbound payment. `--offchain` (default) for a BOLT-11 invoice via the swap subsystem; `--onchain` for an atomic on-chain send (`--sweep-all` drains). No prefix sniff | | `recv` | `walletdkrpc.Recv` / `walletdkrpc.Deposit` | Inbound. `--offchain` (default) returns a Lightning invoice; `--onchain` returns a boarding address | | `activity` | `walletdkrpc.List` | Unified wallet activity view. Defaults to table output; `--format json` returns structured JSON. `--pending` and `--kind` narrow rows | +| `activity inspect ` | `walletdkrpc.InspectActivity` | Correlated swap/VTXO/ledger detail for one activity entry | | `balance` | `walletdkrpc.Balance` | Flat balance (confirmed_sat, pending_in_sat, pending_out_sat) | -| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Trigger a unilateral exit (proxies Unroll) | -| `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit job's status (proxies GetUnrollStatus) | +| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Queue a cooperative leave by default; unilateral unroll only fires with `--force-unroll-ack I_KNOW_WHAT_I_AM_DOING` | +| `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit/unroll job's status (proxies GetUnrollStatus) | +| `exit summary` | `walletdkrpc.ExitSummary` | Aggregate totals across all in-progress exits | +| `exit plan --outpoint ...` | `walletdkrpc.GetExitPlan` | Preview backing-wallet funding readiness for one or more exits | +| `wallet-sweep --destination ADDR` | `walletdkrpc.SweepWallet` | Preview, or with `--broadcast` publish, a sweep of confirmed backing-wallet UTXOs (boarding outputs excluded; see `ark sweep`) | ### Daemon introspection @@ -48,7 +54,7 @@ who want direct access. | Command | RPC | Description | |---------|-----|-------------| | `ark vtxos {list,refresh,leave}` | `ListVTXOs` / `RefreshVTXOs` / `LeaveVTXOs` | VTXO inventory and lifecycle | -| `ark rounds {get,list,watch}` | `GetRound` / `ListRounds` / `WatchRounds` | Round FSM state | +| `ark rounds {get,join,list,watch}` | `GetRound` / (join) / `ListRounds` / `WatchRounds` | Round FSM state; `join` commits queued intents into the next round (`vtxos refresh`/`leave` call it automatically) | | `ark oor {receive,get,list}` | `NewReceiveScript` / `GetOORSession` / `ListOORSessions` | OOR session inspection | | `ark board` | `Board` | Trigger boarding with confirmed UTXOs | | `ark sweep [list]` | `SweepBoardingUTXOs` / `ListBoardingSweeps` | Boarding-timeout sweeps | @@ -56,6 +62,18 @@ who want direct access. | `ark listtransactions` | `ListTransactions` | Raw paginated transaction history | | `ark send {inround,oor}` | `SendVTXO` / `SendOOR` | Raw in-round / OOR send (superseded by `send` for the wallet shape) | +### `recovery.*` advanced commands + +Manual control of daemon-owned vHTLC recovery rows; normal swap clients +let the swap FSM arm and cancel recovery automatically. + +| Command | RPC | Description | +|---------|-----|-------------| +| `recovery list` | `ListVHTLCRecoveries` | List recovery rows (ARMED rows are dormant) | +| `recovery status [id]` | `GetVHTLCRecoveryStatus` | Show one recovery row | +| `recovery escalate [id]` | `EscalateVHTLCRecovery` | Start on-chain unroll for an armed row; requires `--yes` on non-TTY stdin | +| `recovery cancel [id]` | `CancelVHTLCRecovery` | Record that cooperative settlement won; drop the armed row | + ### `swap.*` advanced commands (swapruntime build tag) | Command | RPC | Description | @@ -91,25 +109,25 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/cmd/d `DAREPOD_WALLET_PASSWORD` → `--wallet_password_file` → stdin → TTY. **Never from CLI args.** - `validateDestination()` / `validateOutpoint()` / - `validateFreeText()` — input hardening shared across the seven - top-level verbs (reject control chars, query/fragment chars, - malformed outpoints, ambiguous flag combos). + `validateFreeText()` — input hardening shared across the top-level + wallet verbs (reject control chars, query/fragment chars, malformed + outpoints, ambiguous flag combos). ## Relationships - **Depends on**: - - `rpc/walletdkrpc` (generated stubs for the seven top-level verbs; - `WalletService` client). - - `daemonrpc` (generated stubs for `ark.*` commands and getinfo). + - `rpc/walletdkrpc` (generated stubs for the top-level wallet verbs; + `WalletService` / `WalletInspectionService` clients). + - `daemonrpc` (generated stubs for `ark.*`, `recovery.*`, getinfo). - `rpc/swapclientrpc` (generated stubs for `swap.*` commands, `swapruntime` tag only). - **Depended on by**: `cmd/darepocli` (main entry point). ## Invariants -- The seven top-level wallet verbs ALWAYS register at the root - regardless of build tags; if the daemon lacks the walletdkrpc tag, - gRPC `Unimplemented` is mapped to `errWalletRPCDisabled` with an +- The top-level wallet verbs ALWAYS register at the root regardless + of build tags; if the daemon lacks the walletdkrpc tag, gRPC + `Unimplemented` is mapped to `errWalletRPCDisabled` with an actionable message pointing at the build doc. - The `--offchain` / `--onchain` flags on `send` and `recv` are mutually exclusive; if neither is set, offchain is the default. The @@ -121,6 +139,13 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/cmd/d - JSON output (`stdout`) and diagnostic output (`stderr`) are kept on separate streams so shell pipelines can consume the JSON body while a human reading the terminal sees informative warnings. +- `exit` defaults to a cooperative leave; it only starts a unilateral + on-chain unroll when `--force-unroll-ack` matches the literal string + `I_KNOW_WHAT_I_AM_DOING`, and that flag is mutually exclusive with + `--onchain-address`. +- `recovery escalate` refuses to run on non-interactive stdin unless + `--yes` is passed — it never blocks on a y/N prompt an agent can't + answer. ## Deep Docs diff --git a/cmd/darepocli/darepoclicommands/CLAUDE.md b/cmd/darepocli/darepoclicommands/CLAUDE.md index 45ad83e87..9734b9d0f 100644 --- a/cmd/darepocli/darepoclicommands/CLAUDE.md +++ b/cmd/darepocli/darepoclicommands/CLAUDE.md @@ -8,14 +8,16 @@ embed the same command tree. ## CLI Surface -The CLI surface is split into three tiers: +The CLI surface is split into four tiers: -1. **Top-level wallet verbs (implicit, no parent)** — the seven everyday - commands that map 1:1 to what a user does day-to-day. All seven are +1. **Top-level wallet verbs (implicit, no parent)** — the everyday + commands that map 1:1 to what a user does day-to-day. All are walletdkrpc-backed. 2. **Daemon introspection at root** — getinfo, schema, mcp, dev. 3. **Advanced subtrees (`ark`, `swap`)** — raw daemonrpc/swapclientrpc commands for power users and operator runbooks. +4. **`recovery` subtree** — manual operator control of daemon-owned + vHTLC recovery rows; not exposed via `schema`/MCP. ### Top-level wallet verbs @@ -23,12 +25,16 @@ The CLI surface is split into three tiers: |---------|-----|-------------| | `create` | `walletdkrpc.Create` | Initialize a new wallet (proxies GenSeed + InitWallet). Password from stdin / DAREPOD_WALLET_PASSWORD / --wallet_password_file | | `unlock` | `walletdkrpc.Unlock` | Unlock an existing wallet (proxies UnlockWallet) | -| `send ` | `walletdkrpc.Send` | Outbound payment. `--offchain` (default) for Lightning invoice, `--onchain` for cooperative leave. No prefix sniff | +| `send ` | `walletdkrpc.Send` | Outbound payment. `--offchain` (default) for a BOLT-11 invoice via the swap subsystem; `--onchain` for an atomic on-chain send (`--sweep-all` drains). No prefix sniff | | `recv` | `walletdkrpc.Recv` / `walletdkrpc.Deposit` | Inbound. `--offchain` (default) returns a Lightning invoice; `--onchain` returns a boarding address | | `activity` | `walletdkrpc.List` | Unified wallet activity view. Defaults to table output; `--format json` returns structured JSON. `--pending` and `--kind` narrow rows | +| `activity inspect ` | `walletdkrpc.InspectActivity` | Correlated swap/VTXO/ledger detail for one activity entry | | `balance` | `walletdkrpc.Balance` | Flat balance (confirmed_sat, pending_in_sat, pending_out_sat) | -| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Trigger a unilateral exit (proxies Unroll) | -| `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit job's status (proxies GetUnrollStatus) | +| `exit --outpoint TXID:VOUT` | `walletdkrpc.Exit` | Queue a cooperative leave by default; unilateral unroll only fires with `--force-unroll-ack I_KNOW_WHAT_I_AM_DOING` | +| `exit status --outpoint TXID:VOUT` | `walletdkrpc.ExitStatus` | Query an exit/unroll job's status (proxies GetUnrollStatus) | +| `exit summary` | `walletdkrpc.ExitSummary` | Aggregate totals across all in-progress exits | +| `exit plan --outpoint ...` | `walletdkrpc.GetExitPlan` | Preview backing-wallet funding readiness for one or more exits | +| `wallet-sweep --destination ADDR` | `walletdkrpc.SweepWallet` | Preview, or with `--broadcast` publish, a sweep of confirmed backing-wallet UTXOs (boarding outputs excluded; see `ark sweep`) | ### Daemon introspection @@ -48,7 +54,7 @@ who want direct access. | Command | RPC | Description | |---------|-----|-------------| | `ark vtxos {list,refresh,leave}` | `ListVTXOs` / `RefreshVTXOs` / `LeaveVTXOs` | VTXO inventory and lifecycle | -| `ark rounds {get,list,watch}` | `GetRound` / `ListRounds` / `WatchRounds` | Round FSM state | +| `ark rounds {get,join,list,watch}` | `GetRound` / (join) / `ListRounds` / `WatchRounds` | Round FSM state; `join` commits queued intents into the next round (`vtxos refresh`/`leave` call it automatically) | | `ark oor {receive,get,list}` | `NewReceiveScript` / `GetOORSession` / `ListOORSessions` | OOR session inspection | | `ark board` | `Board` | Trigger boarding with confirmed UTXOs | | `ark sweep [list]` | `SweepBoardingUTXOs` / `ListBoardingSweeps` | Boarding-timeout sweeps | @@ -56,6 +62,18 @@ who want direct access. | `ark listtransactions` | `ListTransactions` | Raw paginated transaction history | | `ark send {inround,oor}` | `SendVTXO` / `SendOOR` | Raw in-round / OOR send (superseded by `send` for the wallet shape) | +### `recovery.*` advanced commands + +Manual control of daemon-owned vHTLC recovery rows; normal swap clients +let the swap FSM arm and cancel recovery automatically. + +| Command | RPC | Description | +|---------|-----|-------------| +| `recovery list` | `ListVHTLCRecoveries` | List recovery rows (ARMED rows are dormant) | +| `recovery status [id]` | `GetVHTLCRecoveryStatus` | Show one recovery row | +| `recovery escalate [id]` | `EscalateVHTLCRecovery` | Start on-chain unroll for an armed row; requires `--yes` on non-TTY stdin | +| `recovery cancel [id]` | `CancelVHTLCRecovery` | Record that cooperative settlement won; drop the armed row | + ### `swap.*` advanced commands (swapruntime build tag) | Command | RPC | Description | @@ -91,25 +109,25 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/cmd/d `DAREPOD_WALLET_PASSWORD` → `--wallet_password_file` → stdin → TTY. **Never from CLI args.** - `validateDestination()` / `validateOutpoint()` / - `validateFreeText()` — input hardening shared across the seven - top-level verbs (reject control chars, query/fragment chars, - malformed outpoints, ambiguous flag combos). + `validateFreeText()` — input hardening shared across the top-level + wallet verbs (reject control chars, query/fragment chars, malformed + outpoints, ambiguous flag combos). ## Relationships - **Depends on**: - - `rpc/walletdkrpc` (generated stubs for the seven top-level verbs; - `WalletService` client). - - `daemonrpc` (generated stubs for `ark.*` commands and getinfo). + - `rpc/walletdkrpc` (generated stubs for the top-level wallet verbs; + `WalletService` / `WalletInspectionService` clients). + - `daemonrpc` (generated stubs for `ark.*`, `recovery.*`, getinfo). - `rpc/swapclientrpc` (generated stubs for `swap.*` commands, `swapruntime` tag only). - **Depended on by**: `cmd/darepocli` (main entry point). ## Invariants -- The seven top-level wallet verbs ALWAYS register at the root - regardless of build tags; if the daemon lacks the walletdkrpc tag, - gRPC `Unimplemented` is mapped to `errWalletRPCDisabled` with an +- The top-level wallet verbs ALWAYS register at the root regardless + of build tags; if the daemon lacks the walletdkrpc tag, gRPC + `Unimplemented` is mapped to `errWalletRPCDisabled` with an actionable message pointing at the build doc. - The `--offchain` / `--onchain` flags on `send` and `recv` are mutually exclusive; if neither is set, offchain is the default. The @@ -121,6 +139,13 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/cmd/d - JSON output (`stdout`) and diagnostic output (`stderr`) are kept on separate streams so shell pipelines can consume the JSON body while a human reading the terminal sees informative warnings. +- `exit` defaults to a cooperative leave; it only starts a unilateral + on-chain unroll when `--force-unroll-ack` matches the literal string + `I_KNOW_WHAT_I_AM_DOING`, and that flag is mutually exclusive with + `--onchain-address`. +- `recovery escalate` refuses to run on non-interactive stdin unless + `--yes` is passed — it never blocks on a y/N prompt an agent can't + answer. ## Deep Docs diff --git a/cmd/darepocli/internal/gen-devrpc/AGENTS.md b/cmd/darepocli/internal/gen-devrpc/AGENTS.md index 299220119..5e51b6b08 100644 --- a/cmd/darepocli/internal/gen-devrpc/AGENTS.md +++ b/cmd/darepocli/internal/gen-devrpc/AGENTS.md @@ -3,10 +3,10 @@ ## Purpose Build-time code generator that produces -`cmd/darepocli/darepoclicommands/devrpc/registry_generated.go`. Scans -`daemonrpc` and `swapclientrpc` proto descriptors and emits a static -service/method registry with alias tables and extracted comments for the dev -RPC command tree. +`cmd/darepocli/darepoclicommands/devrpc/registry_generated.go`. Scans the +`daemonrpc`, `swapclientrpc`, `walletdkrpc`, and `btcwallet/rpc/walletrpc` +proto descriptors and emits a static service/method registry with alias +tables and extracted comments for the dev RPC command tree. ## Key Types @@ -31,8 +31,9 @@ RPC command tree. ## Relationships -- **Depends on**: `daemonrpc` (for `File_daemon_proto`), - `rpc/swapclientrpc` (for `File_swap_client_proto`). +- **Depends on**: `daemonrpc` (`File_daemon_proto`), `rpc/swapclientrpc` + (`File_swap_client_proto`), `rpc/walletdkrpc` (`File_wallet_proto`), + `github.com/btcsuite/btcwallet/rpc/walletrpc` (`File_api_proto`). - **Depended on by**: nothing at runtime. This is a `package main` build tool invoked by `make rpc` or an equivalent Makefile target. @@ -40,12 +41,14 @@ RPC command tree. - The output file contains a "Code generated by gen-devrpc. DO NOT EDIT." header; editing it manually will be overwritten on the next `make rpc`. -- The generator validates that both `DaemonService` and `SwapClientService` - are present in the scanned files; it fails with an error if either is - missing, catching proto renames early. +- The generator validates that all six `expectedServices` (DaemonService, + SwapClientService, walletdkrpc's WalletService and + WalletInspectionService, and walletrpc's VersionService and + WalletService) are present in the scanned files; it fails with an error + if any is missing, catching proto renames early. ## Deep Docs -- [cmd/darepocli/darepoclicommands/devrpc/CLAUDE.md](../darepoclicommands/devrpc/CLAUDE.md) — +- [cmd/darepocli/darepoclicommands/devrpc/CLAUDE.md](../../darepoclicommands/devrpc/CLAUDE.md) — The package this generator feeds. - [ARCHITECTURE.md](../../../../ARCHITECTURE.md) — System-wide package map. diff --git a/cmd/darepocli/internal/gen-devrpc/CLAUDE.md b/cmd/darepocli/internal/gen-devrpc/CLAUDE.md index 299220119..5e51b6b08 100644 --- a/cmd/darepocli/internal/gen-devrpc/CLAUDE.md +++ b/cmd/darepocli/internal/gen-devrpc/CLAUDE.md @@ -3,10 +3,10 @@ ## Purpose Build-time code generator that produces -`cmd/darepocli/darepoclicommands/devrpc/registry_generated.go`. Scans -`daemonrpc` and `swapclientrpc` proto descriptors and emits a static -service/method registry with alias tables and extracted comments for the dev -RPC command tree. +`cmd/darepocli/darepoclicommands/devrpc/registry_generated.go`. Scans the +`daemonrpc`, `swapclientrpc`, `walletdkrpc`, and `btcwallet/rpc/walletrpc` +proto descriptors and emits a static service/method registry with alias +tables and extracted comments for the dev RPC command tree. ## Key Types @@ -31,8 +31,9 @@ RPC command tree. ## Relationships -- **Depends on**: `daemonrpc` (for `File_daemon_proto`), - `rpc/swapclientrpc` (for `File_swap_client_proto`). +- **Depends on**: `daemonrpc` (`File_daemon_proto`), `rpc/swapclientrpc` + (`File_swap_client_proto`), `rpc/walletdkrpc` (`File_wallet_proto`), + `github.com/btcsuite/btcwallet/rpc/walletrpc` (`File_api_proto`). - **Depended on by**: nothing at runtime. This is a `package main` build tool invoked by `make rpc` or an equivalent Makefile target. @@ -40,12 +41,14 @@ RPC command tree. - The output file contains a "Code generated by gen-devrpc. DO NOT EDIT." header; editing it manually will be overwritten on the next `make rpc`. -- The generator validates that both `DaemonService` and `SwapClientService` - are present in the scanned files; it fails with an error if either is - missing, catching proto renames early. +- The generator validates that all six `expectedServices` (DaemonService, + SwapClientService, walletdkrpc's WalletService and + WalletInspectionService, and walletrpc's VersionService and + WalletService) are present in the scanned files; it fails with an error + if any is missing, catching proto renames early. ## Deep Docs -- [cmd/darepocli/darepoclicommands/devrpc/CLAUDE.md](../darepoclicommands/devrpc/CLAUDE.md) — +- [cmd/darepocli/darepoclicommands/devrpc/CLAUDE.md](../../darepoclicommands/devrpc/CLAUDE.md) — The package this generator feeds. - [ARCHITECTURE.md](../../../../ARCHITECTURE.md) — System-wide package map. diff --git a/cmd/darepod/AGENTS.md b/cmd/darepod/AGENTS.md index fe22bc1e4..7e362578f 100644 --- a/cmd/darepod/AGENTS.md +++ b/cmd/darepod/AGENTS.md @@ -2,10 +2,46 @@ ## Purpose -Daemon entry point. Parses flags, initializes configuration, and starts the -`darepod.Server`. +Daemon entry point. Builds the cobra/viper flag surface, loads a +`darepod.Config`, wires optional build-tag-gated subservers onto it, and +hands off to `darepod.Main` to run the daemon. + +## Key Functions + +- `newRootCmd()` — builds the `darepod` cobra command: registers all + flags (datadir, network, lnd.*, wallet.*, bitcoind.*, oor.limits.*, + db.sqlite.*, etc.), binds them through viper (flag > env > config + file > default), then runs `configureBitcoindSubmitter`, + `configureSwapRuntime`, and `configureWalletRPC` in `PreRunE` before + `run(cfg)`. +- `run(cfg)` — validates the config, wires the daemon log writer, installs + an OS signal interceptor, and calls `darepod.Main`. +- `configureSwapRuntime(cfg)` / `configureWalletRPC(cfg)` — build-tag-gated + (see Invariants) hooks that append optional RPC subserver registrars + (`swapclientserver.Register`, `swapwallet.Register`) onto `cfg`. +- `configureBitcoindSubmitter(v, cfg)` — opt-in direct bitcoind + `submitpackage` wiring for V3 ephemeral-anchor package relay; a no-op + when `bitcoind.host` is unset. ## Relationships -- **Depends on**: `darepod` (Server orchestrator). +- **Depends on**: `darepod` (`Config`, `Main`, the Server orchestrator), + `swapclientserver` (swap subserver, `swapruntime` tag), + `swapwallet` (wallet subserver, `walletdkrpc`+`swapruntime` tags), + `chainbackends/bitcoindrpc` (direct package-relay submitter). - **Depended on by**: nothing (binary entry point). + +## Invariants + +- `configureWalletRPC` requires BOTH the `walletdkrpc` and `swapruntime` + build tags (`walletdkrpc.go` has `//go:build walletdkrpc && swapruntime`); + a `walletdkrpc`-only build still gets the stub no-op from + `walletdkrpc_stub.go`, because the wallet subserver composes the daemon's + swap subsystem and cannot exist without it. +- `configureWalletRPC` runs AFTER `configureSwapRuntime` in `PreRunE`; the + wallet registrar reads `cfg.Swap.Backend`, which the swap subserver + registrar publishes, and sets `cfg.Swap.SuppressResume = true` so the + wallet layer (not the swap subserver) drives the unified startup resume. +- `EagerRoundJoin`'s flag default comes from `darepod.DefaultConfig()`, + which is itself build-tag aware (true under `walletdkrpc`, false + otherwise); `--eagerroundjoin` still overrides it either way. diff --git a/cmd/darepod/CLAUDE.md b/cmd/darepod/CLAUDE.md index fe22bc1e4..7e362578f 100644 --- a/cmd/darepod/CLAUDE.md +++ b/cmd/darepod/CLAUDE.md @@ -2,10 +2,46 @@ ## Purpose -Daemon entry point. Parses flags, initializes configuration, and starts the -`darepod.Server`. +Daemon entry point. Builds the cobra/viper flag surface, loads a +`darepod.Config`, wires optional build-tag-gated subservers onto it, and +hands off to `darepod.Main` to run the daemon. + +## Key Functions + +- `newRootCmd()` — builds the `darepod` cobra command: registers all + flags (datadir, network, lnd.*, wallet.*, bitcoind.*, oor.limits.*, + db.sqlite.*, etc.), binds them through viper (flag > env > config + file > default), then runs `configureBitcoindSubmitter`, + `configureSwapRuntime`, and `configureWalletRPC` in `PreRunE` before + `run(cfg)`. +- `run(cfg)` — validates the config, wires the daemon log writer, installs + an OS signal interceptor, and calls `darepod.Main`. +- `configureSwapRuntime(cfg)` / `configureWalletRPC(cfg)` — build-tag-gated + (see Invariants) hooks that append optional RPC subserver registrars + (`swapclientserver.Register`, `swapwallet.Register`) onto `cfg`. +- `configureBitcoindSubmitter(v, cfg)` — opt-in direct bitcoind + `submitpackage` wiring for V3 ephemeral-anchor package relay; a no-op + when `bitcoind.host` is unset. ## Relationships -- **Depends on**: `darepod` (Server orchestrator). +- **Depends on**: `darepod` (`Config`, `Main`, the Server orchestrator), + `swapclientserver` (swap subserver, `swapruntime` tag), + `swapwallet` (wallet subserver, `walletdkrpc`+`swapruntime` tags), + `chainbackends/bitcoindrpc` (direct package-relay submitter). - **Depended on by**: nothing (binary entry point). + +## Invariants + +- `configureWalletRPC` requires BOTH the `walletdkrpc` and `swapruntime` + build tags (`walletdkrpc.go` has `//go:build walletdkrpc && swapruntime`); + a `walletdkrpc`-only build still gets the stub no-op from + `walletdkrpc_stub.go`, because the wallet subserver composes the daemon's + swap subsystem and cannot exist without it. +- `configureWalletRPC` runs AFTER `configureSwapRuntime` in `PreRunE`; the + wallet registrar reads `cfg.Swap.Backend`, which the swap subserver + registrar publishes, and sets `cfg.Swap.SuppressResume = true` so the + wallet layer (not the swap subserver) drives the unified startup resume. +- `EagerRoundJoin`'s flag default comes from `darepod.DefaultConfig()`, + which is itself build-tag aware (true under `walletdkrpc`, false + otherwise); `--eagerroundjoin` still overrides it either way. diff --git a/cmd/protoc-gen-mailboxrpc/internal/gen/AGENTS.md b/cmd/protoc-gen-mailboxrpc/internal/gen/AGENTS.md index 358145705..26a9091dc 100644 --- a/cmd/protoc-gen-mailboxrpc/internal/gen/AGENTS.md +++ b/cmd/protoc-gen-mailboxrpc/internal/gen/AGENTS.md @@ -20,8 +20,8 @@ metadata. references for `mailbox/rpc`, `context`, `proto`, `fmt`). - `serviceData` / `methodData` — Template input structs. - `serviceTmpl` — `text/template` expanding `serviceRawTemplate` into Go - source that compiles against `mailbox/rpc.RPCClient`, - `rpc.SendRPC`/`AwaitRPC`, and `rpc.Router`. + source that calls the generated `RPCClient`'s `SendRPC`/`AwaitRPC` methods + and registers handlers with `rpc.Router`. ## Relationships diff --git a/cmd/protoc-gen-mailboxrpc/internal/gen/CLAUDE.md b/cmd/protoc-gen-mailboxrpc/internal/gen/CLAUDE.md index 358145705..26a9091dc 100644 --- a/cmd/protoc-gen-mailboxrpc/internal/gen/CLAUDE.md +++ b/cmd/protoc-gen-mailboxrpc/internal/gen/CLAUDE.md @@ -20,8 +20,8 @@ metadata. references for `mailbox/rpc`, `context`, `proto`, `fmt`). - `serviceData` / `methodData` — Template input structs. - `serviceTmpl` — `text/template` expanding `serviceRawTemplate` into Go - source that compiles against `mailbox/rpc.RPCClient`, - `rpc.SendRPC`/`AwaitRPC`, and `rpc.Router`. + source that calls the generated `RPCClient`'s `SendRPC`/`AwaitRPC` methods + and registers handlers with `rpc.Router`. ## Relationships diff --git a/daemonrpc/AGENTS.md b/daemonrpc/AGENTS.md index 4e81f2111..6c8253fa9 100644 --- a/daemonrpc/AGENTS.md +++ b/daemonrpc/AGENTS.md @@ -2,14 +2,40 @@ ## Purpose -Daemon gRPC API definitions for wallet operations and round queries. Proto -source: `daemonrpc/daemon.proto`. +Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll, and +VHTLC-recovery operations. Proto source: `daemonrpc/daemon.proto`. Generated +gRPC, REST-gateway, and mailbox-RPC stubs plus one hand-written helper file +(`errors.go`) for structured wallet-lifecycle errors. + +## Key Types + +- `DaemonServiceClient` / `DaemonServiceServer` — Generated gRPC client and + server interfaces for the daemon API. +- `DaemonServiceMailboxClient` / `DaemonServiceMailboxServer` — Generated + mailbox-RPC client/server stubs (via `protoc-gen-mailboxrpc`). +- `WalletNotReadyError(msg)` / `WalletNotReadyStateError(msg, state)` — Build a + structured `FailedPrecondition` gRPC error carrying a stable `ErrorInfo` + reason (`WalletNotReadyReason`) and optional `wallet_state` metadata. +- `IsWalletNotReadyError(err)` / `WalletNotReadyState(err)` — Match and unpack + the structured error produced above; callers should key off these instead of + matching on message text. ## Relationships -- **Depends on**: nothing (proto definitions). -- **Depended on by**: `darepod` (implements services), `cmd/darepocli` (uses generated clients). +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the + generated mailbox stubs), `google.golang.org/genproto/googleapis/rpc/errdetails` + and `google.golang.org/grpc` (structured errors in `errors.go`), + `grpc-gateway/runtime` (REST gateway in `daemon.pb.gw.go`). +- **Depended on by**: `darepod` (implements `DaemonServiceServer`), + `cmd/darepocli` and `rpc/restclient` (CLI/REST clients), `sdk/ark`, + `sdk/swaps`, `sdk/walletdk`, `swapclientserver`, `swapwallet` (typed clients + for daemon RPCs). ## Invariants -- **Never edit generated code** — regenerate via `make rpc`. +- **Never edit generated code** (`daemon.pb.go`, `daemon_grpc.pb.go`, + `daemon.pb.gw.go`, `daemon_mailboxrpc.pb.go`) — regenerate via `make rpc` + after editing `daemon.proto` or `daemon.yaml`. +- `errors.go` is hand-written and not regenerated; callers must match wallet + lifecycle errors via `IsWalletNotReadyError`/`WalletNotReadyState`, never by + parsing the error message string. diff --git a/daemonrpc/CLAUDE.md b/daemonrpc/CLAUDE.md index 4e81f2111..6c8253fa9 100644 --- a/daemonrpc/CLAUDE.md +++ b/daemonrpc/CLAUDE.md @@ -2,14 +2,40 @@ ## Purpose -Daemon gRPC API definitions for wallet operations and round queries. Proto -source: `daemonrpc/daemon.proto`. +Daemon gRPC API definitions for wallet, boarding, round, OOR, unroll, and +VHTLC-recovery operations. Proto source: `daemonrpc/daemon.proto`. Generated +gRPC, REST-gateway, and mailbox-RPC stubs plus one hand-written helper file +(`errors.go`) for structured wallet-lifecycle errors. + +## Key Types + +- `DaemonServiceClient` / `DaemonServiceServer` — Generated gRPC client and + server interfaces for the daemon API. +- `DaemonServiceMailboxClient` / `DaemonServiceMailboxServer` — Generated + mailbox-RPC client/server stubs (via `protoc-gen-mailboxrpc`). +- `WalletNotReadyError(msg)` / `WalletNotReadyStateError(msg, state)` — Build a + structured `FailedPrecondition` gRPC error carrying a stable `ErrorInfo` + reason (`WalletNotReadyReason`) and optional `wallet_state` metadata. +- `IsWalletNotReadyError(err)` / `WalletNotReadyState(err)` — Match and unpack + the structured error produced above; callers should key off these instead of + matching on message text. ## Relationships -- **Depends on**: nothing (proto definitions). -- **Depended on by**: `darepod` (implements services), `cmd/darepocli` (uses generated clients). +- **Depends on**: `mailbox/rpc` (mailbox-RPC runtime types used by the + generated mailbox stubs), `google.golang.org/genproto/googleapis/rpc/errdetails` + and `google.golang.org/grpc` (structured errors in `errors.go`), + `grpc-gateway/runtime` (REST gateway in `daemon.pb.gw.go`). +- **Depended on by**: `darepod` (implements `DaemonServiceServer`), + `cmd/darepocli` and `rpc/restclient` (CLI/REST clients), `sdk/ark`, + `sdk/swaps`, `sdk/walletdk`, `swapclientserver`, `swapwallet` (typed clients + for daemon RPCs). ## Invariants -- **Never edit generated code** — regenerate via `make rpc`. +- **Never edit generated code** (`daemon.pb.go`, `daemon_grpc.pb.go`, + `daemon.pb.gw.go`, `daemon_mailboxrpc.pb.go`) — regenerate via `make rpc` + after editing `daemon.proto` or `daemon.yaml`. +- `errors.go` is hand-written and not regenerated; callers must match wallet + lifecycle errors via `IsWalletNotReadyError`/`WalletNotReadyState`, never by + parsing the error message string. diff --git a/darepod/AGENTS.md b/darepod/AGENTS.md index 57f51ff42..15603127e 100644 --- a/darepod/AGENTS.md +++ b/darepod/AGENTS.md @@ -2,367 +2,92 @@ ## Purpose -Top-level daemon orchestrator that wires wallet backend, mailbox transport, -chain backend, database, and all domain actors into a running system with a -gRPC API. +Top-level daemon orchestrator that wires the wallet backend, mailbox +transport, chain backend, database, and all domain actors into a running +system with a gRPC API. ## Key Types For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darepod.`. -### Server & Configuration - -- `Server` — main daemon. Owns wallet, DB, chainsource actor, gRPC +- `Server` — main daemon. Owns the wallet, DB, chainsource actor, gRPC server, and `ActorSystem`. Caches `localMailboxID` (pubkey-derived), - `authSigHex` (Schnorr auth) and a single `clk` (`clock.Clock`) that - all sub-stores share for deterministic time injection. -- `RPCServer` — implements gRPC `DaemonService`. Holds an in-memory - `customInputLocks` map (guarded by `customInputLocksMu`) that - reserves custom OOR input outpoints for the duration of a `SendOOR` - call. -- `Config` — daemon configuration. Notable fields: - `MailboxEdgeFactory` (test transport interception), - `PackageSubmitter chainbackends.PackageSubmitter` (v3 CPFP submitter - injected by the harness via `BitcoindPackageSubmitter` and by - `cmd/darepod` from `bitcoind.{host,user,pass}` flags; not - serialized), optional `Unroll *UnrollConfig`, and `MaxOperatorFeeSat - int64` cap fed into `ClientEnvironment.MaxOperatorFee`. Under the - #270 seal-time fee handshake every server-issued `JoinRoundQuote` - is compared against this cap; `Config.Validate()` fails closed when - the value is non-positive. CLI flag `--maxoperatorfeesat`; - `DefaultMaxOperatorFeeSat` is a generous default. -- `UnrollConfig` — `BumpAfterBlocks int32` (fee-bump cadence; zero → - default 6), `MaxFeeRateSatPerVByte int64` (cap fed to both - `txconfirm` and the unroll registry; zero → each subsystem's - default). Surfaced via `unrollMaxFeeRate()`. -- `OORConfig` / `OORLimitsConfig` — incoming receive safety caps - (`MaxCheckpoints`, `MaxVTXOMatches`, `MaxMailboxItems`, - `MaxMailboxScriptBytes`). `Config.OORReceiveLimits()` normalizes - into `oor.ReceiveLimits`. -- `WalletState` — `None` / `Locked` / `Ready`. - -### RPC Handlers - -- `Board` — non-blocking; delegates to wallet actor. -- `GetRound` / `ListRounds` — round operation status. Live rounds come - from the round actor; persisted rounds from SQL summaries. Live - rounds surface `commitment_txid` once the FSM reaches - `CommitmentTxReceived` (recovered from the state's `TxID` or the - embedded commitment PSBT for MuSig2 phases) plus the per-owner - expected VTXO amounts, filtered by `IsOwner=true` so other clients' - outputs don't inflate the local view (helper: `liveRoundDetails`). -- `GetOORSession` / `ListOORSessions` — pending and failed from OOR - actor `ListSessionsRequest`; completed from persisted artifacts. - Actor state is authoritative when both views exist. -- `SendVTXO` — in-round directed sends. Validates recipients (count - cap, positive and `MaxSatoshi`-bounded amounts, overflow-safe sum), - resolves destinations via `resolveRecipientOutput`, delegates to - the wallet actor. -- `resolveRecipientOutput` — extracts pkScript and client pubkey from - an `Output` proto oneof (pubkey or address). Taproot-only. -- `ListVTXOs` — paginated VTXO inventory. When called with - `VTXO_STATUS_PENDING_ROUND`, branches to `listPendingRoundVTXOs` - which bypasses `s.vtxoStore` and projects synthetic VTXOs (amount - + round id + commitment txid; outpoint deliberately empty) from - each live round FSM via `queryRoundStates`. Rounds already in - `ROUND_STATE_CONFIRMED` are skipped so the store-backed - `VTXO_STATUS_LIVE` rows don't double-report. -- `Unroll` / `GetUnrollStatus` — manual unilateral-exit RPCs. `Unroll` - short-circuits with `Created=false` when the VTXO is already in - `VTXOStatusUnilateralExit`; else asks the VTXO manager - `ForceUnrollRequest{Reason: "manual RPC request"}`. Uses - `manualUnrollAdmissionTimeout`-bounded context derived via - `context.WithoutCancel(ctx)` so CLI disconnect doesn't cancel the - daemon-local handoff. `GetUnrollStatus` is read-through: prefers - live registry via `queryUnrollRegistry`, falls back to - `db.UnilateralExitPersistenceStore.GetJob`. Returns `Found=false` - (not error) when neither layer has a record. -- `unrollPhaseToProto` / `unrollJobStatusToProto` — dual mappers from - live `unroll.Phase` and persisted `db.UnilateralExitJobStatus` to - the same proto. `PhaseSweepBroadcast` and `PhaseSweepConfirmation` - both project to `UNROLL_JOB_STATUS_SWEEPING`. -- `EstimateFee` / `GetFeeHistory` — operator fee surface. - `EstimateFee` proxies to the operator's `EstimateFee` over the - direct gRPC connection (`s.serverConn`); no local caching. - `GetFeeHistory` reads through - `s.ledgerStore.ListLedgerEntriesWithFeesTotal` for page + - cumulative-total consistency. -- `ListTransactions` — newest-first unified history from ledger + - sweep DBs. Accepts `type` filter, optional time range, `limit` - (cap 1000), `offset` (clamped to `math.MaxInt32`). Delegates to - `ledgerStore.ListTransactionHistory` and projects via - `transactionHistoryRowToProto`. -- `proxyUpstreamError(err, msg)` — gRPC-safety helper preserving - upstream codes while stripping operator-side text. Errors without a - status map to `codes.Unavailable`. -- `quoteOperatorFee` — internal helper asking the operator's - `ArkService.EstimateFee` via direct gRPC. Returns - `codes.Unavailable` when `serverConn` is nil (degraded mode) so - callers can distinguish transient from permanent. -- `SweepBoardingUTXOs` — sweeps CSV-mature boarding UTXOs back to the - wallet. Resolves candidates (explicit outpoints or all - confirmed/failed/expired intents), estimates fee, builds and signs - an aggregate tx via `buildBoardingSweepTx`, persists, broadcasts, - and wakes `boardingSweepWatcher`. Returns preview when - `broadcast=false`. -- `ListBoardingSweeps` — paginated persisted aggregate sweeps with - optional status filter and cursor-based pagination. -- `ArmVHTLCRecovery` — persists a dormant vHTLC on-chain recovery job (armed - state). The job remains dormant until `EscalateVHTLCRecovery` is called. - Idempotent on `request_id`. -- `EscalateVHTLCRecovery` — transitions an armed job into active unroll by - calling `coordinator.Service.EscalateRecovery`. Triggers - `TargetMaterializer.EnsureRecoveryTarget` before admitting the target to - the unroll registry. -- `CancelVHTLCRecovery` — marks a recovery job cancelled (cooperative - settlement or explicit operator action). -- `StatusVHTLCRecovery` — returns the current recovery row joined with live - unroll status for the target outpoint. -- `SendOnChain` — RPC handler delegating to the wallet actor's - `SendOnChainRequest`. Routes through coin selection, leave output - construction, and eager round join. Supports bounded and sweep-all modes. - -### Adapters & Helpers - -- `serverDurableUnaryBuilder` — implements - `serverconn.DurableUnaryRequestBuilder` via the indexer client with - proof-of-control credentials. -- `IndexerProofKey` — derives the fixed wallet key for a given key - locator; returns an `indexer.SchnorrSigner` backed by the proof-key - backend. -- `NewOwnedReceiveScriptSigner` — indexer signer that resolves the - wallet key for any persisted owned receive script, then delegates - to the backend-specific signer. -- `ownedScriptCheckerAdapter` / `ownedScriptRegistrarAdapter` / - `ownedScriptLookupAdapter` — wrap `db.OORArtifactPersistenceStore` - to satisfy `round.OwnedScriptChecker` / - `round.OwnedScriptRegistrar` / `vtxo.OwnedScriptLookup`. The - checker uses `context.WithoutCancel` so confirmation-time ownership - survives FSM shutdown; returns `false` on `sql.ErrNoRows`. The - registrar persists pkScripts as `OwnedReceiveScriptSourceWallet` - with the operator pubkey and VTXO exit delay from `OperatorTerms`. -- `EnsureDefaultOORReceiveScript` / `CreateOORReceiveScript` — - receive-key lifecycle: derive, register with indexer - (proof-of-control), persist ownership record. -- `ResolveIncomingMetadataFromIndexer` — resolves authoritative VTXO - lineage metadata from `ListVTXOsByScripts`. -- Ancestry conversion lives in - [`vtxo.AncestryFromRPC`](../vtxo/incoming_ancestry.go); the - darepod-local copy that previously lived here was deleted when the - OOR and in-round receive paths both converged on the shared `vtxo` - helper. `vtxo.MaxAncestryPaths = 64` is the shared cap. -- `lndUnrollWallet` / `lwUnrollWallet` / `btcwUnrollWallet` — - backend-specific adapters satisfying both `txconfirm.Wallet` - (`ListUnspent`/`NewWalletPkScript`/`FinalizePsbt`/`LeaseOutput`/ - `ReleaseOutput`) and `unroll.SweepWallet`. LND forwards to the - `BoardingBackend`; lwwallet/btcwallet paths reach into `BtcWallet` - directly, reinterpreting `wallet.LockID` as `wtxmgr.LockID` via - direct `[32]byte` cast so leases round-trip across restart. -- `reserveCustomInputs` (on `RPCServer`) — atomically claims every - custom OOR outpoint for a `SendOOR` call. Returns a release - function (typically deferred). -- `autoRefreshFeeQuoter` — wires `vtxo.RefreshFeeQuoter` into every - VTXO actor. Advisory under #270: the closure's return value - populates `RefreshVTXORequest.OperatorFee` for observability but - is not written to the intent. Falls back to - `terms.MinOperatorFee` when unreachable. -- `boardingSweepWatcher` — daemon-owned background watcher: resumes - pending sweeps on startup, rate-limited rebroadcasts, registers - spend notifications per input, marks inputs spent on confirmation. - Started by `startBoardingSweepWatcher` on wallet unlock; - idempotent. -- `vhtlcRecoveryTargetMaterializer` — darepod adapter implementing - `coordinator.TargetMaterializer`. Binds vHTLC recovery rows to local OOR - packages and VTXO descriptors so the generic unroll subsystem can assemble - lineage and watch the target without swap-specific knowledge. -- `boardingSweepTx` / `buildBoardingSweepTx` — constructs and signs - one aggregate timeout-path sweep tx. Iterates the weight estimate - up to three times until `SerializeSize` converges so `fee`/`txid` - are accurate. Validates `defaultBoardingSweepMaxFeePercent = 25%` - and `defaultBoardingSweepMaxInputs = 100`. -- `deriveIdentityKeyEarly` — derives the client's secp256k1 identity - key from LND or lwwallet before mailbox transport starts. -- `signMailboxAuth` — Schnorr auth. LND uses the tagged Schnorr - signing RPC (`withSchnorrTag`); lwwallet signs locally via - `serverconn.SignMailboxAuth`. -- `fetchOperatorPubKeyDirect` — fetches operator pubkey via direct - gRPC `GetInfo` before the mailbox runtime starts. -- `initLedgerActor` — constructs `ledger.LedgerActor` with both - `LedgerStoreDB` and `UTXOAuditStoreDB`, registers under - `ledger.ServiceKeyName`, stashes `LedgerStoreDB` on the `Server` - for RPC reads. Called after DB ready but before wallet unlock. -- `initUnrollSubsystem` — wires the unilateral-exit runtime during - `startWalletDependentActors` (step 12, before `initOORActor`). - Builds a backend adapter, registers the shared `TxBroadcasterActor` - under `"txconfirm"`, constructs `UnrollRegistryActor` with the - persistence store, `LocalProofAssembler`, shared `txConfirmRef`, - and wallet, then calls `RestoreNonTerminal(ctx)`. Builds a - `MapInputRef` translating `vtxo.ExpiringNotification` → - `unroll.EnsureUnrollRequest{Trigger: TriggerCriticalExpiry}` and - hands it to `lazyChainResolver.Set`. -- `unrollMaxFeeRate` — `cfg.Unroll.MaxFeeRateSatPerVByte` if - positive, else zero (each downstream uses its own default). - -### Test Hooks (NOT for production) - -- `TriggerRoundRegistration` — injects an `IntentRequested` event - into the round actor; backs `JoinNextRound` RPC and the harness - registration hook. Uses `context.WithoutCancel` on `Ask` so the - caller's ctx doesn't propagate into the FSM's forfeit-VTXO lookup; - keeps original ctx on `Await`. -- `GetStoredVTXO` — harness-only accessor returning a persisted - `vtxo.Descriptor` for an outpoint directly from the VTXO store. -- `GetVTXOLineageTx` / `VTXOLineageEntry` — harness-only accessor - returning one lineage tx plus the outpoints of its parent txs. - Walked by recursing on each parent outpoint until - `OnChainRoot=true`. Implemented on top of the same - `unroll.LocalProofAssembler`, but routed through the terminal- - tolerant `EnsureProofForHarness` entry point so the lineage of an - already-spent / forfeited VTXO stays walkable. The field type - `harnessProofAssembler` is a 1-method local interface exposing - ONLY the terminal-tolerant entry point so production code paths - cannot reach `EnsureProof` through this seam. -- `NewWalletAddress` / `ListWalletUnspent` - (`wallet_testhooks.go`) — backend-agnostic harness helpers - returning a fresh P2TR address and the current confirmed UTXO set. + `authSigHex` (Schnorr auth), and a single `clk` (`clock.Clock`) shared by + all sub-stores for deterministic time injection. +- `RPCServer` — implements the gRPC `DaemonService`. Most write RPCs + (`Board`, `SendVTXO`, `SendOOR`, `SweepBoardingUTXOs`, `SendOnChain`) + validate input locally then `Ask` the relevant actor; `GetRound` and + `ListVTXOs` merge live actor state with persisted SQL rows, while + `GetFeeHistory` and `ListTransactions` are pure SQL reads + (`rpc_fees.go`). +- `Config` — daemon configuration: wallet backend selection, mailbox/chain + backend wiring, `OORConfig`/`OORLimitsConfig` (receive safety caps), + `UnrollConfig` (unilateral-exit fee-bump cadence and cap), and + `MaxOperatorFeeSat` (the #270 seal-time fee-cap validated in + `Config.Validate()`). +- `WalletState` — `None` / `Locked` / `Ready` wallet lifecycle. +- `UnrollConfig` / `OORConfig` — subsystem tunables; see `Config.Validate()` + for the invariants each enforces. ## Relationships - **Depends on**: `baselib/actor`, `btcwbackend`, `chainbackends`, `chainsource`, `lib/actormsg`, `db`, `ledger`, `round`, `txconfirm`, - `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, - `indexer`, `arkrpc`, `lndbackend`, `harness` (bitcoind package - submitter wiring in `cmd/darepod`), `fraud`, `gateway`, - `rpc/restclient`, `vhtlcrecovery`, `vhtlcrecovery/coordinator`, - `vhtlcrecovery/unrollpolicy`. + `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, `indexer`, + `arkrpc`, `lndbackend`, `fraud`, `gateway`, `rpc/restclient`, + `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`. - **Depended on by**: `cmd/darepod`. ## Invariants -- Server owns `ActorSystem` lifetime; `Server.run` registers a - deferred `actorSystem.Shutdown()` **before** the deferred - `db.Close()` so all actor DB transactions drain before the - connection pool tears down. Without this ordering, in-flight actor - lease loops produce "sql: database is closed" warnings at the tail - of every itest. -- Wallet transitions `None → Locked → Ready` (or direct to Ready if - seed provided). -- Three wallet modes: LND-backed, lightweight (`lwwallet`), or +- `Server.run` registers a deferred `actorSystem.Shutdown()` **before** the + deferred `db.Close()` so in-flight actor DB transactions drain before the + connection pool tears down. +- Wallet transitions `None → Locked → Ready` (or direct to `Ready` if a seed + is provided). Three wallet backends: LND, lightweight (`lwwallet`), or neutrino-backed (`btcwallet` via `btcwbackend`). -- Mailbox IDs are derived from identity pubkeys (via - `serverconn.PubKeyMailboxID`), not config strings. The operator's - remote mailbox ID is fetched via direct gRPC before the mailbox - runtime starts. -- Auth headers (Schnorr signature) are injected into all outbound - envelopes including response envelopes in `handleInboundRPC`. -- TLS client cert generation is skipped in insecure mode. -- Per-subsystem logging: configurable log writer, no global mutable - loggers. -- All sub-stores share the single `s.clk` clock assigned at - `NewServer`. **New code must not call `clock.NewDefaultClock()` in - `init*` methods** — use `s.clk`. -- `SendVTXO` enforces `maxRecipients = 256` (TODO #241), rejects - per-recipient amounts outside `(0, MaxSatoshi]`, uses - overflow-safe accumulation. Wallet-side `handleSendVTXOs` repeats - these checks as defense-in-depth. -- `SendOOR` with custom inputs serializes concurrent calls on the - same outpoints via `reserveCustomInputs`. Custom inputs lock for - the RPC lifetime; release is deferred on both success and failure. -- `BuildCustomTransferInputs` validates (a) the caller-supplied - policy template compiles to the provided pkScript - (`PolicyTemplate.MatchesPkScript`), and (b) the spend path's - control block commits to the same pkScript - (`SpendPath.VerifyBindsToPkScript`). Together these prevent a - caller from obtaining signatures for an unrelated tapscript by - claiming a different output's policy template. -- `ListRounds` splits pending (in-memory from actor) and persisted - (SQL with cursor pagination). -- Actor startup order: VTXO manager starts BEFORE round actor and - OOR actor so the manager ref is available for both. The round - actor ref in the VTXO manager is lazy (service-key-based, resolved - at Tell time). -- `mapRoundVTXOManagerMsg` bridges `round.VTXOManagerMsg` → - `vtxo.ManagerMsg` via `MapInputRef`. Compile-time assertions - enforce that all `round.VTXOManagerMsg` implementors satisfy - `vtxo.ManagerMsg`. -- OOR receive-key is derived once at startup via - `EnsureDefaultOORReceiveScript` and persisted for restart-safe - re-registration. The `DurableUnaryBuilder` is wired through - `serverconn.ConnectorConfig` so all indexer queries flow through - the durable transport. -- The OOR artifact store backs three round/vtxo abstractions - (`OwnedScriptChecker`, `OwnedScriptRegistrar`, `OwnedScriptLookup`). - One logical "owned receive scripts" table; all ownership questions - resolve through it. -- The incoming VTXO handler actor is registered under - `vtxo.IncomingVTXOServiceKey()` during `initOORActor`. Mailbox - route `MethodIncomingVTXO` decodes `arkrpc.IncomingVTXOEvent` push - notifications and dispatches them. -- Every producer actor (`wallet.NewArk`, `round.RoundClientConfig`, - `vtxo.ManagerConfig`, `oor.ClientActorCfg`) is wired with - `fn.Some(ledger.NewSink(s.actorSystem))`. `wallet.NewArk` takes - the sink as a required constructor argument so every call site - makes an explicit emission choice; test harnesses pass - `fn.None[ledger.Sink]()`. -- `EstimateFee` and `GetFeeHistory` route upstream errors through - `proxyUpstreamError` to preserve gRPC codes and strip operator-side - detail. `GetFeeHistory` validates request bounds locally (limit - positive, offset within `int32` range) before hitting the DB. -- In btcwallet mode, neutrino is pre-started before seed availability - so P2P sync proceeds in parallel. `neutrinoSvc` uses `fn.Option` - and is reused by `startBtcwallet` via `NewWithNeutrino`. -- The neutrino sync-wait goroutine polls indefinitely (no timeout) - with 30s progress logging — avoids leaving the wallet permanently - unready. -- `ensureRoundExists` in `db/vtxo_store.go` uses check-then-insert - (not upsert) because `InsertRound`'s `ON CONFLICT DO UPDATE` would - overwrite richer round state. -- **Unroll subsystem ordering**: wired strictly AFTER the VTXO - manager but BEFORE the OOR actor. The VTXO manager is created with - a `vtxo.LazyChainResolver` placeholder so VTXO actors spawned - during manager construction hold a stable ref; - `initUnrollSubsystem` later calls `lazyChainResolver.Set(...)`. - Any code that also needs this seam must run AFTER - `initUnrollSubsystem` or it will see an unset target. -- `initUnrollSubsystem` creates its own `dbStore` + `vtxoStore` to - decouple the unroll store lifecycle from the VTXO manager's; the - persisted `s.ueStore` is reused by the `GetUnrollStatus` fallback - so terminal jobs remain queryable after registry eviction. -- `Server.run` registers a deferred `s.unrollRegistry.Stop()` during - startup so the registry's durable persist writer drains before - actor-system shutdown. -- `registerOOREventRoutes` checks for `*oorpb.SubmitRejectedError` - before a generic error check on the submit-package response. A - typed server-side rejection (e.g. `OOR_REJECT_LINEAGE_TOO_LARGE`) - becomes an `oor.OutboxErrorEvent{Retryable: false}` rather than - surfacing as an Adapt error — prevents the serverconn ingress - dispatcher from stalling the cursor on a sticky rejection. -- `Unroll` and `GetUnrollStatus` return `codes.Unavailable` (not - `Internal`) when subsystem refs are not yet set, so clients can - retry rather than treating it as permanent failure. -- `SweepBoardingUTXOs` always persists the sweep record before - broadcasting; on broadcast failure the record is marked failed so - the watcher does not rebroadcast. Spend watcher is refreshed via - `getBoardingSweepWatcher().Refresh` (using - `context.WithoutCancel`) immediately after a successful broadcast. -- `boardingSweepWatcher` uses two cancellation scopes: `w.ctx` for - spend registration (watcher lifetime, survives CLI disconnect) and - the per-refresh `ctx` for rebroadcast RPCs. -- `OORConfig.OOR.Limits.MaxMailboxScriptBytes` must be at least - `minOORMailboxScriptBytes = 34` (P2TR script length); validated - during `Config.Validate()`. -- `Config.EagerRoundJoin` is seeded by build-tag-aware - `defaultEagerRoundJoin()`: `false` on the standalone non-walletdkrpc - build, `true` under the `walletdkrpc` tag (both `cmd/darepod` and - `sdk/walletdk` embedded paths). The `--eagerroundjoin` flag - inherits this default so viper precedence overrides it naturally - without `IsSet` probing. `sdk/walletdk` exposes the disable knob - via `WithEagerRoundJoinDisabled()`. +- Mailbox IDs are derived from identity pubkeys via + `serverconn.PubKeyMailboxID`, not config strings. The operator's remote + mailbox ID and pubkey are fetched via direct gRPC (`fetchCurrentOperatorPubKey`) + before the mailbox runtime starts. +- All sub-stores share the single `s.clk` clock assigned in `NewServer`; new + code must not call `clock.NewDefaultClock()` directly, use `s.clk`. +- Actor startup order in `startWalletDependentActors`: VTXO manager, then + round actor, then the unroll subsystem (`initUnrollSubsystem`), then the + OOR actor (`initOORActor`). The VTXO manager is constructed with a + `vtxo.LazyChainResolver` placeholder that `initUnrollSubsystem` fills in + later; anything needing that seam must run after `initUnrollSubsystem`. +- Boarding-sweep transaction construction, fee estimation, spend watching, + and startup resumption live inside the **wallet actor** + (`wallet.Ark.handleSweepBoardingUTXOs` / `handleResumeBoardingSweeps` in + `wallet/boarding_sweep_actor.go` and `wallet/boarding_sweep.go`), not in + darepod. `RPCServer.SweepBoardingUTXOs` only validates the request and + `Ask`s the wallet actor; darepod supplies the boarding store + (`newBoardingStore`) and the backend-specific sweep-wallet adapter + (`newSweepWallet`, one of `lndUnrollWallet` / `lwUnrollWallet` / + `btcwUnrollWallet`), which is structurally compatible with both + `unroll.SweepWallet` and the wallet actor's `SweepSigner`. +- `SendVTXO` enforces `maxRecipients = 256`, rejects per-recipient amounts + outside `(0, MaxSatoshi]`, and uses overflow-safe summation; the wallet + actor repeats these checks as defense-in-depth. +- `SendOOR` with custom inputs serializes concurrent calls on the same + outpoints via `reserveCustomInputs`; the release function is deferred on + both success and failure. +- `Unroll` / `GetUnrollStatus` return `codes.Unavailable` (not `Internal`) + when the unroll subsystem refs are not yet set, so clients can retry. +- `OORLimitsConfig.MaxMailboxScriptBytes` must be at least + `minOORMailboxScriptBytes = 34` (P2TR script length); validated in + `Config.Validate()`. +- `Config.EagerRoundJoin` defaults via `defaultEagerRoundJoin()`: `false` on + the standalone build, `true` under the `walletdkrpc` build tag. +- `registerOOREventRoutes` checks for a typed `*oorpb.SubmitRejectedError` + before the generic error path, so an OOR rejection drives a + non-retryable `OutboxErrorEvent` instead of an `Adapt` error that would + stall the serverconn ingress cursor on the offending envelope + (`server.go`). ## Deep Docs -- [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — - Installation, configuration, CLI reference. +- [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — Installation, + configuration, CLI reference. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/darepod/CLAUDE.md b/darepod/CLAUDE.md index 57f51ff42..15603127e 100644 --- a/darepod/CLAUDE.md +++ b/darepod/CLAUDE.md @@ -2,367 +2,92 @@ ## Purpose -Top-level daemon orchestrator that wires wallet backend, mailbox transport, -chain backend, database, and all domain actors into a running system with a -gRPC API. +Top-level daemon orchestrator that wires the wallet backend, mailbox +transport, chain backend, database, and all domain actors into a running +system with a gRPC API. ## Key Types For field-level detail, use `go doc github.com/lightninglabs/darepo-client/darepod.`. -### Server & Configuration - -- `Server` — main daemon. Owns wallet, DB, chainsource actor, gRPC +- `Server` — main daemon. Owns the wallet, DB, chainsource actor, gRPC server, and `ActorSystem`. Caches `localMailboxID` (pubkey-derived), - `authSigHex` (Schnorr auth) and a single `clk` (`clock.Clock`) that - all sub-stores share for deterministic time injection. -- `RPCServer` — implements gRPC `DaemonService`. Holds an in-memory - `customInputLocks` map (guarded by `customInputLocksMu`) that - reserves custom OOR input outpoints for the duration of a `SendOOR` - call. -- `Config` — daemon configuration. Notable fields: - `MailboxEdgeFactory` (test transport interception), - `PackageSubmitter chainbackends.PackageSubmitter` (v3 CPFP submitter - injected by the harness via `BitcoindPackageSubmitter` and by - `cmd/darepod` from `bitcoind.{host,user,pass}` flags; not - serialized), optional `Unroll *UnrollConfig`, and `MaxOperatorFeeSat - int64` cap fed into `ClientEnvironment.MaxOperatorFee`. Under the - #270 seal-time fee handshake every server-issued `JoinRoundQuote` - is compared against this cap; `Config.Validate()` fails closed when - the value is non-positive. CLI flag `--maxoperatorfeesat`; - `DefaultMaxOperatorFeeSat` is a generous default. -- `UnrollConfig` — `BumpAfterBlocks int32` (fee-bump cadence; zero → - default 6), `MaxFeeRateSatPerVByte int64` (cap fed to both - `txconfirm` and the unroll registry; zero → each subsystem's - default). Surfaced via `unrollMaxFeeRate()`. -- `OORConfig` / `OORLimitsConfig` — incoming receive safety caps - (`MaxCheckpoints`, `MaxVTXOMatches`, `MaxMailboxItems`, - `MaxMailboxScriptBytes`). `Config.OORReceiveLimits()` normalizes - into `oor.ReceiveLimits`. -- `WalletState` — `None` / `Locked` / `Ready`. - -### RPC Handlers - -- `Board` — non-blocking; delegates to wallet actor. -- `GetRound` / `ListRounds` — round operation status. Live rounds come - from the round actor; persisted rounds from SQL summaries. Live - rounds surface `commitment_txid` once the FSM reaches - `CommitmentTxReceived` (recovered from the state's `TxID` or the - embedded commitment PSBT for MuSig2 phases) plus the per-owner - expected VTXO amounts, filtered by `IsOwner=true` so other clients' - outputs don't inflate the local view (helper: `liveRoundDetails`). -- `GetOORSession` / `ListOORSessions` — pending and failed from OOR - actor `ListSessionsRequest`; completed from persisted artifacts. - Actor state is authoritative when both views exist. -- `SendVTXO` — in-round directed sends. Validates recipients (count - cap, positive and `MaxSatoshi`-bounded amounts, overflow-safe sum), - resolves destinations via `resolveRecipientOutput`, delegates to - the wallet actor. -- `resolveRecipientOutput` — extracts pkScript and client pubkey from - an `Output` proto oneof (pubkey or address). Taproot-only. -- `ListVTXOs` — paginated VTXO inventory. When called with - `VTXO_STATUS_PENDING_ROUND`, branches to `listPendingRoundVTXOs` - which bypasses `s.vtxoStore` and projects synthetic VTXOs (amount - + round id + commitment txid; outpoint deliberately empty) from - each live round FSM via `queryRoundStates`. Rounds already in - `ROUND_STATE_CONFIRMED` are skipped so the store-backed - `VTXO_STATUS_LIVE` rows don't double-report. -- `Unroll` / `GetUnrollStatus` — manual unilateral-exit RPCs. `Unroll` - short-circuits with `Created=false` when the VTXO is already in - `VTXOStatusUnilateralExit`; else asks the VTXO manager - `ForceUnrollRequest{Reason: "manual RPC request"}`. Uses - `manualUnrollAdmissionTimeout`-bounded context derived via - `context.WithoutCancel(ctx)` so CLI disconnect doesn't cancel the - daemon-local handoff. `GetUnrollStatus` is read-through: prefers - live registry via `queryUnrollRegistry`, falls back to - `db.UnilateralExitPersistenceStore.GetJob`. Returns `Found=false` - (not error) when neither layer has a record. -- `unrollPhaseToProto` / `unrollJobStatusToProto` — dual mappers from - live `unroll.Phase` and persisted `db.UnilateralExitJobStatus` to - the same proto. `PhaseSweepBroadcast` and `PhaseSweepConfirmation` - both project to `UNROLL_JOB_STATUS_SWEEPING`. -- `EstimateFee` / `GetFeeHistory` — operator fee surface. - `EstimateFee` proxies to the operator's `EstimateFee` over the - direct gRPC connection (`s.serverConn`); no local caching. - `GetFeeHistory` reads through - `s.ledgerStore.ListLedgerEntriesWithFeesTotal` for page + - cumulative-total consistency. -- `ListTransactions` — newest-first unified history from ledger + - sweep DBs. Accepts `type` filter, optional time range, `limit` - (cap 1000), `offset` (clamped to `math.MaxInt32`). Delegates to - `ledgerStore.ListTransactionHistory` and projects via - `transactionHistoryRowToProto`. -- `proxyUpstreamError(err, msg)` — gRPC-safety helper preserving - upstream codes while stripping operator-side text. Errors without a - status map to `codes.Unavailable`. -- `quoteOperatorFee` — internal helper asking the operator's - `ArkService.EstimateFee` via direct gRPC. Returns - `codes.Unavailable` when `serverConn` is nil (degraded mode) so - callers can distinguish transient from permanent. -- `SweepBoardingUTXOs` — sweeps CSV-mature boarding UTXOs back to the - wallet. Resolves candidates (explicit outpoints or all - confirmed/failed/expired intents), estimates fee, builds and signs - an aggregate tx via `buildBoardingSweepTx`, persists, broadcasts, - and wakes `boardingSweepWatcher`. Returns preview when - `broadcast=false`. -- `ListBoardingSweeps` — paginated persisted aggregate sweeps with - optional status filter and cursor-based pagination. -- `ArmVHTLCRecovery` — persists a dormant vHTLC on-chain recovery job (armed - state). The job remains dormant until `EscalateVHTLCRecovery` is called. - Idempotent on `request_id`. -- `EscalateVHTLCRecovery` — transitions an armed job into active unroll by - calling `coordinator.Service.EscalateRecovery`. Triggers - `TargetMaterializer.EnsureRecoveryTarget` before admitting the target to - the unroll registry. -- `CancelVHTLCRecovery` — marks a recovery job cancelled (cooperative - settlement or explicit operator action). -- `StatusVHTLCRecovery` — returns the current recovery row joined with live - unroll status for the target outpoint. -- `SendOnChain` — RPC handler delegating to the wallet actor's - `SendOnChainRequest`. Routes through coin selection, leave output - construction, and eager round join. Supports bounded and sweep-all modes. - -### Adapters & Helpers - -- `serverDurableUnaryBuilder` — implements - `serverconn.DurableUnaryRequestBuilder` via the indexer client with - proof-of-control credentials. -- `IndexerProofKey` — derives the fixed wallet key for a given key - locator; returns an `indexer.SchnorrSigner` backed by the proof-key - backend. -- `NewOwnedReceiveScriptSigner` — indexer signer that resolves the - wallet key for any persisted owned receive script, then delegates - to the backend-specific signer. -- `ownedScriptCheckerAdapter` / `ownedScriptRegistrarAdapter` / - `ownedScriptLookupAdapter` — wrap `db.OORArtifactPersistenceStore` - to satisfy `round.OwnedScriptChecker` / - `round.OwnedScriptRegistrar` / `vtxo.OwnedScriptLookup`. The - checker uses `context.WithoutCancel` so confirmation-time ownership - survives FSM shutdown; returns `false` on `sql.ErrNoRows`. The - registrar persists pkScripts as `OwnedReceiveScriptSourceWallet` - with the operator pubkey and VTXO exit delay from `OperatorTerms`. -- `EnsureDefaultOORReceiveScript` / `CreateOORReceiveScript` — - receive-key lifecycle: derive, register with indexer - (proof-of-control), persist ownership record. -- `ResolveIncomingMetadataFromIndexer` — resolves authoritative VTXO - lineage metadata from `ListVTXOsByScripts`. -- Ancestry conversion lives in - [`vtxo.AncestryFromRPC`](../vtxo/incoming_ancestry.go); the - darepod-local copy that previously lived here was deleted when the - OOR and in-round receive paths both converged on the shared `vtxo` - helper. `vtxo.MaxAncestryPaths = 64` is the shared cap. -- `lndUnrollWallet` / `lwUnrollWallet` / `btcwUnrollWallet` — - backend-specific adapters satisfying both `txconfirm.Wallet` - (`ListUnspent`/`NewWalletPkScript`/`FinalizePsbt`/`LeaseOutput`/ - `ReleaseOutput`) and `unroll.SweepWallet`. LND forwards to the - `BoardingBackend`; lwwallet/btcwallet paths reach into `BtcWallet` - directly, reinterpreting `wallet.LockID` as `wtxmgr.LockID` via - direct `[32]byte` cast so leases round-trip across restart. -- `reserveCustomInputs` (on `RPCServer`) — atomically claims every - custom OOR outpoint for a `SendOOR` call. Returns a release - function (typically deferred). -- `autoRefreshFeeQuoter` — wires `vtxo.RefreshFeeQuoter` into every - VTXO actor. Advisory under #270: the closure's return value - populates `RefreshVTXORequest.OperatorFee` for observability but - is not written to the intent. Falls back to - `terms.MinOperatorFee` when unreachable. -- `boardingSweepWatcher` — daemon-owned background watcher: resumes - pending sweeps on startup, rate-limited rebroadcasts, registers - spend notifications per input, marks inputs spent on confirmation. - Started by `startBoardingSweepWatcher` on wallet unlock; - idempotent. -- `vhtlcRecoveryTargetMaterializer` — darepod adapter implementing - `coordinator.TargetMaterializer`. Binds vHTLC recovery rows to local OOR - packages and VTXO descriptors so the generic unroll subsystem can assemble - lineage and watch the target without swap-specific knowledge. -- `boardingSweepTx` / `buildBoardingSweepTx` — constructs and signs - one aggregate timeout-path sweep tx. Iterates the weight estimate - up to three times until `SerializeSize` converges so `fee`/`txid` - are accurate. Validates `defaultBoardingSweepMaxFeePercent = 25%` - and `defaultBoardingSweepMaxInputs = 100`. -- `deriveIdentityKeyEarly` — derives the client's secp256k1 identity - key from LND or lwwallet before mailbox transport starts. -- `signMailboxAuth` — Schnorr auth. LND uses the tagged Schnorr - signing RPC (`withSchnorrTag`); lwwallet signs locally via - `serverconn.SignMailboxAuth`. -- `fetchOperatorPubKeyDirect` — fetches operator pubkey via direct - gRPC `GetInfo` before the mailbox runtime starts. -- `initLedgerActor` — constructs `ledger.LedgerActor` with both - `LedgerStoreDB` and `UTXOAuditStoreDB`, registers under - `ledger.ServiceKeyName`, stashes `LedgerStoreDB` on the `Server` - for RPC reads. Called after DB ready but before wallet unlock. -- `initUnrollSubsystem` — wires the unilateral-exit runtime during - `startWalletDependentActors` (step 12, before `initOORActor`). - Builds a backend adapter, registers the shared `TxBroadcasterActor` - under `"txconfirm"`, constructs `UnrollRegistryActor` with the - persistence store, `LocalProofAssembler`, shared `txConfirmRef`, - and wallet, then calls `RestoreNonTerminal(ctx)`. Builds a - `MapInputRef` translating `vtxo.ExpiringNotification` → - `unroll.EnsureUnrollRequest{Trigger: TriggerCriticalExpiry}` and - hands it to `lazyChainResolver.Set`. -- `unrollMaxFeeRate` — `cfg.Unroll.MaxFeeRateSatPerVByte` if - positive, else zero (each downstream uses its own default). - -### Test Hooks (NOT for production) - -- `TriggerRoundRegistration` — injects an `IntentRequested` event - into the round actor; backs `JoinNextRound` RPC and the harness - registration hook. Uses `context.WithoutCancel` on `Ask` so the - caller's ctx doesn't propagate into the FSM's forfeit-VTXO lookup; - keeps original ctx on `Await`. -- `GetStoredVTXO` — harness-only accessor returning a persisted - `vtxo.Descriptor` for an outpoint directly from the VTXO store. -- `GetVTXOLineageTx` / `VTXOLineageEntry` — harness-only accessor - returning one lineage tx plus the outpoints of its parent txs. - Walked by recursing on each parent outpoint until - `OnChainRoot=true`. Implemented on top of the same - `unroll.LocalProofAssembler`, but routed through the terminal- - tolerant `EnsureProofForHarness` entry point so the lineage of an - already-spent / forfeited VTXO stays walkable. The field type - `harnessProofAssembler` is a 1-method local interface exposing - ONLY the terminal-tolerant entry point so production code paths - cannot reach `EnsureProof` through this seam. -- `NewWalletAddress` / `ListWalletUnspent` - (`wallet_testhooks.go`) — backend-agnostic harness helpers - returning a fresh P2TR address and the current confirmed UTXO set. + `authSigHex` (Schnorr auth), and a single `clk` (`clock.Clock`) shared by + all sub-stores for deterministic time injection. +- `RPCServer` — implements the gRPC `DaemonService`. Most write RPCs + (`Board`, `SendVTXO`, `SendOOR`, `SweepBoardingUTXOs`, `SendOnChain`) + validate input locally then `Ask` the relevant actor; `GetRound` and + `ListVTXOs` merge live actor state with persisted SQL rows, while + `GetFeeHistory` and `ListTransactions` are pure SQL reads + (`rpc_fees.go`). +- `Config` — daemon configuration: wallet backend selection, mailbox/chain + backend wiring, `OORConfig`/`OORLimitsConfig` (receive safety caps), + `UnrollConfig` (unilateral-exit fee-bump cadence and cap), and + `MaxOperatorFeeSat` (the #270 seal-time fee-cap validated in + `Config.Validate()`). +- `WalletState` — `None` / `Locked` / `Ready` wallet lifecycle. +- `UnrollConfig` / `OORConfig` — subsystem tunables; see `Config.Validate()` + for the invariants each enforces. ## Relationships - **Depends on**: `baselib/actor`, `btcwbackend`, `chainbackends`, `chainsource`, `lib/actormsg`, `db`, `ledger`, `round`, `txconfirm`, - `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, - `indexer`, `arkrpc`, `lndbackend`, `harness` (bitcoind package - submitter wiring in `cmd/darepod`), `fraud`, `gateway`, - `rpc/restclient`, `vhtlcrecovery`, `vhtlcrecovery/coordinator`, - `vhtlcrecovery/unrollpolicy`. + `unroll`, `vtxo`, `wallet`, `walletcore`, `oor`, `serverconn`, `indexer`, + `arkrpc`, `lndbackend`, `fraud`, `gateway`, `rpc/restclient`, + `vhtlcrecovery`, `vhtlcrecovery/coordinator`, `vhtlcrecovery/unrollpolicy`. - **Depended on by**: `cmd/darepod`. ## Invariants -- Server owns `ActorSystem` lifetime; `Server.run` registers a - deferred `actorSystem.Shutdown()` **before** the deferred - `db.Close()` so all actor DB transactions drain before the - connection pool tears down. Without this ordering, in-flight actor - lease loops produce "sql: database is closed" warnings at the tail - of every itest. -- Wallet transitions `None → Locked → Ready` (or direct to Ready if - seed provided). -- Three wallet modes: LND-backed, lightweight (`lwwallet`), or +- `Server.run` registers a deferred `actorSystem.Shutdown()` **before** the + deferred `db.Close()` so in-flight actor DB transactions drain before the + connection pool tears down. +- Wallet transitions `None → Locked → Ready` (or direct to `Ready` if a seed + is provided). Three wallet backends: LND, lightweight (`lwwallet`), or neutrino-backed (`btcwallet` via `btcwbackend`). -- Mailbox IDs are derived from identity pubkeys (via - `serverconn.PubKeyMailboxID`), not config strings. The operator's - remote mailbox ID is fetched via direct gRPC before the mailbox - runtime starts. -- Auth headers (Schnorr signature) are injected into all outbound - envelopes including response envelopes in `handleInboundRPC`. -- TLS client cert generation is skipped in insecure mode. -- Per-subsystem logging: configurable log writer, no global mutable - loggers. -- All sub-stores share the single `s.clk` clock assigned at - `NewServer`. **New code must not call `clock.NewDefaultClock()` in - `init*` methods** — use `s.clk`. -- `SendVTXO` enforces `maxRecipients = 256` (TODO #241), rejects - per-recipient amounts outside `(0, MaxSatoshi]`, uses - overflow-safe accumulation. Wallet-side `handleSendVTXOs` repeats - these checks as defense-in-depth. -- `SendOOR` with custom inputs serializes concurrent calls on the - same outpoints via `reserveCustomInputs`. Custom inputs lock for - the RPC lifetime; release is deferred on both success and failure. -- `BuildCustomTransferInputs` validates (a) the caller-supplied - policy template compiles to the provided pkScript - (`PolicyTemplate.MatchesPkScript`), and (b) the spend path's - control block commits to the same pkScript - (`SpendPath.VerifyBindsToPkScript`). Together these prevent a - caller from obtaining signatures for an unrelated tapscript by - claiming a different output's policy template. -- `ListRounds` splits pending (in-memory from actor) and persisted - (SQL with cursor pagination). -- Actor startup order: VTXO manager starts BEFORE round actor and - OOR actor so the manager ref is available for both. The round - actor ref in the VTXO manager is lazy (service-key-based, resolved - at Tell time). -- `mapRoundVTXOManagerMsg` bridges `round.VTXOManagerMsg` → - `vtxo.ManagerMsg` via `MapInputRef`. Compile-time assertions - enforce that all `round.VTXOManagerMsg` implementors satisfy - `vtxo.ManagerMsg`. -- OOR receive-key is derived once at startup via - `EnsureDefaultOORReceiveScript` and persisted for restart-safe - re-registration. The `DurableUnaryBuilder` is wired through - `serverconn.ConnectorConfig` so all indexer queries flow through - the durable transport. -- The OOR artifact store backs three round/vtxo abstractions - (`OwnedScriptChecker`, `OwnedScriptRegistrar`, `OwnedScriptLookup`). - One logical "owned receive scripts" table; all ownership questions - resolve through it. -- The incoming VTXO handler actor is registered under - `vtxo.IncomingVTXOServiceKey()` during `initOORActor`. Mailbox - route `MethodIncomingVTXO` decodes `arkrpc.IncomingVTXOEvent` push - notifications and dispatches them. -- Every producer actor (`wallet.NewArk`, `round.RoundClientConfig`, - `vtxo.ManagerConfig`, `oor.ClientActorCfg`) is wired with - `fn.Some(ledger.NewSink(s.actorSystem))`. `wallet.NewArk` takes - the sink as a required constructor argument so every call site - makes an explicit emission choice; test harnesses pass - `fn.None[ledger.Sink]()`. -- `EstimateFee` and `GetFeeHistory` route upstream errors through - `proxyUpstreamError` to preserve gRPC codes and strip operator-side - detail. `GetFeeHistory` validates request bounds locally (limit - positive, offset within `int32` range) before hitting the DB. -- In btcwallet mode, neutrino is pre-started before seed availability - so P2P sync proceeds in parallel. `neutrinoSvc` uses `fn.Option` - and is reused by `startBtcwallet` via `NewWithNeutrino`. -- The neutrino sync-wait goroutine polls indefinitely (no timeout) - with 30s progress logging — avoids leaving the wallet permanently - unready. -- `ensureRoundExists` in `db/vtxo_store.go` uses check-then-insert - (not upsert) because `InsertRound`'s `ON CONFLICT DO UPDATE` would - overwrite richer round state. -- **Unroll subsystem ordering**: wired strictly AFTER the VTXO - manager but BEFORE the OOR actor. The VTXO manager is created with - a `vtxo.LazyChainResolver` placeholder so VTXO actors spawned - during manager construction hold a stable ref; - `initUnrollSubsystem` later calls `lazyChainResolver.Set(...)`. - Any code that also needs this seam must run AFTER - `initUnrollSubsystem` or it will see an unset target. -- `initUnrollSubsystem` creates its own `dbStore` + `vtxoStore` to - decouple the unroll store lifecycle from the VTXO manager's; the - persisted `s.ueStore` is reused by the `GetUnrollStatus` fallback - so terminal jobs remain queryable after registry eviction. -- `Server.run` registers a deferred `s.unrollRegistry.Stop()` during - startup so the registry's durable persist writer drains before - actor-system shutdown. -- `registerOOREventRoutes` checks for `*oorpb.SubmitRejectedError` - before a generic error check on the submit-package response. A - typed server-side rejection (e.g. `OOR_REJECT_LINEAGE_TOO_LARGE`) - becomes an `oor.OutboxErrorEvent{Retryable: false}` rather than - surfacing as an Adapt error — prevents the serverconn ingress - dispatcher from stalling the cursor on a sticky rejection. -- `Unroll` and `GetUnrollStatus` return `codes.Unavailable` (not - `Internal`) when subsystem refs are not yet set, so clients can - retry rather than treating it as permanent failure. -- `SweepBoardingUTXOs` always persists the sweep record before - broadcasting; on broadcast failure the record is marked failed so - the watcher does not rebroadcast. Spend watcher is refreshed via - `getBoardingSweepWatcher().Refresh` (using - `context.WithoutCancel`) immediately after a successful broadcast. -- `boardingSweepWatcher` uses two cancellation scopes: `w.ctx` for - spend registration (watcher lifetime, survives CLI disconnect) and - the per-refresh `ctx` for rebroadcast RPCs. -- `OORConfig.OOR.Limits.MaxMailboxScriptBytes` must be at least - `minOORMailboxScriptBytes = 34` (P2TR script length); validated - during `Config.Validate()`. -- `Config.EagerRoundJoin` is seeded by build-tag-aware - `defaultEagerRoundJoin()`: `false` on the standalone non-walletdkrpc - build, `true` under the `walletdkrpc` tag (both `cmd/darepod` and - `sdk/walletdk` embedded paths). The `--eagerroundjoin` flag - inherits this default so viper precedence overrides it naturally - without `IsSet` probing. `sdk/walletdk` exposes the disable knob - via `WithEagerRoundJoinDisabled()`. +- Mailbox IDs are derived from identity pubkeys via + `serverconn.PubKeyMailboxID`, not config strings. The operator's remote + mailbox ID and pubkey are fetched via direct gRPC (`fetchCurrentOperatorPubKey`) + before the mailbox runtime starts. +- All sub-stores share the single `s.clk` clock assigned in `NewServer`; new + code must not call `clock.NewDefaultClock()` directly, use `s.clk`. +- Actor startup order in `startWalletDependentActors`: VTXO manager, then + round actor, then the unroll subsystem (`initUnrollSubsystem`), then the + OOR actor (`initOORActor`). The VTXO manager is constructed with a + `vtxo.LazyChainResolver` placeholder that `initUnrollSubsystem` fills in + later; anything needing that seam must run after `initUnrollSubsystem`. +- Boarding-sweep transaction construction, fee estimation, spend watching, + and startup resumption live inside the **wallet actor** + (`wallet.Ark.handleSweepBoardingUTXOs` / `handleResumeBoardingSweeps` in + `wallet/boarding_sweep_actor.go` and `wallet/boarding_sweep.go`), not in + darepod. `RPCServer.SweepBoardingUTXOs` only validates the request and + `Ask`s the wallet actor; darepod supplies the boarding store + (`newBoardingStore`) and the backend-specific sweep-wallet adapter + (`newSweepWallet`, one of `lndUnrollWallet` / `lwUnrollWallet` / + `btcwUnrollWallet`), which is structurally compatible with both + `unroll.SweepWallet` and the wallet actor's `SweepSigner`. +- `SendVTXO` enforces `maxRecipients = 256`, rejects per-recipient amounts + outside `(0, MaxSatoshi]`, and uses overflow-safe summation; the wallet + actor repeats these checks as defense-in-depth. +- `SendOOR` with custom inputs serializes concurrent calls on the same + outpoints via `reserveCustomInputs`; the release function is deferred on + both success and failure. +- `Unroll` / `GetUnrollStatus` return `codes.Unavailable` (not `Internal`) + when the unroll subsystem refs are not yet set, so clients can retry. +- `OORLimitsConfig.MaxMailboxScriptBytes` must be at least + `minOORMailboxScriptBytes = 34` (P2TR script length); validated in + `Config.Validate()`. +- `Config.EagerRoundJoin` defaults via `defaultEagerRoundJoin()`: `false` on + the standalone build, `true` under the `walletdkrpc` build tag. +- `registerOOREventRoutes` checks for a typed `*oorpb.SubmitRejectedError` + before the generic error path, so an OOR rejection drives a + non-retryable `OutboxErrorEvent` instead of an `Adapt` error that would + stall the serverconn ingress cursor on the offending envelope + (`server.go`). ## Deep Docs -- [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — - Installation, configuration, CLI reference. +- [docs/daemon_cli_guide.md](../docs/daemon_cli_guide.md) — Installation, + configuration, CLI reference. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/db/AGENTS.md b/db/AGENTS.md index 3cf684ded..890238f8c 100644 --- a/db/AGENTS.md +++ b/db/AGENTS.md @@ -71,7 +71,7 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db. - diff --git a/db/CLAUDE.md b/db/CLAUDE.md index 3cf684ded..890238f8c 100644 --- a/db/CLAUDE.md +++ b/db/CLAUDE.md @@ -71,7 +71,7 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/db. - diff --git a/db/actordelivery/AGENTS.md b/db/actordelivery/AGENTS.md index fc0717b42..b09a71f75 100644 --- a/db/actordelivery/AGENTS.md +++ b/db/actordelivery/AGENTS.md @@ -18,6 +18,10 @@ other services can reuse durable actor storage without pulling unrelated tables. same-process callback fired after each `EnqueueOutbox` commit, allowing outbox publishers to wake immediately rather than waiting for the next poll tick. Multiple wakes can be registered; each is called after every enqueue. + `RegisterMailboxWake(mailboxID, wake func())` registers a targeted, + per-mailbox wake: `ExecTx` tracks which mailbox IDs actually received an + enqueue inside the transaction and, on commit, fires only those consumers' + callbacks instead of broadcasting to every registered mailbox. - `TxActorDeliveryStore` — Transaction-scoped delivery store wrapping a live `*sql.Tx`. Implements `actor.DeliveryStore` directly against the transaction without additional `ExecTx` wrapping. `EnqueueOutbox` sets a shared diff --git a/db/actordelivery/CLAUDE.md b/db/actordelivery/CLAUDE.md index fc0717b42..b09a71f75 100644 --- a/db/actordelivery/CLAUDE.md +++ b/db/actordelivery/CLAUDE.md @@ -18,6 +18,10 @@ other services can reuse durable actor storage without pulling unrelated tables. same-process callback fired after each `EnqueueOutbox` commit, allowing outbox publishers to wake immediately rather than waiting for the next poll tick. Multiple wakes can be registered; each is called after every enqueue. + `RegisterMailboxWake(mailboxID, wake func())` registers a targeted, + per-mailbox wake: `ExecTx` tracks which mailbox IDs actually received an + enqueue inside the transaction and, on commit, fires only those consumers' + callbacks instead of broadcasting to every registered mailbox. - `TxActorDeliveryStore` — Transaction-scoped delivery store wrapping a live `*sql.Tx`. Implements `actor.DeliveryStore` directly against the transaction without additional `ExecTx` wrapping. `EnqueueOutbox` sets a shared diff --git a/db/actordelivery/migrations/AGENTS.md b/db/actordelivery/migrations/AGENTS.md index cd3206190..6d71898b8 100644 --- a/db/actordelivery/migrations/AGENTS.md +++ b/db/actordelivery/migrations/AGENTS.md @@ -13,10 +13,11 @@ generic `db/migrate` orchestration layer. `"actor_delivery_schema_migrations"`), `DatabaseName` (default `"actor_delivery"`), `LatestVersion` (downgrade guard, default `LatestMigrationVersion`), optional `Log btclog.Logger`. -- `LatestMigrationVersion = 2` — Current schema version; bump when adding a - new SQL migration file. Version 2 adds the nullable `correlation_key` - column on `mailbox_messages` and the filtered composite index that backs - the per-correlation-key FIFO anti-join in `LeaseNextMailboxMessage`. +- `LatestMigrationVersion = 1` — Current schema version; bump when adding a + new SQL migration file. The single `000001_durable_mailbox` migration + already includes the nullable `correlation_key` column on + `mailbox_messages` and the filtered composite index that backs the + per-correlation-key FIFO anti-join in `LeaseNextMailboxMessage`. - `RunMigrations(db, backend, cfg)` — Applies actor-delivery migrations. Validates inputs, applies `Config` defaults, delegates to `dbmigrate.RunMigrations` with postgres schema token replacements. @@ -25,8 +26,9 @@ generic `db/migrate` orchestration layer. - **Depends on**: `db/migrate` (generic migration orchestration), `db/sqlc` (BackendType enum for driver selection). -- **Depended on by**: `db/actordelivery` (calls `RunMigrations` during - `actordelivery.Open`). +- **Depended on by**: `db/actordelivery` (its own `RunMigrations` wraps this + package's), `db` (`db/sqlite.go` and `db/postgres.go` call this package's + `RunMigrations` directly alongside the main-schema migration run). ## Invariants diff --git a/db/actordelivery/migrations/CLAUDE.md b/db/actordelivery/migrations/CLAUDE.md index cd3206190..6d71898b8 100644 --- a/db/actordelivery/migrations/CLAUDE.md +++ b/db/actordelivery/migrations/CLAUDE.md @@ -13,10 +13,11 @@ generic `db/migrate` orchestration layer. `"actor_delivery_schema_migrations"`), `DatabaseName` (default `"actor_delivery"`), `LatestVersion` (downgrade guard, default `LatestMigrationVersion`), optional `Log btclog.Logger`. -- `LatestMigrationVersion = 2` — Current schema version; bump when adding a - new SQL migration file. Version 2 adds the nullable `correlation_key` - column on `mailbox_messages` and the filtered composite index that backs - the per-correlation-key FIFO anti-join in `LeaseNextMailboxMessage`. +- `LatestMigrationVersion = 1` — Current schema version; bump when adding a + new SQL migration file. The single `000001_durable_mailbox` migration + already includes the nullable `correlation_key` column on + `mailbox_messages` and the filtered composite index that backs the + per-correlation-key FIFO anti-join in `LeaseNextMailboxMessage`. - `RunMigrations(db, backend, cfg)` — Applies actor-delivery migrations. Validates inputs, applies `Config` defaults, delegates to `dbmigrate.RunMigrations` with postgres schema token replacements. @@ -25,8 +26,9 @@ generic `db/migrate` orchestration layer. - **Depends on**: `db/migrate` (generic migration orchestration), `db/sqlc` (BackendType enum for driver selection). -- **Depended on by**: `db/actordelivery` (calls `RunMigrations` during - `actordelivery.Open`). +- **Depended on by**: `db/actordelivery` (its own `RunMigrations` wraps this + package's), `db` (`db/sqlite.go` and `db/postgres.go` call this package's + `RunMigrations` directly alongside the main-schema migration run). ## Invariants diff --git a/db/migrate/AGENTS.md b/db/migrate/AGENTS.md index 3f22e3b6c..839f9cd18 100644 --- a/db/migrate/AGENTS.md +++ b/db/migrate/AGENTS.md @@ -5,18 +5,22 @@ Generic database migration orchestration for SQLite and PostgreSQL backends. Wraps `golang-migrate` with downgrade protection, per-step callbacks, an on-the-fly SQLite→Postgres token replacer, and structured logging. Used by -both the main schema (`db/`) and the actor-delivery sub-schema -(`db/actordelivery/migrations/`). +the main schema (`db/`), the actor-delivery sub-schema +(`db/actordelivery/migrations/`), and `sdk/swaps`. The migration driver is +build-tagged: native builds (`driver_native.go`) use golang-migrate's +sqlite/postgres drivers, js/wasm builds (`driver_wasm.go`, +`sqlite_wasm_driver.go`) use a hand-rolled `wasmSQLiteDriver` to avoid +pulling the modernc sqlite driver into the browser bundle. ## Key Types -- `Target` — Function signature for migration strategies, e.g. - `TargetLatest` or `TargetVersion(n)`. -- `TargetLatest` — Predefined strategy calling `mig.Up()` (apply all pending +- `Target` — Function signature for migration strategies; `TargetLatest` is + the only predefined strategy, calling `mig.Up()` (apply all pending migrations). - `Config` — Migration control: `MigrationsTable`, `DatabaseName`, - `LatestVersion` (downgrade guard), `PostStepCallbacks map[uint]func()` - (called after each step number), `PostgresReplacements map[string]string` + `LatestVersion` (downgrade guard), `PostStepCallbacks + map[uint]golangmigrate.PostStepCallback` (Go callback run after the + matching SQL step applies), `PostgresReplacements map[string]string` (SQLite→Postgres token map), optional `Log btclog.Logger`. - `RunMigrations(db, backend, sourceFS, sourcePath, target, cfg)` — Top-level entry point. Builds the driver, wraps the `fs.FS` with `replacerFS` if @@ -39,7 +43,7 @@ both the main schema (`db/`) and the actor-delivery sub-schema - **Depends on**: `db/sqlc` (BackendType enum for driver selection), `github.com/golang-migrate/migrate/v4`. - **Depended on by**: `db` (main schema runner), `db/actordelivery/migrations` - (actor-delivery schema runner). + (actor-delivery schema runner), `sdk/swaps` (store migrations). ## Invariants diff --git a/db/migrate/CLAUDE.md b/db/migrate/CLAUDE.md index 3f22e3b6c..839f9cd18 100644 --- a/db/migrate/CLAUDE.md +++ b/db/migrate/CLAUDE.md @@ -5,18 +5,22 @@ Generic database migration orchestration for SQLite and PostgreSQL backends. Wraps `golang-migrate` with downgrade protection, per-step callbacks, an on-the-fly SQLite→Postgres token replacer, and structured logging. Used by -both the main schema (`db/`) and the actor-delivery sub-schema -(`db/actordelivery/migrations/`). +the main schema (`db/`), the actor-delivery sub-schema +(`db/actordelivery/migrations/`), and `sdk/swaps`. The migration driver is +build-tagged: native builds (`driver_native.go`) use golang-migrate's +sqlite/postgres drivers, js/wasm builds (`driver_wasm.go`, +`sqlite_wasm_driver.go`) use a hand-rolled `wasmSQLiteDriver` to avoid +pulling the modernc sqlite driver into the browser bundle. ## Key Types -- `Target` — Function signature for migration strategies, e.g. - `TargetLatest` or `TargetVersion(n)`. -- `TargetLatest` — Predefined strategy calling `mig.Up()` (apply all pending +- `Target` — Function signature for migration strategies; `TargetLatest` is + the only predefined strategy, calling `mig.Up()` (apply all pending migrations). - `Config` — Migration control: `MigrationsTable`, `DatabaseName`, - `LatestVersion` (downgrade guard), `PostStepCallbacks map[uint]func()` - (called after each step number), `PostgresReplacements map[string]string` + `LatestVersion` (downgrade guard), `PostStepCallbacks + map[uint]golangmigrate.PostStepCallback` (Go callback run after the + matching SQL step applies), `PostgresReplacements map[string]string` (SQLite→Postgres token map), optional `Log btclog.Logger`. - `RunMigrations(db, backend, sourceFS, sourcePath, target, cfg)` — Top-level entry point. Builds the driver, wraps the `fs.FS` with `replacerFS` if @@ -39,7 +43,7 @@ both the main schema (`db/`) and the actor-delivery sub-schema - **Depends on**: `db/sqlc` (BackendType enum for driver selection), `github.com/golang-migrate/migrate/v4`. - **Depended on by**: `db` (main schema runner), `db/actordelivery/migrations` - (actor-delivery schema runner). + (actor-delivery schema runner), `sdk/swaps` (store migrations). ## Invariants diff --git a/gateway/AGENTS.md b/gateway/AGENTS.md index dfa69e647..4017fbbf9 100644 --- a/gateway/AGENTS.md +++ b/gateway/AGENTS.md @@ -16,24 +16,27 @@ have a consistent configuration across all daemon sub-services. CORS middleware. Requests without an `Origin` header pass through unconditionally (non-browser clients are not restricted); requests carrying an `Origin` are validated against the allowlist and rejected - with 403 when not present. Injects CORS headers and allows GET, POST, - and OPTIONS methods. + with 403 when not present, unless `allowedOrigins` contains the + wildcard `"*"`. Injects CORS headers and allows GET, POST, and OPTIONS + methods. - `NormalizeEndpoint(endpoint) string` — Converts listener addresses for loopback dialing: `0.0.0.0` → `127.0.0.1`, `[::]` → `[::1]`, others pass through unchanged. ## Relationships -- **Depends on**: `google.golang.org/grpc`, `github.com/grpc-ecosystem/grpc-gateway/v2` - (no repo packages). +- **Depends on**: `github.com/grpc-ecosystem/grpc-gateway/v2/runtime`, + `google.golang.org/protobuf/encoding/protojson` (no repo packages). - **Depended on by**: `darepod` (HTTP gateway setup for all sub-services). ## Invariants - Browser callers (those sending an `Origin` header) need an entry in - `allowedOrigins` or the request is rejected with 403. An empty - allowlist means no browser caller can reach the gateway; non-browser - clients without an `Origin` header are unaffected. + `allowedOrigins` or the request is rejected with 403, unless the + allowlist contains `"*"` (allow-all, only fit for APIs with explicit + per-request auth). An empty allowlist means no browser caller can + reach the gateway; non-browser clients without an `Origin` header are + unaffected. - JSON marshaling always uses `UseProtoNames` (snake_case field names) and `EmitUnpopulated` (include zero/default values) for API consistency with grpc-gateway clients. diff --git a/gateway/CLAUDE.md b/gateway/CLAUDE.md index dfa69e647..4017fbbf9 100644 --- a/gateway/CLAUDE.md +++ b/gateway/CLAUDE.md @@ -16,24 +16,27 @@ have a consistent configuration across all daemon sub-services. CORS middleware. Requests without an `Origin` header pass through unconditionally (non-browser clients are not restricted); requests carrying an `Origin` are validated against the allowlist and rejected - with 403 when not present. Injects CORS headers and allows GET, POST, - and OPTIONS methods. + with 403 when not present, unless `allowedOrigins` contains the + wildcard `"*"`. Injects CORS headers and allows GET, POST, and OPTIONS + methods. - `NormalizeEndpoint(endpoint) string` — Converts listener addresses for loopback dialing: `0.0.0.0` → `127.0.0.1`, `[::]` → `[::1]`, others pass through unchanged. ## Relationships -- **Depends on**: `google.golang.org/grpc`, `github.com/grpc-ecosystem/grpc-gateway/v2` - (no repo packages). +- **Depends on**: `github.com/grpc-ecosystem/grpc-gateway/v2/runtime`, + `google.golang.org/protobuf/encoding/protojson` (no repo packages). - **Depended on by**: `darepod` (HTTP gateway setup for all sub-services). ## Invariants - Browser callers (those sending an `Origin` header) need an entry in - `allowedOrigins` or the request is rejected with 403. An empty - allowlist means no browser caller can reach the gateway; non-browser - clients without an `Origin` header are unaffected. + `allowedOrigins` or the request is rejected with 403, unless the + allowlist contains `"*"` (allow-all, only fit for APIs with explicit + per-request auth). An empty allowlist means no browser caller can + reach the gateway; non-browser clients without an `Origin` header are + unaffected. - JSON marshaling always uses `UseProtoNames` (snake_case field names) and `EmitUnpopulated` (include zero/default values) for API consistency with grpc-gateway clients. diff --git a/harness/AGENTS.md b/harness/AGENTS.md index 59179e972..2ac7ee2f9 100644 --- a/harness/AGENTS.md +++ b/harness/AGENTS.md @@ -2,47 +2,55 @@ ## Purpose -Docker-based Bitcoin/LND integration test environment. Manages bitcoind and LND -containers with network isolation for end-to-end testing. +Docker-based regtest integration-test harness. Spins up bitcoind, electrs +(Esplora HTTP), optional postgres, and one or more LND containers with +per-run network isolation, artifact/log capture, and mining/funding/reorg +helpers for end-to-end tests. ## Key Types -- `Harness` — Top-level test harness owning bitcoind, lnd, and arkd lifecycle. -- `LndInstance` — Manages an LND container's lifecycle and connection. -- `TapdHarness` — Optional Tapd instance for asset-related tests. -- `Options` — Configuration struct passed to `NewHarness`. Controls image - tags, artifact directory, log routing, tapd toggle, `GroupName`, and - `AlwaysKeepArtifacts`. -- `DefaultOptions()` — Returns a populated `Options` with safe defaults. -- `Block` — Mined block header plus txid list; used by mining helpers. -- `BlockHeader` — Verbose bitcoind `getblockheader` RPC representation. -- `ReorgResult` — Describes the branches produced by a reorg: `OldTip`, - `ForkPoint`, `Disconnected` (old-chain blocks in height order), and - `Connected` (new replacement blocks in height order). -- `SetPostgresEnabled(enabled bool) bool` — Toggles postgres mode - programmatically; returns old value for restore-on-cleanup patterns. - -## Key Methods (on `*Harness`) - -- `Reorg(depth, newBlocks int) ReorgResult` — Invalidates the last `depth` - blocks via `invalidateblock`, mines `newBlocks` on the fork point (must be - > `depth`), and waits for the primary LND node to resync. -- `ReorgDepth(depth int) ReorgResult` — Convenience wrapper: `Reorg(depth, - depth+1)` to produce a strictly longer replacement branch. -- `ReconsiderBlock(hash string)` — Asks bitcoind to reconsider a previously - invalidated block. +- `Harness` — Top-level test harness owning the bitcoind, electrs, postgres, + and LND container lifecycle. `NewHarness` builds it; `Start` launches + containers and `Stop` tears them down. Exposes host ports + (`BitcoindRPC`, `LNDGRPCPort`, ...), mining (`Generate`, + `GenerateAndWait`), funding (`Faucet`, `FundOperatorLND`), reorg + (`Reorg`, `ReorgDepth`, `ReconsiderBlock`), and multi-node helpers + (`StartAdditionalLND`, `StartAdditionalLNDWithBackend`, + `SetupChannelBetween`). +- `Options` — `NewHarness` configuration: image tags, `LNDRequireInterceptor`, + `LNDBuildPath`, `ArtifactsBaseDir`, `GroupName`, log-to-stdout toggles, + `StartTapd`, `AlwaysKeepArtifacts`. `DefaultOptions()` gives safe defaults. +- `LndInstance` — Handle to one LND container (ports, TLS cert/macaroon + paths, `*lndclient.LndServices`). +- `TapdHarness` — Paired LND + tapd instance for asset-related tests, + created via `Harness.NewTapdHarness`. +- `ReorgResult` — Branches produced by a harness reorg: `OldTip`, + `ForkPoint`, `Disconnected` (old-chain blocks, height order), `Connected` + (new replacement blocks, height order). +- `LNDChainBackendBitcoind` / `LNDChainBackendNeutrino` — Chain-backend + selectors for `StartAdditionalLNDWithBackend`, letting a test run one LND + node over bitcoind RPC+ZMQ and another over neutrino/P2P (BIP157/158) to + exercise both broadcast paths. ## Relationships -- **Depends on**: `chain` (bitcoind RPC), `lndbackend` (LND integration), - `chainbackends` (PackageSubmitter interface). +- **Depends on**: `chain` (wraps bitcoind RPC client for + `SubmitPackage`/package-relay tests via `BitcoindClient()`). - **Depended on by**: `systest` (system-level tests). -## Key Constants +## Invariants -- `numInitialBlocks` = 106, `defaultTimeout` = 30s, `pollInterval` = 200ms. -- `BitcoindRPCUser` / `BitcoindRPCPass` — RPC credentials shared across - tests. -- `electrsReadyTimeout` = 2 minutes — separate extended timeout for the - electrs container HTTP readiness check. -- Coinbase maturity: 100 blocks + 6-block buffer. +- `NewHarness` only builds the struct; nothing starts until `Start` is + called. `Start` pre-mines `numInitialBlocks` (106 = 100-block coinbase + maturity + 6-block buffer) before tests run. +- `Reorg(depth, newBlocks)` requires `newBlocks > depth` so the replacement + branch is strictly longer than the disconnected one; it blocks until the + primary LND node resyncs to the new tip. +- `LNDRequireInterceptor` only applies to the primary LND node; additional + nodes started via `StartAdditionalLND*` never set it. +- Container teardown (`Stop`) is guarded by `sync.Once`; a signal handler + also calls `Stop` as a safety net against orphaned containers. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/harness/CLAUDE.md b/harness/CLAUDE.md index 59179e972..2ac7ee2f9 100644 --- a/harness/CLAUDE.md +++ b/harness/CLAUDE.md @@ -2,47 +2,55 @@ ## Purpose -Docker-based Bitcoin/LND integration test environment. Manages bitcoind and LND -containers with network isolation for end-to-end testing. +Docker-based regtest integration-test harness. Spins up bitcoind, electrs +(Esplora HTTP), optional postgres, and one or more LND containers with +per-run network isolation, artifact/log capture, and mining/funding/reorg +helpers for end-to-end tests. ## Key Types -- `Harness` — Top-level test harness owning bitcoind, lnd, and arkd lifecycle. -- `LndInstance` — Manages an LND container's lifecycle and connection. -- `TapdHarness` — Optional Tapd instance for asset-related tests. -- `Options` — Configuration struct passed to `NewHarness`. Controls image - tags, artifact directory, log routing, tapd toggle, `GroupName`, and - `AlwaysKeepArtifacts`. -- `DefaultOptions()` — Returns a populated `Options` with safe defaults. -- `Block` — Mined block header plus txid list; used by mining helpers. -- `BlockHeader` — Verbose bitcoind `getblockheader` RPC representation. -- `ReorgResult` — Describes the branches produced by a reorg: `OldTip`, - `ForkPoint`, `Disconnected` (old-chain blocks in height order), and - `Connected` (new replacement blocks in height order). -- `SetPostgresEnabled(enabled bool) bool` — Toggles postgres mode - programmatically; returns old value for restore-on-cleanup patterns. - -## Key Methods (on `*Harness`) - -- `Reorg(depth, newBlocks int) ReorgResult` — Invalidates the last `depth` - blocks via `invalidateblock`, mines `newBlocks` on the fork point (must be - > `depth`), and waits for the primary LND node to resync. -- `ReorgDepth(depth int) ReorgResult` — Convenience wrapper: `Reorg(depth, - depth+1)` to produce a strictly longer replacement branch. -- `ReconsiderBlock(hash string)` — Asks bitcoind to reconsider a previously - invalidated block. +- `Harness` — Top-level test harness owning the bitcoind, electrs, postgres, + and LND container lifecycle. `NewHarness` builds it; `Start` launches + containers and `Stop` tears them down. Exposes host ports + (`BitcoindRPC`, `LNDGRPCPort`, ...), mining (`Generate`, + `GenerateAndWait`), funding (`Faucet`, `FundOperatorLND`), reorg + (`Reorg`, `ReorgDepth`, `ReconsiderBlock`), and multi-node helpers + (`StartAdditionalLND`, `StartAdditionalLNDWithBackend`, + `SetupChannelBetween`). +- `Options` — `NewHarness` configuration: image tags, `LNDRequireInterceptor`, + `LNDBuildPath`, `ArtifactsBaseDir`, `GroupName`, log-to-stdout toggles, + `StartTapd`, `AlwaysKeepArtifacts`. `DefaultOptions()` gives safe defaults. +- `LndInstance` — Handle to one LND container (ports, TLS cert/macaroon + paths, `*lndclient.LndServices`). +- `TapdHarness` — Paired LND + tapd instance for asset-related tests, + created via `Harness.NewTapdHarness`. +- `ReorgResult` — Branches produced by a harness reorg: `OldTip`, + `ForkPoint`, `Disconnected` (old-chain blocks, height order), `Connected` + (new replacement blocks, height order). +- `LNDChainBackendBitcoind` / `LNDChainBackendNeutrino` — Chain-backend + selectors for `StartAdditionalLNDWithBackend`, letting a test run one LND + node over bitcoind RPC+ZMQ and another over neutrino/P2P (BIP157/158) to + exercise both broadcast paths. ## Relationships -- **Depends on**: `chain` (bitcoind RPC), `lndbackend` (LND integration), - `chainbackends` (PackageSubmitter interface). +- **Depends on**: `chain` (wraps bitcoind RPC client for + `SubmitPackage`/package-relay tests via `BitcoindClient()`). - **Depended on by**: `systest` (system-level tests). -## Key Constants +## Invariants -- `numInitialBlocks` = 106, `defaultTimeout` = 30s, `pollInterval` = 200ms. -- `BitcoindRPCUser` / `BitcoindRPCPass` — RPC credentials shared across - tests. -- `electrsReadyTimeout` = 2 minutes — separate extended timeout for the - electrs container HTTP readiness check. -- Coinbase maturity: 100 blocks + 6-block buffer. +- `NewHarness` only builds the struct; nothing starts until `Start` is + called. `Start` pre-mines `numInitialBlocks` (106 = 100-block coinbase + maturity + 6-block buffer) before tests run. +- `Reorg(depth, newBlocks)` requires `newBlocks > depth` so the replacement + branch is strictly longer than the disconnected one; it blocks until the + primary LND node resyncs to the new tip. +- `LNDRequireInterceptor` only applies to the primary LND node; additional + nodes started via `StartAdditionalLND*` never set it. +- Container teardown (`Stop`) is guarded by `sync.Once`; a signal handler + also calls `Stop` as a safety net against orphaned containers. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/indexer/AGENTS.md b/indexer/AGENTS.md index 236f7498f..b7ceb8254 100644 --- a/indexer/AGENTS.md +++ b/indexer/AGENTS.md @@ -20,14 +20,22 @@ proofs for proof-of-control. ## Key Methods (on `*Client`) +- `RegisterReceiveScriptTaproot` / `UnregisterReceiveScript` / + `ListMyReceiveScripts` — Register, unregister, and enumerate the caller's + receive scripts on the server. - `BuildListVTXOsByScriptsTaprootRequest(ctx, scopes, afterCursor []byte, limit, statusFilter)` / `ListVTXOsByScriptsTaproot(ctx, scopes, afterCursor []byte, limit, statusFilter)` — Build and execute taproot-scope-proofed `ListVTXOsByScripts` queries. `afterCursor` is an opaque `[]byte` keyset cursor passed through unchanged. The proof covers each pkScript using owner-key signatures gated on script scope. - `BuildGetOORSessionByTxidTaprootRequest` / `GetOORSessionByTxidTaproot` — Build and execute a taproot-proofed OOR session lookup by Ark txid. - `BuildListOORRecipientEventsByScriptTaprootRequest` / `ListOORRecipientEventsByScriptTaproot` — Build and execute a taproot-proofed listing of OOR receive events for a given pkScript. +- `GetSubtreeByScriptsTaproot` — Taproot-proofed lookup of a VTXO subtree + (optionally including internal nodes) for the given scopes. ## Relationships -- **Depends on**: `arkrpc` (IndexerService stubs), `serverconn` (mailbox transport). -- **Depended on by**: `darepod` (wiring, receive script registration, metadata queries). +- **Depends on**: `arkrpc` (generated IndexerService stubs), `mailbox/rpc` + (transport-agnostic `RPCClient`/`RPCOptions` contracts), `internal/indexerlimits` + (pagination bounds). +- **Depended on by**: `darepod` (wiring, receive script registration, metadata + queries), `proofkeys`, `walletcore`. ## Invariants diff --git a/indexer/CLAUDE.md b/indexer/CLAUDE.md index 236f7498f..b7ceb8254 100644 --- a/indexer/CLAUDE.md +++ b/indexer/CLAUDE.md @@ -20,14 +20,22 @@ proofs for proof-of-control. ## Key Methods (on `*Client`) +- `RegisterReceiveScriptTaproot` / `UnregisterReceiveScript` / + `ListMyReceiveScripts` — Register, unregister, and enumerate the caller's + receive scripts on the server. - `BuildListVTXOsByScriptsTaprootRequest(ctx, scopes, afterCursor []byte, limit, statusFilter)` / `ListVTXOsByScriptsTaproot(ctx, scopes, afterCursor []byte, limit, statusFilter)` — Build and execute taproot-scope-proofed `ListVTXOsByScripts` queries. `afterCursor` is an opaque `[]byte` keyset cursor passed through unchanged. The proof covers each pkScript using owner-key signatures gated on script scope. - `BuildGetOORSessionByTxidTaprootRequest` / `GetOORSessionByTxidTaproot` — Build and execute a taproot-proofed OOR session lookup by Ark txid. - `BuildListOORRecipientEventsByScriptTaprootRequest` / `ListOORRecipientEventsByScriptTaproot` — Build and execute a taproot-proofed listing of OOR receive events for a given pkScript. +- `GetSubtreeByScriptsTaproot` — Taproot-proofed lookup of a VTXO subtree + (optionally including internal nodes) for the given scopes. ## Relationships -- **Depends on**: `arkrpc` (IndexerService stubs), `serverconn` (mailbox transport). -- **Depended on by**: `darepod` (wiring, receive script registration, metadata queries). +- **Depends on**: `arkrpc` (generated IndexerService stubs), `mailbox/rpc` + (transport-agnostic `RPCClient`/`RPCOptions` contracts), `internal/indexerlimits` + (pagination bounds). +- **Depended on by**: `darepod` (wiring, receive script registration, metadata + queries), `proofkeys`, `walletcore`. ## Invariants diff --git a/internal/AGENTS.md b/internal/AGENTS.md index 52365f902..b03d6886a 100644 --- a/internal/AGENTS.md +++ b/internal/AGENTS.md @@ -11,9 +11,13 @@ module. - `internal/actortest` — Durable actor integration tests using real DB backends (SQLite, Postgres), verifying at-least-once delivery, exactly-once dedup, FIFO ordering, and atomic state+outbox. - `internal/cmd/tools/accounting` — DB-backed admin command that reports ledger balances, event totals, and optional BTC/fiat valuation. - `internal/indexerlimits` — Shared client-side bounds for indexer pagination cursors. +- `internal/sqlbase` — `js && wasm`-only `walletdb`-compatible SQL backend + (SQLite over `go-wasmsqlite`), used by `lwwallet` for browser builds. - `internal/testutils` — Deterministic key pair and Schnorr signature generation for tests. ## Relationships -- **Depends on**: `baselib/actor`, `db` (real backends for integration tests). -- **Depended on by**: internal module packages only. +- **Depends on**: `baselib/actor`, `db` (real backends for integration tests), + `btcwallet/walletdb` (sqlbase's wasm backend). +- **Depended on by**: internal module packages only, plus `lwwallet` (wasm + builds, via `internal/sqlbase`). diff --git a/internal/CLAUDE.md b/internal/CLAUDE.md index 52365f902..b03d6886a 100644 --- a/internal/CLAUDE.md +++ b/internal/CLAUDE.md @@ -11,9 +11,13 @@ module. - `internal/actortest` — Durable actor integration tests using real DB backends (SQLite, Postgres), verifying at-least-once delivery, exactly-once dedup, FIFO ordering, and atomic state+outbox. - `internal/cmd/tools/accounting` — DB-backed admin command that reports ledger balances, event totals, and optional BTC/fiat valuation. - `internal/indexerlimits` — Shared client-side bounds for indexer pagination cursors. +- `internal/sqlbase` — `js && wasm`-only `walletdb`-compatible SQL backend + (SQLite over `go-wasmsqlite`), used by `lwwallet` for browser builds. - `internal/testutils` — Deterministic key pair and Schnorr signature generation for tests. ## Relationships -- **Depends on**: `baselib/actor`, `db` (real backends for integration tests). -- **Depended on by**: internal module packages only. +- **Depends on**: `baselib/actor`, `db` (real backends for integration tests), + `btcwallet/walletdb` (sqlbase's wasm backend). +- **Depended on by**: internal module packages only, plus `lwwallet` (wasm + builds, via `internal/sqlbase`). diff --git a/internal/actortest/AGENTS.md b/internal/actortest/AGENTS.md index 6012e7e0c..991a757b5 100644 --- a/internal/actortest/AGENTS.md +++ b/internal/actortest/AGENTS.md @@ -10,11 +10,22 @@ state+outbox checkpointing. ## Key Test Infrastructure -- `testHarness` / `newTestHarness` — Central test scaffolding: sets up real DB, actor system, delivery store, and outbox publisher. +- `testHarness` / `newTestHarness` — Central test scaffolding: sets up a + per-test in-memory SQLite DB, `actor.ActorSystem`, and TX-aware actor + delivery store; tests create their own `actor.OutboxPublisher` per case. +- `CounterBehavior` / `CounterMessage` (`IncrementMsg`, `DecrementMsg`, + `GetCountMsg`, `ForwardMsg`) — Demo durable actor and TLV-coded messages + used to drive the e2e scenarios. - `eventuallyWithOutboxPublish` — Helper that actively triggers `OutboxPublisher.PublishPending()` on every polling iteration, making outbox delivery assertions robust under the race detector and CI scheduler pressure. -- Timeout constants: `outboxForwardProcessingTimeout` (5s), `outboxDeliveryTimeout` (10s), `durableAskResponseTimeout` (10s). +- `newLedgerActorForTest` (`ledger_e2e_test.go`) — Wires a real + `ledger.LedgerActor` on the durable mailbox against the same SQLite DB, so + ledger writes join the actor's fenced `Commit` transaction as in production. +- Timeout constants: `outboxForwardProcessingTimeout`, `outboxDeliveryTimeout`, + `durableAskResponseTimeout` — all 30s, kept aligned since DurableAsk + responses and forwards are also delivered through the outbox. ## Relationships -- **Depends on**: `baselib/actor`, `db/actordelivery` (real backends, not mocks). +- **Depends on**: `baselib/actor`, `db` / `db/actordelivery` (real backends, + not mocks), `ledger` (`LedgerActor` e2e coverage). - **Depended on by**: nothing (test-only). diff --git a/internal/actortest/CLAUDE.md b/internal/actortest/CLAUDE.md index 6012e7e0c..991a757b5 100644 --- a/internal/actortest/CLAUDE.md +++ b/internal/actortest/CLAUDE.md @@ -10,11 +10,22 @@ state+outbox checkpointing. ## Key Test Infrastructure -- `testHarness` / `newTestHarness` — Central test scaffolding: sets up real DB, actor system, delivery store, and outbox publisher. +- `testHarness` / `newTestHarness` — Central test scaffolding: sets up a + per-test in-memory SQLite DB, `actor.ActorSystem`, and TX-aware actor + delivery store; tests create their own `actor.OutboxPublisher` per case. +- `CounterBehavior` / `CounterMessage` (`IncrementMsg`, `DecrementMsg`, + `GetCountMsg`, `ForwardMsg`) — Demo durable actor and TLV-coded messages + used to drive the e2e scenarios. - `eventuallyWithOutboxPublish` — Helper that actively triggers `OutboxPublisher.PublishPending()` on every polling iteration, making outbox delivery assertions robust under the race detector and CI scheduler pressure. -- Timeout constants: `outboxForwardProcessingTimeout` (5s), `outboxDeliveryTimeout` (10s), `durableAskResponseTimeout` (10s). +- `newLedgerActorForTest` (`ledger_e2e_test.go`) — Wires a real + `ledger.LedgerActor` on the durable mailbox against the same SQLite DB, so + ledger writes join the actor's fenced `Commit` transaction as in production. +- Timeout constants: `outboxForwardProcessingTimeout`, `outboxDeliveryTimeout`, + `durableAskResponseTimeout` — all 30s, kept aligned since DurableAsk + responses and forwards are also delivered through the outbox. ## Relationships -- **Depends on**: `baselib/actor`, `db/actordelivery` (real backends, not mocks). +- **Depends on**: `baselib/actor`, `db` / `db/actordelivery` (real backends, + not mocks), `ledger` (`LedgerActor` e2e coverage). - **Depended on by**: nothing (test-only). diff --git a/internal/indexerlimits/AGENTS.md b/internal/indexerlimits/AGENTS.md index 1b3189151..49371b2a0 100644 --- a/internal/indexerlimits/AGENTS.md +++ b/internal/indexerlimits/AGENTS.md @@ -16,8 +16,8 @@ passing them to server queries. ## Relationships - **Depends on**: nothing (stdlib only). -- **Depended on by**: `serverconn`, `darepod`, `indexer` (validate inbound - `ListVTXOsByScripts` cursors before query execution). +- **Depended on by**: `serverconn`, `darepod`, `indexer`, `vtxo` (validate + inbound `ListVTXOsByScripts` cursors before query execution). ## Invariants diff --git a/internal/indexerlimits/CLAUDE.md b/internal/indexerlimits/CLAUDE.md index 1b3189151..49371b2a0 100644 --- a/internal/indexerlimits/CLAUDE.md +++ b/internal/indexerlimits/CLAUDE.md @@ -16,8 +16,8 @@ passing them to server queries. ## Relationships - **Depends on**: nothing (stdlib only). -- **Depended on by**: `serverconn`, `darepod`, `indexer` (validate inbound - `ListVTXOsByScripts` cursors before query execution). +- **Depended on by**: `serverconn`, `darepod`, `indexer`, `vtxo` (validate + inbound `ListVTXOsByScripts` cursors before query execution). ## Invariants diff --git a/internal/testutils/AGENTS.md b/internal/testutils/AGENTS.md index fdd1246e8..adf5ec2f2 100644 --- a/internal/testutils/AGENTS.md +++ b/internal/testutils/AGENTS.md @@ -2,9 +2,31 @@ ## Purpose -Deterministic key pair and Schnorr signature generation for tests. +Shared test helpers: deterministic key/signature generation and a Script +engine execution asserter, used by unit tests across the repo. + +## Key Types + +- `CreateKey(index int32) (*btcec.PublicKey, input.Signer)` — Deterministic + key pair (from `index`) plus a mock `input.Signer` for it. +- `TestSchnorrSignature(t, seed string) *schnorr.Signature` — Deterministic + Schnorr signature over a fixed test message, keyed off `seed`. +- `AssertEngineExecution(t, testNum int, valid bool, newEngine func() (*txscript.Engine, error))` — + Runs a `txscript.Engine`, asserting it validates (or fails) as expected; + on mismatch, single-steps the VM and dumps stack/disassembly for debugging. ## Relationships -- **Depends on**: nothing. -- **Depended on by**: test packages across the repo. +- **Depends on**: `btcec`/`schnorr`/`txscript` (btcd), `lnd/input` (mock + signer), `testify/require`. +- **Depended on by**: test files in `vtxo`, `round`, `lib/tree`, `lib/tx`, + `lib/arkscript`, `vhtlcrecovery/unrollpolicy`. + +## Invariants + +- Key/signature generation must stay deterministic (fixed seeds/indices) so + golden-value and reproducibility-sensitive tests do not flake. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/internal/testutils/CLAUDE.md b/internal/testutils/CLAUDE.md index fdd1246e8..adf5ec2f2 100644 --- a/internal/testutils/CLAUDE.md +++ b/internal/testutils/CLAUDE.md @@ -2,9 +2,31 @@ ## Purpose -Deterministic key pair and Schnorr signature generation for tests. +Shared test helpers: deterministic key/signature generation and a Script +engine execution asserter, used by unit tests across the repo. + +## Key Types + +- `CreateKey(index int32) (*btcec.PublicKey, input.Signer)` — Deterministic + key pair (from `index`) plus a mock `input.Signer` for it. +- `TestSchnorrSignature(t, seed string) *schnorr.Signature` — Deterministic + Schnorr signature over a fixed test message, keyed off `seed`. +- `AssertEngineExecution(t, testNum int, valid bool, newEngine func() (*txscript.Engine, error))` — + Runs a `txscript.Engine`, asserting it validates (or fails) as expected; + on mismatch, single-steps the VM and dumps stack/disassembly for debugging. ## Relationships -- **Depends on**: nothing. -- **Depended on by**: test packages across the repo. +- **Depends on**: `btcec`/`schnorr`/`txscript` (btcd), `lnd/input` (mock + signer), `testify/require`. +- **Depended on by**: test files in `vtxo`, `round`, `lib/tree`, `lib/tx`, + `lib/arkscript`, `vhtlcrecovery/unrollpolicy`. + +## Invariants + +- Key/signature generation must stay deterministic (fixed seeds/indices) so + golden-value and reproducibility-sensitive tests do not flake. + +## Deep Docs + +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/ledger/AGENTS.md b/ledger/AGENTS.md index 0584ade1a..871062ddc 100644 --- a/ledger/AGENTS.md +++ b/ledger/AGENTS.md @@ -209,5 +209,3 @@ or balance reconciliation. Required emission pairs: - [db/CLAUDE.md](../db/CLAUDE.md) — `LedgerStoreDB` and `UTXOAuditStoreDB` adapters. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/ledger/CLAUDE.md b/ledger/CLAUDE.md index 0584ade1a..871062ddc 100644 --- a/ledger/CLAUDE.md +++ b/ledger/CLAUDE.md @@ -209,5 +209,3 @@ or balance reconciliation. Required emission pairs: - [db/CLAUDE.md](../db/CLAUDE.md) — `LedgerStoreDB` and `UTXOAuditStoreDB` adapters. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/lib/AGENTS.md b/lib/AGENTS.md index 84374bf85..baf512ef9 100644 --- a/lib/AGENTS.md +++ b/lib/AGENTS.md @@ -41,10 +41,21 @@ message interfaces, and core Ark types. - `TriggerBoardMsg`, `RegisterIntentMsg` — Cross-package messages from wallet→round. - `SelectAndReserveSpendRequest`, `ReserveForfeitRequest`, etc. — VTXO manager admission types. +### lib/recovery +- `Proof` — Immutable unilateral-exit recovery graph for one target outpoint. +- `Session` / `SessionState` — Mutable planning state and its durable TLV + projection, driven by broadcast/confirm/fail observations. + +### lib/scripts +- Removed; superseded by `lib/arkscript`. + ## Relationships - **Depends on**: `baselib/actor` (actormsg only, for ServiceKey). -- **Depended on by**: `round`, `vtxo`, `oor`, `wallet`, `darepod`, `rpc/*`. +- **Depended on by**: nearly every client subsystem (`round`, `vtxo`, `oor`, + `wallet`, `unroll`, `unrollplan`, `txconfirm`, `fraud`, `db`, `sdk`, + `vhtlcrecovery`, `swapclientserver`, `darepod`, `rpc`/`arkrpc`) — `lib` + holds the shared domain types the rest of the client builds on. ## Deep Docs diff --git a/lib/CLAUDE.md b/lib/CLAUDE.md index 84374bf85..baf512ef9 100644 --- a/lib/CLAUDE.md +++ b/lib/CLAUDE.md @@ -41,10 +41,21 @@ message interfaces, and core Ark types. - `TriggerBoardMsg`, `RegisterIntentMsg` — Cross-package messages from wallet→round. - `SelectAndReserveSpendRequest`, `ReserveForfeitRequest`, etc. — VTXO manager admission types. +### lib/recovery +- `Proof` — Immutable unilateral-exit recovery graph for one target outpoint. +- `Session` / `SessionState` — Mutable planning state and its durable TLV + projection, driven by broadcast/confirm/fail observations. + +### lib/scripts +- Removed; superseded by `lib/arkscript`. + ## Relationships - **Depends on**: `baselib/actor` (actormsg only, for ServiceKey). -- **Depended on by**: `round`, `vtxo`, `oor`, `wallet`, `darepod`, `rpc/*`. +- **Depended on by**: nearly every client subsystem (`round`, `vtxo`, `oor`, + `wallet`, `unroll`, `unrollplan`, `txconfirm`, `fraud`, `db`, `sdk`, + `vhtlcrecovery`, `swapclientserver`, `darepod`, `rpc`/`arkrpc`) — `lib` + holds the shared domain types the rest of the client builds on. ## Deep Docs diff --git a/lib/arkscript/AGENTS.md b/lib/arkscript/AGENTS.md index be4f2dc76..8fdcf3912 100644 --- a/lib/arkscript/AGENTS.md +++ b/lib/arkscript/AGENTS.md @@ -56,9 +56,11 @@ validated invariants. ## Relationships - **Depends on**: (no internal repo imports; pure cryptographic library). -- **Depended on by**: `darepod`, `db`, `lib/tree`, `lib/types`, `lib/tx/arktx`, - `lib/tx/checkpoint`, `lib/tx`, `lib/tx/oor`, `lib/tx/psbtutil`, - `oor`, `round`, `vtxo`, `wallet`. +- **Depended on by**: nearly every protocol-logic package — `darepod`, `db`, + `lib/tree`, `lib/types`, `lib/recovery`, `lib/tx` (and `arktx`, + `checkpoint`, `oor`, `psbtutil` subpackages), `oor`, `round`, `txconfirm`, + `unroll`, `vhtlcrecovery/unrollpolicy`, `sdk/swaps`, `vtxo`, `wallet`. It is + the base script/policy layer. ## Invariants diff --git a/lib/arkscript/CLAUDE.md b/lib/arkscript/CLAUDE.md index be4f2dc76..8fdcf3912 100644 --- a/lib/arkscript/CLAUDE.md +++ b/lib/arkscript/CLAUDE.md @@ -56,9 +56,11 @@ validated invariants. ## Relationships - **Depends on**: (no internal repo imports; pure cryptographic library). -- **Depended on by**: `darepod`, `db`, `lib/tree`, `lib/types`, `lib/tx/arktx`, - `lib/tx/checkpoint`, `lib/tx`, `lib/tx/oor`, `lib/tx/psbtutil`, - `oor`, `round`, `vtxo`, `wallet`. +- **Depended on by**: nearly every protocol-logic package — `darepod`, `db`, + `lib/tree`, `lib/types`, `lib/recovery`, `lib/tx` (and `arktx`, + `checkpoint`, `oor`, `psbtutil` subpackages), `oor`, `round`, `txconfirm`, + `unroll`, `vhtlcrecovery/unrollpolicy`, `sdk/swaps`, `vtxo`, `wallet`. It is + the base script/policy layer. ## Invariants diff --git a/lib/bip322/AGENTS.md b/lib/bip322/AGENTS.md index d401ee5bf..a40d0a554 100644 --- a/lib/bip322/AGENTS.md +++ b/lib/bip322/AGENTS.md @@ -22,8 +22,8 @@ metadata encoding. ## Relationships - **Depends on**: (no internal repo imports; pure cryptographic library). -- **Depended on by**: `round` (intent signing), `darepod` (auth validation), - `wallet` (signing support). +- **Depended on by**: `round` (join-round intent signing and BIP-322 auth + validation, via `join_auth.go`). ## Invariants diff --git a/lib/bip322/CLAUDE.md b/lib/bip322/CLAUDE.md index d401ee5bf..a40d0a554 100644 --- a/lib/bip322/CLAUDE.md +++ b/lib/bip322/CLAUDE.md @@ -22,8 +22,8 @@ metadata encoding. ## Relationships - **Depends on**: (no internal repo imports; pure cryptographic library). -- **Depended on by**: `round` (intent signing), `darepod` (auth validation), - `wallet` (signing support). +- **Depended on by**: `round` (join-round intent signing and BIP-322 auth + validation, via `join_auth.go`). ## Invariants diff --git a/lib/recovery/AGENTS.md b/lib/recovery/AGENTS.md index e8102ffad..bcff53e9d 100644 --- a/lib/recovery/AGENTS.md +++ b/lib/recovery/AGENTS.md @@ -4,8 +4,9 @@ Pure, immutable proof graph plus per-session planning state for unilateral exit / recovery of a VTXO target outpoint. The package exposes the data model -(proof, session, durable state) and a TLV codec for crash-safe persistence; -actual broadcast orchestration lives downstream in later PRs. +(proof, session, durable state) and a TLV codec for crash-safe persistence. +It is deliberately I/O-free: broadcast orchestration, chain queries, and retry +scheduling live downstream in `unrollplan` and `unroll`. ## Key Types @@ -29,9 +30,9 @@ actual broadcast orchestration lives downstream in later PRs. `lib/tree` (generic BFS `Queue[T]` for iterative ancestor traversal), `github.com/lightningnetwork/lnd/fn/v2` (Option type), `github.com/lightningnetwork/lnd/tlv` (state / proof codec). -- **Depended on by**: `unrollplan` (pure planning layer; re-uses - `Proof`, `Node`, `ComputeMaturityHeight`). Later recovery PRs (3/5, 4/5, - 5/5) will consume the codec for checkpoint persistence. +- **Depended on by**: `unrollplan` (pure planning layer; re-uses `Proof`, + `Node`, `ComputeMaturityHeight`), `unroll` (actor/FSM that drives broadcast + and persists `SessionState` via the TLV codec), `darepod` (RPC wiring). ## Invariants diff --git a/lib/recovery/CLAUDE.md b/lib/recovery/CLAUDE.md index e8102ffad..bcff53e9d 100644 --- a/lib/recovery/CLAUDE.md +++ b/lib/recovery/CLAUDE.md @@ -4,8 +4,9 @@ Pure, immutable proof graph plus per-session planning state for unilateral exit / recovery of a VTXO target outpoint. The package exposes the data model -(proof, session, durable state) and a TLV codec for crash-safe persistence; -actual broadcast orchestration lives downstream in later PRs. +(proof, session, durable state) and a TLV codec for crash-safe persistence. +It is deliberately I/O-free: broadcast orchestration, chain queries, and retry +scheduling live downstream in `unrollplan` and `unroll`. ## Key Types @@ -29,9 +30,9 @@ actual broadcast orchestration lives downstream in later PRs. `lib/tree` (generic BFS `Queue[T]` for iterative ancestor traversal), `github.com/lightningnetwork/lnd/fn/v2` (Option type), `github.com/lightningnetwork/lnd/tlv` (state / proof codec). -- **Depended on by**: `unrollplan` (pure planning layer; re-uses - `Proof`, `Node`, `ComputeMaturityHeight`). Later recovery PRs (3/5, 4/5, - 5/5) will consume the codec for checkpoint persistence. +- **Depended on by**: `unrollplan` (pure planning layer; re-uses `Proof`, + `Node`, `ComputeMaturityHeight`), `unroll` (actor/FSM that drives broadcast + and persists `SessionState` via the TLV codec), `darepod` (RPC wiring). ## Invariants diff --git a/lib/scripts/AGENTS.md b/lib/scripts/AGENTS.md index 42c2ec165..b605ff3c4 100644 --- a/lib/scripts/AGENTS.md +++ b/lib/scripts/AGENTS.md @@ -3,5 +3,3 @@ **Removed.** All functionality migrated to [`lib/arkscript`](../arkscript/CLAUDE.md) — see `VTXOPolicy`, `CheckpointPolicy`, `SpendInfo`, `AnchorOutput`, and `AnchorPkScript`. - - diff --git a/lib/scripts/CLAUDE.md b/lib/scripts/CLAUDE.md index 42c2ec165..b605ff3c4 100644 --- a/lib/scripts/CLAUDE.md +++ b/lib/scripts/CLAUDE.md @@ -3,5 +3,3 @@ **Removed.** All functionality migrated to [`lib/arkscript`](../arkscript/CLAUDE.md) — see `VTXOPolicy`, `CheckpointPolicy`, `SpendInfo`, `AnchorOutput`, and `AnchorPkScript`. - - diff --git a/lib/tree/AGENTS.md b/lib/tree/AGENTS.md index b248fa2d0..55fabdea9 100644 --- a/lib/tree/AGENTS.md +++ b/lib/tree/AGENTS.md @@ -17,12 +17,13 @@ descriptors through branch nodes to the batch output. - `StructureConfig` — Configuration for tree building (radix, partition weight function). - `SignerSession` — MuSig2 signing session for tree transactions, wrapping `input.MuSig2Signer`. - `Materializer` / `BTCMaterializer` — Interface and implementation for materializing tree nodes into actual Bitcoin transactions. +- `TreeAssembler` — Two-pass builder (`BuildStructure` then `Materialize`) driven by `TreeConfig`. - `Queue[T]` — Generic queue used internally for BFS tree traversal. ## Relationships - **Depends on**: `lib/arkscript` (taproot script construction, policy templates, `SpendInfo`). -- **Depended on by**: `round` (tree construction/validation), `vtxo` (tree paths in `Descriptor`), `oor` (tree references), `db` (tree serialization), `lib/tx` (forfeit construction). +- **Depended on by**: `round` (tree construction/validation), `oor` (tree references), `db` (tree serialization). ## Invariants @@ -31,7 +32,11 @@ descriptors through branch nodes to the batch output. - Cosigner keys must be deduplicated (`UniqueCosigners`) before computing the final MuSig2 key. - Tree materialization is deterministic given the same leaf descriptors and operator key. - `ValidateVTXODescriptors` / `ValidateConnectorDescriptor` must pass before tree construction. -- **Cache-aliasing invariant**: a `*Tree` is effectively immutable once published from a builder or resolver. Multiple consumers may share the same `*Tree` pointer through caches and ancestry-fragment slices (see `indexer.lineageResolver.treeByKey`). Silently mutating a shared tree's nodes or root would corrupt every aliasing reader. Callers that need to transform a tree must clone it first. +- **Cache-aliasing invariant**: a `*Tree` is effectively immutable once published from + a builder or resolver. Multiple downstream consumers may share the same `*Tree` + pointer through caches and ancestry-fragment slices. Silently mutating a shared + tree's nodes or root would corrupt every aliasing reader. Callers that need to + transform a tree must clone it first. ## Deep Docs diff --git a/lib/tree/CLAUDE.md b/lib/tree/CLAUDE.md index b248fa2d0..55fabdea9 100644 --- a/lib/tree/CLAUDE.md +++ b/lib/tree/CLAUDE.md @@ -17,12 +17,13 @@ descriptors through branch nodes to the batch output. - `StructureConfig` — Configuration for tree building (radix, partition weight function). - `SignerSession` — MuSig2 signing session for tree transactions, wrapping `input.MuSig2Signer`. - `Materializer` / `BTCMaterializer` — Interface and implementation for materializing tree nodes into actual Bitcoin transactions. +- `TreeAssembler` — Two-pass builder (`BuildStructure` then `Materialize`) driven by `TreeConfig`. - `Queue[T]` — Generic queue used internally for BFS tree traversal. ## Relationships - **Depends on**: `lib/arkscript` (taproot script construction, policy templates, `SpendInfo`). -- **Depended on by**: `round` (tree construction/validation), `vtxo` (tree paths in `Descriptor`), `oor` (tree references), `db` (tree serialization), `lib/tx` (forfeit construction). +- **Depended on by**: `round` (tree construction/validation), `oor` (tree references), `db` (tree serialization). ## Invariants @@ -31,7 +32,11 @@ descriptors through branch nodes to the batch output. - Cosigner keys must be deduplicated (`UniqueCosigners`) before computing the final MuSig2 key. - Tree materialization is deterministic given the same leaf descriptors and operator key. - `ValidateVTXODescriptors` / `ValidateConnectorDescriptor` must pass before tree construction. -- **Cache-aliasing invariant**: a `*Tree` is effectively immutable once published from a builder or resolver. Multiple consumers may share the same `*Tree` pointer through caches and ancestry-fragment slices (see `indexer.lineageResolver.treeByKey`). Silently mutating a shared tree's nodes or root would corrupt every aliasing reader. Callers that need to transform a tree must clone it first. +- **Cache-aliasing invariant**: a `*Tree` is effectively immutable once published from + a builder or resolver. Multiple downstream consumers may share the same `*Tree` + pointer through caches and ancestry-fragment slices. Silently mutating a shared + tree's nodes or root would corrupt every aliasing reader. Callers that need to + transform a tree must clone it first. ## Deep Docs diff --git a/lib/tx/AGENTS.md b/lib/tx/AGENTS.md index 71294ece7..13c82213b 100644 --- a/lib/tx/AGENTS.md +++ b/lib/tx/AGENTS.md @@ -27,8 +27,9 @@ handle specific transaction types (Ark batch, checkpoint, OOR, PSBT utilities). ## Relationships -- **Depends on**: `lib/arkscript` (taproot script construction, policy types), `lib/tree` (tree types). -- **Depended on by**: `round` (forfeit construction/validation), `oor` (checkpoint/Ark signing), `vtxo` (forfeit signing). +- **Depends on**: `lib/arkscript` (taproot script construction, policy types). +- **Depended on by**: `round` (forfeit construction/validation), `oor` (checkpoint/Ark signing), + `vtxo` (forfeit signing), `darepod` (forfeit signature broker, RPC server). ## Invariants diff --git a/lib/tx/CLAUDE.md b/lib/tx/CLAUDE.md index 71294ece7..13c82213b 100644 --- a/lib/tx/CLAUDE.md +++ b/lib/tx/CLAUDE.md @@ -27,8 +27,9 @@ handle specific transaction types (Ark batch, checkpoint, OOR, PSBT utilities). ## Relationships -- **Depends on**: `lib/arkscript` (taproot script construction, policy types), `lib/tree` (tree types). -- **Depended on by**: `round` (forfeit construction/validation), `oor` (checkpoint/Ark signing), `vtxo` (forfeit signing). +- **Depends on**: `lib/arkscript` (taproot script construction, policy types). +- **Depended on by**: `round` (forfeit construction/validation), `oor` (checkpoint/Ark signing), + `vtxo` (forfeit signing), `darepod` (forfeit signature broker, RPC server). ## Invariants diff --git a/lib/tx/arktx/AGENTS.md b/lib/tx/arktx/AGENTS.md index 6118efe0f..77ce00725 100644 --- a/lib/tx/arktx/AGENTS.md +++ b/lib/tx/arktx/AGENTS.md @@ -18,12 +18,17 @@ construction). rules including exactly one anchor output placed last. - `CanonicalizeOrdering` — Sorts transaction inputs/outputs in-place according to v0 canonical rules (BIP69 ordering). -- `IsAnchorOutput` — Identifies v0 Ark anchor outputs (P2A, value 0). +- `IsAnchorOutput` — Identifies the zero-value ephemeral P2A anchor output. +- `IsFundedAnchorOutput` / `IsP2AAnchorScript` — Identify a P2A anchor carrying + a non-zero "funded" value (spare CPFP handle on an otherwise fee-paying + parent), as distinct from the zero-value ephemeral form. ## Relationships - **Depends on**: `lib/arkscript` (for `AnchorPkScript`). -- **Depended on by**: `lib/tx/checkpoint`, `lib/tx/oor`, `oor`. +- **Depended on by**: `oor`, `lib/tx/oor`, `lib/tx/checkpoint` (canonical + construction/validation); `unroll`, `wallet`, `vhtlcrecovery/unrollpolicy`, + `db` (via `TxVersion`); `txconfirm` (funded-anchor detection for CPFP). ## Invariants @@ -31,6 +36,10 @@ construction). - Input ordering follows BIP69: sorted by outpoint hash then index. - Recipient output ordering follows BIP69: sorted by amount then pkScript. - Validation is deterministic and byte-identical across client and server. +- `IsAnchorOutput` matches only the zero-value ephemeral anchor; use + `IsFundedAnchorOutput`/`IsP2AAnchorScript` when the anchor may carry a + non-zero fee-bump value. Conflating the two misclassifies a funded anchor + as absent (or vice versa). ## Deep Docs diff --git a/lib/tx/arktx/CLAUDE.md b/lib/tx/arktx/CLAUDE.md index 6118efe0f..77ce00725 100644 --- a/lib/tx/arktx/CLAUDE.md +++ b/lib/tx/arktx/CLAUDE.md @@ -18,12 +18,17 @@ construction). rules including exactly one anchor output placed last. - `CanonicalizeOrdering` — Sorts transaction inputs/outputs in-place according to v0 canonical rules (BIP69 ordering). -- `IsAnchorOutput` — Identifies v0 Ark anchor outputs (P2A, value 0). +- `IsAnchorOutput` — Identifies the zero-value ephemeral P2A anchor output. +- `IsFundedAnchorOutput` / `IsP2AAnchorScript` — Identify a P2A anchor carrying + a non-zero "funded" value (spare CPFP handle on an otherwise fee-paying + parent), as distinct from the zero-value ephemeral form. ## Relationships - **Depends on**: `lib/arkscript` (for `AnchorPkScript`). -- **Depended on by**: `lib/tx/checkpoint`, `lib/tx/oor`, `oor`. +- **Depended on by**: `oor`, `lib/tx/oor`, `lib/tx/checkpoint` (canonical + construction/validation); `unroll`, `wallet`, `vhtlcrecovery/unrollpolicy`, + `db` (via `TxVersion`); `txconfirm` (funded-anchor detection for CPFP). ## Invariants @@ -31,6 +36,10 @@ construction). - Input ordering follows BIP69: sorted by outpoint hash then index. - Recipient output ordering follows BIP69: sorted by amount then pkScript. - Validation is deterministic and byte-identical across client and server. +- `IsAnchorOutput` matches only the zero-value ephemeral anchor; use + `IsFundedAnchorOutput`/`IsP2AAnchorScript` when the anchor may carry a + non-zero fee-bump value. Conflating the two misclassifies a funded anchor + as absent (or vice versa). ## Deep Docs diff --git a/lib/tx/checkpoint/AGENTS.md b/lib/tx/checkpoint/AGENTS.md index 1a29d2fb7..7d5552442 100644 --- a/lib/tx/checkpoint/AGENTS.md +++ b/lib/tx/checkpoint/AGENTS.md @@ -27,7 +27,8 @@ transfers. - **Depends on**: `lib/arkscript` (CheckpointPolicy, CheckpointTapScript), `lib/tx/arktx` (TxVersion, validation). -- **Depended on by**: `lib/tx/oor`, `oor`. +- **Depended on by**: `lib/tx/oor` (checkpoint PSBT construction for OOR + transfers). ## Invariants diff --git a/lib/tx/checkpoint/CLAUDE.md b/lib/tx/checkpoint/CLAUDE.md index 1a29d2fb7..7d5552442 100644 --- a/lib/tx/checkpoint/CLAUDE.md +++ b/lib/tx/checkpoint/CLAUDE.md @@ -27,7 +27,8 @@ transfers. - **Depends on**: `lib/arkscript` (CheckpointPolicy, CheckpointTapScript), `lib/tx/arktx` (TxVersion, validation). -- **Depended on by**: `lib/tx/oor`, `oor`. +- **Depended on by**: `lib/tx/oor` (checkpoint PSBT construction for OOR + transfers). ## Invariants diff --git a/lib/tx/oor/AGENTS.md b/lib/tx/oor/AGENTS.md index 7639b5a32..46f74698f 100644 --- a/lib/tx/oor/AGENTS.md +++ b/lib/tx/oor/AGENTS.md @@ -24,17 +24,22 @@ finalize packages. - `BuildArkPSBT` — Constructs deterministic Ark PSBT spending checkpoint outputs, enforcing fee-less transfers and canonical ordering. - `BuildCheckpointPSBT` — Wraps checkpoint.BuildPSBT with tap tree metadata. -- `ValidateSubmitPackage` / `ValidateFinalizePackage` — Structural validators - ensuring canonical Ark PSBT, checkpoint set matches inputs, witness UTXOs - present. -- `ApplyFinalizeData` — Applies finalize data to a validated submit package. +- `ValidateSubmitPackage` / `ValidateSubmitPackageSigned` — Structural (resp. + signature+VM) validators for a submit package. +- `ValidateFinalizePackage` / `ValidateFinalizePackageSigned` — Structural + (resp. signature+VM) validators for a finalize package. +- `(*SubmitPackage).Validate` / `(*FinalizePackage).Validate` — Convenience + wrappers around the structural validators above. +- `MarshalSubmitPackage` / `UnmarshalSubmitPackage` — Versioned TLV + encode/decode for a submit package. ## Relationships - **Depends on**: `lib/arkscript` (policy types, spend helpers), `lib/tx/arktx` (validation, TxVersion), - `lib/tx/checkpoint` (BuildPSBT), `lib/tx/psbtutil` (Serialize/Parse). -- **Depended on by**: `oor` (session state machine), `db` (artifact store), - `rpc/oorpb`, `darepod` (RPC server). + `lib/tx/checkpoint` (BuildPSBT, Input/SpentVTXORef aliases), + `lib/tx/psbtutil` (Serialize/Parse). +- **Depended on by**: `oor` (session state machine), `rpc/oorpb` (wire + payloads), `darepod` (RPC server), `unroll` (proof assembly). ## Invariants @@ -43,8 +48,8 @@ finalize packages. - Checkpoint set must exactly match Ark input references (no missing, no extra). - Each Ark input spends checkpoint output index 0 (canonical v0 mapping). - Witness UTXOs must be present in Ark PSBT inputs (package is self-contained). -- Tap tree metadata (`TapTreePSBTKey`) required on all Ark inputs for - finalization. +- Each checkpoint PSBT output must carry standard PSBT tap tree metadata + (`TaprootTapTree`); required for finalization and script-VM validation. - Fee-less constraint: sum(checkpoint inputs) == sum(recipient outputs excluding anchor). - Anchor output always last with value 0. diff --git a/lib/tx/oor/CLAUDE.md b/lib/tx/oor/CLAUDE.md index 7639b5a32..46f74698f 100644 --- a/lib/tx/oor/CLAUDE.md +++ b/lib/tx/oor/CLAUDE.md @@ -24,17 +24,22 @@ finalize packages. - `BuildArkPSBT` — Constructs deterministic Ark PSBT spending checkpoint outputs, enforcing fee-less transfers and canonical ordering. - `BuildCheckpointPSBT` — Wraps checkpoint.BuildPSBT with tap tree metadata. -- `ValidateSubmitPackage` / `ValidateFinalizePackage` — Structural validators - ensuring canonical Ark PSBT, checkpoint set matches inputs, witness UTXOs - present. -- `ApplyFinalizeData` — Applies finalize data to a validated submit package. +- `ValidateSubmitPackage` / `ValidateSubmitPackageSigned` — Structural (resp. + signature+VM) validators for a submit package. +- `ValidateFinalizePackage` / `ValidateFinalizePackageSigned` — Structural + (resp. signature+VM) validators for a finalize package. +- `(*SubmitPackage).Validate` / `(*FinalizePackage).Validate` — Convenience + wrappers around the structural validators above. +- `MarshalSubmitPackage` / `UnmarshalSubmitPackage` — Versioned TLV + encode/decode for a submit package. ## Relationships - **Depends on**: `lib/arkscript` (policy types, spend helpers), `lib/tx/arktx` (validation, TxVersion), - `lib/tx/checkpoint` (BuildPSBT), `lib/tx/psbtutil` (Serialize/Parse). -- **Depended on by**: `oor` (session state machine), `db` (artifact store), - `rpc/oorpb`, `darepod` (RPC server). + `lib/tx/checkpoint` (BuildPSBT, Input/SpentVTXORef aliases), + `lib/tx/psbtutil` (Serialize/Parse). +- **Depended on by**: `oor` (session state machine), `rpc/oorpb` (wire + payloads), `darepod` (RPC server), `unroll` (proof assembly). ## Invariants @@ -43,8 +48,8 @@ finalize packages. - Checkpoint set must exactly match Ark input references (no missing, no extra). - Each Ark input spends checkpoint output index 0 (canonical v0 mapping). - Witness UTXOs must be present in Ark PSBT inputs (package is self-contained). -- Tap tree metadata (`TapTreePSBTKey`) required on all Ark inputs for - finalization. +- Each checkpoint PSBT output must carry standard PSBT tap tree metadata + (`TaprootTapTree`); required for finalization and script-VM validation. - Fee-less constraint: sum(checkpoint inputs) == sum(recipient outputs excluding anchor). - Anchor output always last with value 0. diff --git a/lib/tx/psbtutil/AGENTS.md b/lib/tx/psbtutil/AGENTS.md index cf2e13849..c1591b48c 100644 --- a/lib/tx/psbtutil/AGENTS.md +++ b/lib/tx/psbtutil/AGENTS.md @@ -18,8 +18,9 @@ transaction semantics — callers run appropriate protocol validators. ## Relationships - **Depends on**: `lib/arkscript` (`SpendInfo` for taproot helpers). -- **Depended on by**: `lib/tx/oor` (package marshaling), `oor` (signing flow), - `db` (persistence layers). +- **Depended on by**: `lib/tx/oor` (package marshaling), `oor` (signing flow, + snapshot/artifact codecs), `db` (artifact store persistence), `unroll` + (proof assembly), `rpc/oorpb` / `darepod` (wire payload conversion). ## Invariants diff --git a/lib/tx/psbtutil/CLAUDE.md b/lib/tx/psbtutil/CLAUDE.md index cf2e13849..c1591b48c 100644 --- a/lib/tx/psbtutil/CLAUDE.md +++ b/lib/tx/psbtutil/CLAUDE.md @@ -18,8 +18,9 @@ transaction semantics — callers run appropriate protocol validators. ## Relationships - **Depends on**: `lib/arkscript` (`SpendInfo` for taproot helpers). -- **Depended on by**: `lib/tx/oor` (package marshaling), `oor` (signing flow), - `db` (persistence layers). +- **Depended on by**: `lib/tx/oor` (package marshaling), `oor` (signing flow, + snapshot/artifact codecs), `db` (artifact store persistence), `unroll` + (proof assembly), `rpc/oorpb` / `darepod` (wire payload conversion). ## Invariants diff --git a/lib/types/AGENTS.md b/lib/types/AGENTS.md index 82787805c..0c2eda85c 100644 --- a/lib/types/AGENTS.md +++ b/lib/types/AGENTS.md @@ -9,23 +9,26 @@ server during round participation. These types are used across `round`, `vtxo`, ## Key Types - `JoinRoundRequest` — Client's round registration request: boarding inputs, VTXO requests, forfeit requests, leave requests. -- `JoinRoundAuth` — Authentication data for round join (Schnorr signature proof-of-control). -- `VTXORequest` — Describes a new VTXO to create in a round (amount, - owner key, cosigner keys). `IsChange bool` (TLV record 4) marks the +- `JoinRoundAuth` — Round-join authentication: canonical signed `Message`, + `ValidFrom`/`ValidUntil` block-height window, and the full-format BIP-322 + `Signature` proof-of-control. +- `VTXORequest` — Describes a new VTXO to create in a round (amount, policy + template, owner key, signing key). `IsChange bool` (TLV record 4) marks the output that absorbs the server-computed fee residual under the #270 seal-time handshake; serialized into `JoinRoundAuth`. -- `ForfeitRequest` — Describes a VTXO being forfeited (outpoint, - connector leaf info, forfeit tx signature). Local-only fields: - `AuthSpend *arkscript.SpendPath` (proof-of-control path for custom-script - join-auth construction) and `ForfeitSpend *arkscript.SpendPath` (spend - path for forfeit tx building when the VTXO uses a non-standard policy). +- `ForfeitRequest` — Describes a VTXO being forfeited: `VTXOOutpoint`, + local-only `Amount`, plus optional `AuthSpend *arkscript.SpendPath` + (proof-of-control path for custom-script join-auth construction) and + `ForfeitSpend *arkscript.SpendPath` (spend path for forfeit tx building + when the VTXO uses a non-standard policy). - `LeaveRequest` — Describes a cooperative exit (VTXO outpoint, destination address). `IsChange bool` (TLV record 3) marks the leave output that absorbs the server fee residual; serialized into `JoinRoundAuth`. -- `BoardingRequest` — Describes a boarding input (outpoint, amount, script). - `TxProof fn.Option[proof.TxProof]` carries an optional SPV merkle - inclusion proof for server-side verification of boarding UTXOs without - requiring the server's own chain source. +- `BoardingRequest` — Describes a boarding input: `Outpoint`, + `PolicyTemplate` (authoritative join-round policy), `ClientKey` / + `OperatorKey`, and `ExitDelay`. `TxProof fn.Option[proof.TxProof]` carries + an optional SPV merkle inclusion proof for server-side verification of + boarding UTXOs without requiring the server's own chain source. - `OperatorTerms` — Server-published round parameters (fee rates, expiry config, connector dust amount). `MaxOORLineageVBytes uint32` carries the operator-published cap on the cumulative on-chain vbytes a recipient must publish to claim a VTXO produced by an OOR submit unilaterally. Zero means no cap enforced server-side (clients fall back to a conservative local default). - `Ancestry` — One rooted commitment-tree fragment contributing ancestry to a VTXO (defined in `lib/types/ancestry.go`). Fields: `TreePath *tree.Tree` (extracted root-to-leaf path), `CommitmentTxID chainhash.Hash`, `InputIndices []uint32` (Ark tx input indices this fragment serves; empty for round-direct VTXOs), `TreeDepth uint32`. Round-direct VTXOs carry a single-element slice; cross-round multi-input OOR VTXOs carry one entry per distinct commitment tx. - `MaxAncestryTreeDepth([]Ancestry) int` — Returns the largest `TreeDepth` across a slice; drives worst-case unilateral-exit timing calculations. @@ -33,7 +36,9 @@ server during round participation. These types are used across `round`, `vtxo`, - `BatchOutputInfo` — Batch output metadata (outpoint, value, tree root). - `ConnectorLeafInfo` — Assigned connector leaf (outpoint + output) plus the connector-tree ancestry params (`RootOutputIndex`, `NumLeaves`, `Radix`, `LeafIndex`) the client uses to reconstruct the tree and prove the leaf descends from the commitment tx before signing the forfeit (darepo-client#681). - `BoardingInputSignature` — Signed boarding input for round commitment. -- `ForfeitTxSig` — Forfeit transaction signature. +- `ForfeitTxSig` — Unsigned forfeit tx plus `ClientVTXOSig`, + `ParticipantVTXOSigs []*ForfeitParticipantSig` (multi-sig custom policies + such as vHTLC refunds), and the `SpendPath` used. - `OORPackageDirection` / `OORPackageLinkKind` — Enums for OOR package direction and link types. - `VTXORequest.EffectivePolicyTemplate` / `DecodePolicyTemplate` / `DecodeStandardPolicyTemplate` / `EffectivePkScript` — Policy helpers that decode the serialized `PolicyTemplate` field into an `arkscript.PolicyTemplate` and derive the output pkScript. - `BoardingRequest.EffectivePolicyTemplate` / `DecodePolicyTemplate` / `DecodeStandardPolicyTemplate` — Equivalent policy helpers for boarding inputs. @@ -43,13 +48,15 @@ server during round participation. These types are used across `round`, `vtxo`, ## Relationships - **Depends on**: `lib/arkscript` (policy template decoding, `StandardVTXOParams`), `lib/tree` (tree types, used by `Ancestry.TreePath`). -- **Depended on by**: `round` (round protocol messages), `wallet` (boarding types), `db` (persistence), `rpc` (proto conversion). +- **Depended on by**: `round` (round protocol messages), `wallet` (boarding + types), `db` (persistence), `vtxo` (descriptor ancestry), `oor` (OOR + package/session types), `rpc/roundpb` (proto conversion). ## Invariants - `VTXOOwnerKeyFamily` (44) is the HD key family used for deriving VTXO owner signing keys. - `VTXOSigningKeyFamily` (45) is the HD key family used for per-round VTXO MuSig2 signing keys. -- `JoinRoundAuthMessage` produces a deterministic byte encoding for Schnorr signature verification. +- `JoinRoundAuthMessage` produces a deterministic, versioned TLV byte encoding that the client signs (and the server verifies) via BIP-322. ## Deep Docs diff --git a/lib/types/CLAUDE.md b/lib/types/CLAUDE.md index 82787805c..0c2eda85c 100644 --- a/lib/types/CLAUDE.md +++ b/lib/types/CLAUDE.md @@ -9,23 +9,26 @@ server during round participation. These types are used across `round`, `vtxo`, ## Key Types - `JoinRoundRequest` — Client's round registration request: boarding inputs, VTXO requests, forfeit requests, leave requests. -- `JoinRoundAuth` — Authentication data for round join (Schnorr signature proof-of-control). -- `VTXORequest` — Describes a new VTXO to create in a round (amount, - owner key, cosigner keys). `IsChange bool` (TLV record 4) marks the +- `JoinRoundAuth` — Round-join authentication: canonical signed `Message`, + `ValidFrom`/`ValidUntil` block-height window, and the full-format BIP-322 + `Signature` proof-of-control. +- `VTXORequest` — Describes a new VTXO to create in a round (amount, policy + template, owner key, signing key). `IsChange bool` (TLV record 4) marks the output that absorbs the server-computed fee residual under the #270 seal-time handshake; serialized into `JoinRoundAuth`. -- `ForfeitRequest` — Describes a VTXO being forfeited (outpoint, - connector leaf info, forfeit tx signature). Local-only fields: - `AuthSpend *arkscript.SpendPath` (proof-of-control path for custom-script - join-auth construction) and `ForfeitSpend *arkscript.SpendPath` (spend - path for forfeit tx building when the VTXO uses a non-standard policy). +- `ForfeitRequest` — Describes a VTXO being forfeited: `VTXOOutpoint`, + local-only `Amount`, plus optional `AuthSpend *arkscript.SpendPath` + (proof-of-control path for custom-script join-auth construction) and + `ForfeitSpend *arkscript.SpendPath` (spend path for forfeit tx building + when the VTXO uses a non-standard policy). - `LeaveRequest` — Describes a cooperative exit (VTXO outpoint, destination address). `IsChange bool` (TLV record 3) marks the leave output that absorbs the server fee residual; serialized into `JoinRoundAuth`. -- `BoardingRequest` — Describes a boarding input (outpoint, amount, script). - `TxProof fn.Option[proof.TxProof]` carries an optional SPV merkle - inclusion proof for server-side verification of boarding UTXOs without - requiring the server's own chain source. +- `BoardingRequest` — Describes a boarding input: `Outpoint`, + `PolicyTemplate` (authoritative join-round policy), `ClientKey` / + `OperatorKey`, and `ExitDelay`. `TxProof fn.Option[proof.TxProof]` carries + an optional SPV merkle inclusion proof for server-side verification of + boarding UTXOs without requiring the server's own chain source. - `OperatorTerms` — Server-published round parameters (fee rates, expiry config, connector dust amount). `MaxOORLineageVBytes uint32` carries the operator-published cap on the cumulative on-chain vbytes a recipient must publish to claim a VTXO produced by an OOR submit unilaterally. Zero means no cap enforced server-side (clients fall back to a conservative local default). - `Ancestry` — One rooted commitment-tree fragment contributing ancestry to a VTXO (defined in `lib/types/ancestry.go`). Fields: `TreePath *tree.Tree` (extracted root-to-leaf path), `CommitmentTxID chainhash.Hash`, `InputIndices []uint32` (Ark tx input indices this fragment serves; empty for round-direct VTXOs), `TreeDepth uint32`. Round-direct VTXOs carry a single-element slice; cross-round multi-input OOR VTXOs carry one entry per distinct commitment tx. - `MaxAncestryTreeDepth([]Ancestry) int` — Returns the largest `TreeDepth` across a slice; drives worst-case unilateral-exit timing calculations. @@ -33,7 +36,9 @@ server during round participation. These types are used across `round`, `vtxo`, - `BatchOutputInfo` — Batch output metadata (outpoint, value, tree root). - `ConnectorLeafInfo` — Assigned connector leaf (outpoint + output) plus the connector-tree ancestry params (`RootOutputIndex`, `NumLeaves`, `Radix`, `LeafIndex`) the client uses to reconstruct the tree and prove the leaf descends from the commitment tx before signing the forfeit (darepo-client#681). - `BoardingInputSignature` — Signed boarding input for round commitment. -- `ForfeitTxSig` — Forfeit transaction signature. +- `ForfeitTxSig` — Unsigned forfeit tx plus `ClientVTXOSig`, + `ParticipantVTXOSigs []*ForfeitParticipantSig` (multi-sig custom policies + such as vHTLC refunds), and the `SpendPath` used. - `OORPackageDirection` / `OORPackageLinkKind` — Enums for OOR package direction and link types. - `VTXORequest.EffectivePolicyTemplate` / `DecodePolicyTemplate` / `DecodeStandardPolicyTemplate` / `EffectivePkScript` — Policy helpers that decode the serialized `PolicyTemplate` field into an `arkscript.PolicyTemplate` and derive the output pkScript. - `BoardingRequest.EffectivePolicyTemplate` / `DecodePolicyTemplate` / `DecodeStandardPolicyTemplate` — Equivalent policy helpers for boarding inputs. @@ -43,13 +48,15 @@ server during round participation. These types are used across `round`, `vtxo`, ## Relationships - **Depends on**: `lib/arkscript` (policy template decoding, `StandardVTXOParams`), `lib/tree` (tree types, used by `Ancestry.TreePath`). -- **Depended on by**: `round` (round protocol messages), `wallet` (boarding types), `db` (persistence), `rpc` (proto conversion). +- **Depended on by**: `round` (round protocol messages), `wallet` (boarding + types), `db` (persistence), `vtxo` (descriptor ancestry), `oor` (OOR + package/session types), `rpc/roundpb` (proto conversion). ## Invariants - `VTXOOwnerKeyFamily` (44) is the HD key family used for deriving VTXO owner signing keys. - `VTXOSigningKeyFamily` (45) is the HD key family used for per-round VTXO MuSig2 signing keys. -- `JoinRoundAuthMessage` produces a deterministic byte encoding for Schnorr signature verification. +- `JoinRoundAuthMessage` produces a deterministic, versioned TLV byte encoding that the client signs (and the server verifies) via BIP-322. ## Deep Docs diff --git a/lndbackend/AGENTS.md b/lndbackend/AGENTS.md index f1bb344a3..666452c00 100644 --- a/lndbackend/AGENTS.md +++ b/lndbackend/AGENTS.md @@ -2,29 +2,46 @@ ## Purpose -`BoardingBackend` and `ProofKeyBackend` implementations wrapping lndclient's -WalletKitClient for key derivation, taproot script import, UTXO enumeration, -and proof key signing via LND. +lndclient-backed implementations of wallet interfaces for connecting to +remote LND nodes: boarding UTXO/key management, remote signing (including +MuSig2) for the round actor, and proof-key derivation/signing. ## Key Types -- `BoardingBackend` — Struct holding `walletKit lndclient.WalletKitClient` and - `chainKit lndclient.ChainKitClient`. Implements `wallet.BoardingBackend` and - `wallet.OutputLeaser`. `GetTransaction` returns `*wallet.TxInfo`; `GetBlock` - fetches raw blocks via `chainKit`. Exposes `WalletKit() - lndclient.WalletKitClient` for callers that need operations beyond the - `BoardingBackend` interface (e.g., building the `LndClientTxBroadcaster` in - `chainbackends`). `LeaseOutput`/`ReleaseOutput` forward to walletKit, casting - `wallet.LockID` → `wtxmgr.LockID`. +- `BoardingBackend` — Wraps `lndclient.WalletKitClient` and `ChainKitClient`. + Implements `wallet.BoardingBackend` and `wallet.OutputLeaser`. + `GetTransaction` returns `*wallet.TxInfo`; `GetBlock` fetches raw blocks via + `chainKit` for TxProof merkle inclusion. `ListUnspent` spans every wallet + account including imported watch-only scripts; `ListUnspentDefaultAccount` + restricts to the default account for CPFP fee-input selection (watch-only + outputs are unsignable). `LeaseOutput`/`ReleaseOutput` forward to + walletKit, casting `wallet.LockID` <-> `wtxmgr.LockID`. +- `ClientWallet` — Adapts lndclient's remote signer to `input.Signer` + + MuSig2 (`round.ClientWallet`), so the round actor can sign VTXO tree + branches and forfeit transactions via LND's remote signer without a local + wallet. Uses a background context internally since `input.Signer` carries + none; relies on the lndclient dial-option gRPC deadline instead. - `ProofKeyBackend` — Implements `proofkeys.Backend` for LND-backed key - derivation and Schnorr proof signing. Wraps `walletKit` for `DeriveKey`, - `DeriveNextKey`, and produces `indexer.SchnorrSigner` instances. + derivation and Schnorr proof signing. Wraps `walletKit`/`signer` for + `DeriveKey`, `DeriveNextKey`, and produces `indexer.SchnorrSigner` + instances via `indexer.NewLNDSchnorrSigner`. ## Relationships -- **Depends on**: `wallet` (implements `BoardingBackend`), `proofkeys` - (implements `Backend`), `indexer` (SchnorrSigner interface). -- **Depended on by**: `darepod` (LND-backed wallet mode). +- **Depends on**: `wallet` (implements `BoardingBackend`/`OutputLeaser`), + `proofkeys` (implements `Backend`), `indexer` (`SchnorrSigner`), `build` + (context logger fallback). +- **Depended on by**: `darepod` (LND-backed wallet mode, all three types), + root `main` package (`lnd_boarding_wallet.go` back-compat alias), `systest`. + +## Invariants + +- `ClientWallet.signOutputRawWithLocator` always forwards the key locator + when set (including family != 0, index == 0), working around an lndclient + gap that otherwise breaks the family-6/index-0 identity signing path. +- CPFP fee-input selection must use `ListUnspentDefaultAccount`, not + `ListUnspent`: offering a watch-only (imported script) output as a fee + input makes the child PSBT unsignable. ## Deep Docs diff --git a/lndbackend/CLAUDE.md b/lndbackend/CLAUDE.md index f1bb344a3..666452c00 100644 --- a/lndbackend/CLAUDE.md +++ b/lndbackend/CLAUDE.md @@ -2,29 +2,46 @@ ## Purpose -`BoardingBackend` and `ProofKeyBackend` implementations wrapping lndclient's -WalletKitClient for key derivation, taproot script import, UTXO enumeration, -and proof key signing via LND. +lndclient-backed implementations of wallet interfaces for connecting to +remote LND nodes: boarding UTXO/key management, remote signing (including +MuSig2) for the round actor, and proof-key derivation/signing. ## Key Types -- `BoardingBackend` — Struct holding `walletKit lndclient.WalletKitClient` and - `chainKit lndclient.ChainKitClient`. Implements `wallet.BoardingBackend` and - `wallet.OutputLeaser`. `GetTransaction` returns `*wallet.TxInfo`; `GetBlock` - fetches raw blocks via `chainKit`. Exposes `WalletKit() - lndclient.WalletKitClient` for callers that need operations beyond the - `BoardingBackend` interface (e.g., building the `LndClientTxBroadcaster` in - `chainbackends`). `LeaseOutput`/`ReleaseOutput` forward to walletKit, casting - `wallet.LockID` → `wtxmgr.LockID`. +- `BoardingBackend` — Wraps `lndclient.WalletKitClient` and `ChainKitClient`. + Implements `wallet.BoardingBackend` and `wallet.OutputLeaser`. + `GetTransaction` returns `*wallet.TxInfo`; `GetBlock` fetches raw blocks via + `chainKit` for TxProof merkle inclusion. `ListUnspent` spans every wallet + account including imported watch-only scripts; `ListUnspentDefaultAccount` + restricts to the default account for CPFP fee-input selection (watch-only + outputs are unsignable). `LeaseOutput`/`ReleaseOutput` forward to + walletKit, casting `wallet.LockID` <-> `wtxmgr.LockID`. +- `ClientWallet` — Adapts lndclient's remote signer to `input.Signer` + + MuSig2 (`round.ClientWallet`), so the round actor can sign VTXO tree + branches and forfeit transactions via LND's remote signer without a local + wallet. Uses a background context internally since `input.Signer` carries + none; relies on the lndclient dial-option gRPC deadline instead. - `ProofKeyBackend` — Implements `proofkeys.Backend` for LND-backed key - derivation and Schnorr proof signing. Wraps `walletKit` for `DeriveKey`, - `DeriveNextKey`, and produces `indexer.SchnorrSigner` instances. + derivation and Schnorr proof signing. Wraps `walletKit`/`signer` for + `DeriveKey`, `DeriveNextKey`, and produces `indexer.SchnorrSigner` + instances via `indexer.NewLNDSchnorrSigner`. ## Relationships -- **Depends on**: `wallet` (implements `BoardingBackend`), `proofkeys` - (implements `Backend`), `indexer` (SchnorrSigner interface). -- **Depended on by**: `darepod` (LND-backed wallet mode). +- **Depends on**: `wallet` (implements `BoardingBackend`/`OutputLeaser`), + `proofkeys` (implements `Backend`), `indexer` (`SchnorrSigner`), `build` + (context logger fallback). +- **Depended on by**: `darepod` (LND-backed wallet mode, all three types), + root `main` package (`lnd_boarding_wallet.go` back-compat alias), `systest`. + +## Invariants + +- `ClientWallet.signOutputRawWithLocator` always forwards the key locator + when set (including family != 0, index == 0), working around an lndclient + gap that otherwise breaks the family-6/index-0 identity signing path. +- CPFP fee-input selection must use `ListUnspentDefaultAccount`, not + `ListUnspent`: offering a watch-only (imported script) output as a fee + input makes the child PSBT unsignable. ## Deep Docs diff --git a/lwwallet/AGENTS.md b/lwwallet/AGENTS.md index bfeb325b1..2163bf7e5 100644 --- a/lwwallet/AGENTS.md +++ b/lwwallet/AGENTS.md @@ -2,10 +2,12 @@ ## Purpose -Lightweight in-process Bitcoin wallet backed by LND's btcwallet for HD key -management and a shared Esplora/mempool.space chain backend. Self-contained -without an external LND node. Implements `wallet.BoardingBackend`, -`input.Signer` + MuSig2, and `chainsource.ChainBackend`. +Lightweight in-process Bitcoin wallet backed by LND's btcwallet and an +Esplora/mempool.space chain backend. Self-contained without an external LND +node. Implements `wallet.BoardingBackend`, `input.Signer` + MuSig2, and +`chainsource.ChainBackend`. Shares HD key management, signing, and boarding +base logic with the neutrino-backed `btcwbackend` sibling via the extracted +`walletcore` package. ## Key Types @@ -43,23 +45,28 @@ without an external LND node. Implements `wallet.BoardingBackend`, - `EsploraChainService` — `chain.Interface` adapter over `EsploraClient`, driven by a shared `TipPoller`. Feeds btcwallet's internal address-credit pipeline. Constructor: `NewEsploraChainService(esplora, tipPoller, logger)`. -- `BoardingBackendAdapter` — Implements `wallet.BoardingBackend` and - `wallet.OutputLeaser`. Queries Esplora directly for UTXOs (bypasses +- `BoardingBackendAdapter` — Embeds `walletcore.BoardingBackendBase` for + shared key derivation/script import; implements `wallet.BoardingBackend` + and `wallet.OutputLeaser`. Queries Esplora directly for UTXOs (bypasses btcwallet's UTXO tracking because btcwallet skips credit marking for non-default key scopes like m/1017'). `LeaseOutput`/`ReleaseOutput` forward to btcwallet's native lock table. -- `Wallet.WaitForSync(ctx)` — Blocks until btcwallet's internal height catches - the Esplora tip, closing the race between the chain backend actor and - btcwallet's asynchronous block processing pipeline. Polls at 50ms. -- `Wallet.FinalizePsbtDirect(packet)` — Signs and finalizes a PSBT via - `BtcWallet.FinalizePsbt` under `DefaultAccountName`. Used by the darepod +- `Wallet` — Embeds `walletcore.Wallet` for shared btcwallet operations, adding + the Esplora chain source. `WaitForSync(ctx)` blocks until btcwallet's + internal height catches the Esplora tip, closing the race between the chain + backend actor and btcwallet's asynchronous block processing pipeline (polls + at 50ms). `FinalizePsbtDirect(packet)` signs and finalizes a PSBT via + `BtcWallet.FinalizePsbt` under `DefaultAccountName`; used by the darepod unroll sweep adapter since lwwallet has no gRPC surface. ## Relationships -- **Depends on**: `chainsource` (implements `ChainBackend`), `wallet` - (implements `BoardingBackend`). -- **Depended on by**: `darepod` (alternative to LND-backed wallet). +- **Depends on**: `walletcore` (shared HD key mgmt, signing, boarding base — + also used by `btcwbackend`), `chainsource` (implements `ChainBackend`), + `wallet` (implements `BoardingBackend`), `chainbackends` (typed + `PackageTxError` for package-relay results). +- **Depended on by**: `darepod` (alternative to LND-backed wallet), `sdk` + (embedded-wallet config references). ## Invariants diff --git a/lwwallet/CLAUDE.md b/lwwallet/CLAUDE.md index bfeb325b1..2163bf7e5 100644 --- a/lwwallet/CLAUDE.md +++ b/lwwallet/CLAUDE.md @@ -2,10 +2,12 @@ ## Purpose -Lightweight in-process Bitcoin wallet backed by LND's btcwallet for HD key -management and a shared Esplora/mempool.space chain backend. Self-contained -without an external LND node. Implements `wallet.BoardingBackend`, -`input.Signer` + MuSig2, and `chainsource.ChainBackend`. +Lightweight in-process Bitcoin wallet backed by LND's btcwallet and an +Esplora/mempool.space chain backend. Self-contained without an external LND +node. Implements `wallet.BoardingBackend`, `input.Signer` + MuSig2, and +`chainsource.ChainBackend`. Shares HD key management, signing, and boarding +base logic with the neutrino-backed `btcwbackend` sibling via the extracted +`walletcore` package. ## Key Types @@ -43,23 +45,28 @@ without an external LND node. Implements `wallet.BoardingBackend`, - `EsploraChainService` — `chain.Interface` adapter over `EsploraClient`, driven by a shared `TipPoller`. Feeds btcwallet's internal address-credit pipeline. Constructor: `NewEsploraChainService(esplora, tipPoller, logger)`. -- `BoardingBackendAdapter` — Implements `wallet.BoardingBackend` and - `wallet.OutputLeaser`. Queries Esplora directly for UTXOs (bypasses +- `BoardingBackendAdapter` — Embeds `walletcore.BoardingBackendBase` for + shared key derivation/script import; implements `wallet.BoardingBackend` + and `wallet.OutputLeaser`. Queries Esplora directly for UTXOs (bypasses btcwallet's UTXO tracking because btcwallet skips credit marking for non-default key scopes like m/1017'). `LeaseOutput`/`ReleaseOutput` forward to btcwallet's native lock table. -- `Wallet.WaitForSync(ctx)` — Blocks until btcwallet's internal height catches - the Esplora tip, closing the race between the chain backend actor and - btcwallet's asynchronous block processing pipeline. Polls at 50ms. -- `Wallet.FinalizePsbtDirect(packet)` — Signs and finalizes a PSBT via - `BtcWallet.FinalizePsbt` under `DefaultAccountName`. Used by the darepod +- `Wallet` — Embeds `walletcore.Wallet` for shared btcwallet operations, adding + the Esplora chain source. `WaitForSync(ctx)` blocks until btcwallet's + internal height catches the Esplora tip, closing the race between the chain + backend actor and btcwallet's asynchronous block processing pipeline (polls + at 50ms). `FinalizePsbtDirect(packet)` signs and finalizes a PSBT via + `BtcWallet.FinalizePsbt` under `DefaultAccountName`; used by the darepod unroll sweep adapter since lwwallet has no gRPC surface. ## Relationships -- **Depends on**: `chainsource` (implements `ChainBackend`), `wallet` - (implements `BoardingBackend`). -- **Depended on by**: `darepod` (alternative to LND-backed wallet). +- **Depends on**: `walletcore` (shared HD key mgmt, signing, boarding base — + also used by `btcwbackend`), `chainsource` (implements `ChainBackend`), + `wallet` (implements `BoardingBackend`), `chainbackends` (typed + `PackageTxError` for package-relay results). +- **Depended on by**: `darepod` (alternative to LND-backed wallet), `sdk` + (embedded-wallet config references). ## Invariants diff --git a/mailbox/AGENTS.md b/mailbox/AGENTS.md index d8e7382aa..d2338b5b0 100644 --- a/mailbox/AGENTS.md +++ b/mailbox/AGENTS.md @@ -25,8 +25,13 @@ primitives for durable transport (conn). ## Relationships -- **Depends on**: `baselib/actor` (for serverconn integration). -- **Depended on by**: `serverconn` (constructs envelopes, uses RPCClient, manages AckState), `darepod` (server-side mailbox). +- **Depends on**: `baselib/actor` (`conn` uses Promise/Future for response + correlation). +- **Depended on by**: generated `*_mailboxrpc.pb.go` stubs across the repo + (e.g. `arkrpc`, `oor`, `round`, `swaprpc`, `daemonrpc`) depend on + `mailbox/rpc`'s runtime interfaces; `serverconn` and `darepod` depend on + all three sub-packages to construct envelopes, route RPCs, and manage ack + watermarks. ## Invariants diff --git a/mailbox/CLAUDE.md b/mailbox/CLAUDE.md index d8e7382aa..d2338b5b0 100644 --- a/mailbox/CLAUDE.md +++ b/mailbox/CLAUDE.md @@ -25,8 +25,13 @@ primitives for durable transport (conn). ## Relationships -- **Depends on**: `baselib/actor` (for serverconn integration). -- **Depended on by**: `serverconn` (constructs envelopes, uses RPCClient, manages AckState), `darepod` (server-side mailbox). +- **Depends on**: `baselib/actor` (`conn` uses Promise/Future for response + correlation). +- **Depended on by**: generated `*_mailboxrpc.pb.go` stubs across the repo + (e.g. `arkrpc`, `oor`, `round`, `swaprpc`, `daemonrpc`) depend on + `mailbox/rpc`'s runtime interfaces; `serverconn` and `darepod` depend on + all three sub-packages to construct envelopes, route RPCs, and manage ack + watermarks. ## Invariants diff --git a/mailbox/conn/AGENTS.md b/mailbox/conn/AGENTS.md index 6affc7e4f..da55473a2 100644 --- a/mailbox/conn/AGENTS.md +++ b/mailbox/conn/AGENTS.md @@ -40,6 +40,14 @@ delivery. buffered response cleanup. - `ErrWaiterExpired` / `ErrWaiterCancelled` — Sentinel errors signaled to blocked `AwaitRPC` callers when a waiter is pruned or explicitly removed. +- `StatusError` — Wraps a non-OK `mailboxpb.Status` from a Send/Pull/AckUpTo + call, preserving the op, message, code, and advertised supported versions. + `IsPermanentVersion()` / package-level `IsPermanentVersionError(err)` + classify one of four permanent version-compatibility codes + (`StatusMailboxVersionUnsupported`, `StatusArkVersionUnsupported`, + `StatusArkVersionMismatch`, `StatusUpgradeRequired`) so durable senders know + to stop retrying and dead-letter the message instead of treating it as a + transient failure. ## Relationships diff --git a/mailbox/conn/CLAUDE.md b/mailbox/conn/CLAUDE.md index 6affc7e4f..da55473a2 100644 --- a/mailbox/conn/CLAUDE.md +++ b/mailbox/conn/CLAUDE.md @@ -40,6 +40,14 @@ delivery. buffered response cleanup. - `ErrWaiterExpired` / `ErrWaiterCancelled` — Sentinel errors signaled to blocked `AwaitRPC` callers when a waiter is pruned or explicitly removed. +- `StatusError` — Wraps a non-OK `mailboxpb.Status` from a Send/Pull/AckUpTo + call, preserving the op, message, code, and advertised supported versions. + `IsPermanentVersion()` / package-level `IsPermanentVersionError(err)` + classify one of four permanent version-compatibility codes + (`StatusMailboxVersionUnsupported`, `StatusArkVersionUnsupported`, + `StatusArkVersionMismatch`, `StatusUpgradeRequired`) so durable senders know + to stop retrying and dead-letter the message instead of treating it as a + transient failure. ## Relationships diff --git a/mailbox/rpc/AGENTS.md b/mailbox/rpc/AGENTS.md index 1d5e7d596..b1afa3a24 100644 --- a/mailbox/rpc/AGENTS.md +++ b/mailbox/rpc/AGENTS.md @@ -26,6 +26,10 @@ and server-side routing need without including any transport implementation. successful `SendRPC` call. Callers pass `CorrelationID` to `AwaitRPC`. - `RPCOptions` — Per-call overrides: `IdempotencyKey`, `CorrelationID`, `Headers`. All fields are optional; zero values use implementation defaults. +- `EncodeErrorHeaders` / `DecodeErrorHeaders` — Round-trip a gRPC `error` as a + base64-encoded `google.rpc.Status` under the `HeaderGRPCStatusB64` envelope + header, so a failed handler call can surface a typed error across the + mailbox instead of a response body. ## Relationships @@ -44,6 +48,9 @@ and server-side routing need without including any transport implementation. redeliver the same `idempotency_key` after a crash. - `ServiceMethod.Service` uses the fully-qualified protobuf package + service name, not the Go package path. +- A `KIND_RESPONSE` envelope carrying `HeaderGRPCStatusB64` signals a failed + RPC; receivers must decode it via `DecodeErrorHeaders` before attempting to + unmarshal the body. ## Deep Docs diff --git a/mailbox/rpc/CLAUDE.md b/mailbox/rpc/CLAUDE.md index 1d5e7d596..b1afa3a24 100644 --- a/mailbox/rpc/CLAUDE.md +++ b/mailbox/rpc/CLAUDE.md @@ -26,6 +26,10 @@ and server-side routing need without including any transport implementation. successful `SendRPC` call. Callers pass `CorrelationID` to `AwaitRPC`. - `RPCOptions` — Per-call overrides: `IdempotencyKey`, `CorrelationID`, `Headers`. All fields are optional; zero values use implementation defaults. +- `EncodeErrorHeaders` / `DecodeErrorHeaders` — Round-trip a gRPC `error` as a + base64-encoded `google.rpc.Status` under the `HeaderGRPCStatusB64` envelope + header, so a failed handler call can surface a typed error across the + mailbox instead of a response body. ## Relationships @@ -44,6 +48,9 @@ and server-side routing need without including any transport implementation. redeliver the same `idempotency_key` after a crash. - `ServiceMethod.Service` uses the fully-qualified protobuf package + service name, not the Go package path. +- A `KIND_RESPONSE` envelope carrying `HeaderGRPCStatusB64` signals a failed + RPC; receivers must decode it via `DecodeErrorHeaders` before attempting to + unmarshal the body. ## Deep Docs diff --git a/metrics/AGENTS.md b/metrics/AGENTS.md index a117b6dcf..f2306b876 100644 --- a/metrics/AGENTS.md +++ b/metrics/AGENTS.md @@ -4,79 +4,65 @@ Prometheus instrumentation for the darepo client daemon (`darepod`). All metrics are namespaced under `darepod_`. Mirrors the arkd **server** metrics -package (one directory up) in structure and collection strategy. +package (one directory up) in structure and collection strategy: an +event-driven actor for lifecycle counters, plus a scrape-time collector for +gauges that must stay fresh (VTXO inventory, wallet balance, chain tip, live +OOR/round state). -Two collection strategies: +## Key Types -1. **Event-driven** — `MetricsActor` receives typed fire-and-forget messages - (`metrics.Msg`) and increments lifecycle counters. All instrumentation - logic lives in the actor; no call site touches Prometheus directly. -2. **Scrape-driven** — `SystemCollector` implements `prometheus.Collector` and - queries client system state on each scrape (VTXO inventory, on-chain wallet - balance, chain tip, live OOR/round actor state) so balance/inventory/health - gauges stay fresh without a ticker. Each source is collected independently; - a not-ready source skips only its own gauges. - -Also provides the opt-in HTTP `/metrics` server and the shared -`GRPCClientMetrics` for client-side gRPC interceptors. - -## Key Concepts - -Use `go doc metrics.` for signatures. - -- **`MetricsActor`** / **`ActorConfig`** — event-driven counters. The daemon - spawns a small **pool** of these (`metricsActorWorkers`, currently 4) all - registered under `ActorKey`. The actor is stateless (it only `Inc()`s - concurrency-safe Prometheus counters), so workers drain events in parallel. -- **`Sink`** / **`NewSink`** — fire-and-forget reference, resolved from the - actor system via the service key. `ActorKey.Ref` returns a **round-robin - router** over every actor registered under the key, so a single `Sink` fans - Tells across the worker pool with no change at producer call sites. Mirrors - `ledger.Sink`. -- **`SystemCollector`** + **`SystemStatsQuerier`** — scrape-driven gauges - (VTXO inventory + value, on-chain `wallet_confirmed/unconfirmed_satoshis`, - `block_height`, live `oor_sessions_by_state`, live `rounds_by_status`). The - `darepod` `systemStatsAdapter` implements the querier, delegating to the VTXO - store, the wallet backend, the chain backend, and the live OOR/round actors. - The `*_by_state`/`*_by_status` gauges read only live actors (bounded); - lifetime totals live in the `_total` counters. -- **`Server`** / **`ServerConfig`** — opt-in HTTP scrape endpoint. Disabled - unless `ListenAddr` is set. -- **`GRPCClientMetrics`** — shared - `go-grpc-middleware/providers/prometheus` `ClientMetrics`, installed as - interceptors on the operator gRPC connection in `darepod.dialServer`. -- **`RegisterAll`** — registers event-driven collectors; tolerates duplicate - registration (multiple daemons in one test process). +- `MetricsActor` / `ActorConfig` — event-driven counters. `darepod` spawns a + small pool of these (`metricsActorWorkers = 4`, in `darepod/metrics.go`) + registered under `ActorKey`; the actor holds no mutable state, so any + worker can handle any message. +- `Msg` — sealed interface for actor messages: `BoardingEventMsg`, + `RoundJoinedMsg`, `RoundCompletedMsg`, `OORTransferReceivedMsg`, + `OORTransferSentMsg`, `BackgroundTaskErrorMsg`. +- `Sink` (`= actor.TellOnlyRef[Msg]`) / `NewSink` — fire-and-forget handle + resolved from the actor system via `ActorKey`, which round-robins Tells + across the worker pool. +- `SystemCollector` / `SystemStatsQuerier` — `prometheus.Collector` that + queries live client state on each scrape (VTXO inventory/value, wallet + balance, block height, `oor_sessions_by_state`, `rounds_by_status`). Each + querier method is collected independently; an error only suppresses that + method's gauges for the scrape. +- `Server` / `ServerConfig` — opt-in HTTP `/metrics` endpoint; disabled + unless `ServerConfig.ListenAddr` is set. +- `GRPCClientMetrics` — shared `go-grpc-middleware/providers/prometheus` + `ClientMetrics`, installed as interceptors on the operator gRPC connection. ## Relationships -- **Depends on**: `baselib/actor` (framework, sink), `btclog`, +- **Depends on**: `baselib/actor` (actor framework, `Sink`), `btclog`, `lnd/fn/v2`, `client_golang/prometheus`, `go-grpc-middleware/providers/prometheus`. -- **Depended on by**: `darepod` (config field, server start/stop, collector - adapter, actor-pool spawn, emission sites, connection watcher, gRPC - interceptors), `round` (`RoundClientConfig.MetricsSink`, emits - `RoundCompletedMsg` at terminal round outcomes), `wallet` - (`WithMetricsSink`, emits `BackgroundTaskErrorMsg` from the boarding-sweep - watcher), `vtxo` (`IncomingVTXOHandlerConfig.MetricsSink`, emits - `OORTransferReceivedMsg` from the incoming-VTXO materialization path). +- **Depended on by**: `darepod` (spawns the actor pool, owns `SystemCollector` + adapter and HTTP server, emits `BoardingEventMsg`/`OORTransferSentMsg` etc. + directly), `round` (`RoundClientConfig.MetricsSink` emits + `RoundJoinedMsg`/`RoundCompletedMsg`), `wallet` (`WithMetricsSink` emits + `BackgroundTaskErrorMsg` from the boarding-sweep watcher), `vtxo` + (`IncomingVTXOHandlerConfig.MetricsSink` emits `OORTransferReceivedMsg`). +- **Messages to/from**: Receives `Msg` (`BoardingEventMsg`, `RoundJoinedMsg`, + `RoundCompletedMsg`, `OORTransferReceivedMsg`, `OORTransferSentMsg`, + `BackgroundTaskErrorMsg`) <- `round`, `wallet`, `vtxo`, `darepod`. ## Invariants -- All event-driven updates go through `MetricsActor.Receive`. The actor holds - no mutable state (counters live in package-level Prometheus vectors), so the - worker pool is safe: any worker may handle any message. -- Scrape-driven (VTXO) gauges are collected by `SystemCollector` at scrape - time, not the actor. -- The metrics server is **opt-in**: empty `ServerConfig.ListenAddr` disables - everything (no HTTP server, no actor spawn, no collector registration). -- `RegisterAll` uses `Register` + `AlreadyRegisteredError`, not `MustRegister`. +- All event-driven updates go through `MetricsActor.Receive`; counters live + in package-level Prometheus vectors, not actor state, so the worker pool is + safe for concurrent delivery. +- Scrape-driven gauges are collected by `SystemCollector` at scrape time, not + by the actor. +- The metrics server is opt-in: empty `ServerConfig.ListenAddr` disables the + HTTP server, actor spawn, and collector registration. +- `RegisterAll` uses `Register` + tolerates `AlreadyRegisteredError`, not + `MustRegister` (multiple daemons may share a process in tests). - Non-counter metrics must not use the `_total` suffix (promlinter enforced). -- Emission never blocks or fails the operation being recorded: - `Server.emitMetric` Tells through an `fn.Option[Sink]` and only debug-logs a - Tell error. +- Emission never blocks or fails the operation being recorded: emit sites + Tell through an `fn.Option[Sink]` and only debug-log a Tell error. ## Deep Docs -- [README.md](README.md) — Full metrics reference table (names, types, labels, - status enum values) for building Grafana dashboards. +- [README.md](README.md) — Full metrics reference table (names, types, + labels, status enum values) for building Grafana dashboards. +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/metrics/CLAUDE.md b/metrics/CLAUDE.md index a117b6dcf..f2306b876 100644 --- a/metrics/CLAUDE.md +++ b/metrics/CLAUDE.md @@ -4,79 +4,65 @@ Prometheus instrumentation for the darepo client daemon (`darepod`). All metrics are namespaced under `darepod_`. Mirrors the arkd **server** metrics -package (one directory up) in structure and collection strategy. +package (one directory up) in structure and collection strategy: an +event-driven actor for lifecycle counters, plus a scrape-time collector for +gauges that must stay fresh (VTXO inventory, wallet balance, chain tip, live +OOR/round state). -Two collection strategies: +## Key Types -1. **Event-driven** — `MetricsActor` receives typed fire-and-forget messages - (`metrics.Msg`) and increments lifecycle counters. All instrumentation - logic lives in the actor; no call site touches Prometheus directly. -2. **Scrape-driven** — `SystemCollector` implements `prometheus.Collector` and - queries client system state on each scrape (VTXO inventory, on-chain wallet - balance, chain tip, live OOR/round actor state) so balance/inventory/health - gauges stay fresh without a ticker. Each source is collected independently; - a not-ready source skips only its own gauges. - -Also provides the opt-in HTTP `/metrics` server and the shared -`GRPCClientMetrics` for client-side gRPC interceptors. - -## Key Concepts - -Use `go doc metrics.` for signatures. - -- **`MetricsActor`** / **`ActorConfig`** — event-driven counters. The daemon - spawns a small **pool** of these (`metricsActorWorkers`, currently 4) all - registered under `ActorKey`. The actor is stateless (it only `Inc()`s - concurrency-safe Prometheus counters), so workers drain events in parallel. -- **`Sink`** / **`NewSink`** — fire-and-forget reference, resolved from the - actor system via the service key. `ActorKey.Ref` returns a **round-robin - router** over every actor registered under the key, so a single `Sink` fans - Tells across the worker pool with no change at producer call sites. Mirrors - `ledger.Sink`. -- **`SystemCollector`** + **`SystemStatsQuerier`** — scrape-driven gauges - (VTXO inventory + value, on-chain `wallet_confirmed/unconfirmed_satoshis`, - `block_height`, live `oor_sessions_by_state`, live `rounds_by_status`). The - `darepod` `systemStatsAdapter` implements the querier, delegating to the VTXO - store, the wallet backend, the chain backend, and the live OOR/round actors. - The `*_by_state`/`*_by_status` gauges read only live actors (bounded); - lifetime totals live in the `_total` counters. -- **`Server`** / **`ServerConfig`** — opt-in HTTP scrape endpoint. Disabled - unless `ListenAddr` is set. -- **`GRPCClientMetrics`** — shared - `go-grpc-middleware/providers/prometheus` `ClientMetrics`, installed as - interceptors on the operator gRPC connection in `darepod.dialServer`. -- **`RegisterAll`** — registers event-driven collectors; tolerates duplicate - registration (multiple daemons in one test process). +- `MetricsActor` / `ActorConfig` — event-driven counters. `darepod` spawns a + small pool of these (`metricsActorWorkers = 4`, in `darepod/metrics.go`) + registered under `ActorKey`; the actor holds no mutable state, so any + worker can handle any message. +- `Msg` — sealed interface for actor messages: `BoardingEventMsg`, + `RoundJoinedMsg`, `RoundCompletedMsg`, `OORTransferReceivedMsg`, + `OORTransferSentMsg`, `BackgroundTaskErrorMsg`. +- `Sink` (`= actor.TellOnlyRef[Msg]`) / `NewSink` — fire-and-forget handle + resolved from the actor system via `ActorKey`, which round-robins Tells + across the worker pool. +- `SystemCollector` / `SystemStatsQuerier` — `prometheus.Collector` that + queries live client state on each scrape (VTXO inventory/value, wallet + balance, block height, `oor_sessions_by_state`, `rounds_by_status`). Each + querier method is collected independently; an error only suppresses that + method's gauges for the scrape. +- `Server` / `ServerConfig` — opt-in HTTP `/metrics` endpoint; disabled + unless `ServerConfig.ListenAddr` is set. +- `GRPCClientMetrics` — shared `go-grpc-middleware/providers/prometheus` + `ClientMetrics`, installed as interceptors on the operator gRPC connection. ## Relationships -- **Depends on**: `baselib/actor` (framework, sink), `btclog`, +- **Depends on**: `baselib/actor` (actor framework, `Sink`), `btclog`, `lnd/fn/v2`, `client_golang/prometheus`, `go-grpc-middleware/providers/prometheus`. -- **Depended on by**: `darepod` (config field, server start/stop, collector - adapter, actor-pool spawn, emission sites, connection watcher, gRPC - interceptors), `round` (`RoundClientConfig.MetricsSink`, emits - `RoundCompletedMsg` at terminal round outcomes), `wallet` - (`WithMetricsSink`, emits `BackgroundTaskErrorMsg` from the boarding-sweep - watcher), `vtxo` (`IncomingVTXOHandlerConfig.MetricsSink`, emits - `OORTransferReceivedMsg` from the incoming-VTXO materialization path). +- **Depended on by**: `darepod` (spawns the actor pool, owns `SystemCollector` + adapter and HTTP server, emits `BoardingEventMsg`/`OORTransferSentMsg` etc. + directly), `round` (`RoundClientConfig.MetricsSink` emits + `RoundJoinedMsg`/`RoundCompletedMsg`), `wallet` (`WithMetricsSink` emits + `BackgroundTaskErrorMsg` from the boarding-sweep watcher), `vtxo` + (`IncomingVTXOHandlerConfig.MetricsSink` emits `OORTransferReceivedMsg`). +- **Messages to/from**: Receives `Msg` (`BoardingEventMsg`, `RoundJoinedMsg`, + `RoundCompletedMsg`, `OORTransferReceivedMsg`, `OORTransferSentMsg`, + `BackgroundTaskErrorMsg`) <- `round`, `wallet`, `vtxo`, `darepod`. ## Invariants -- All event-driven updates go through `MetricsActor.Receive`. The actor holds - no mutable state (counters live in package-level Prometheus vectors), so the - worker pool is safe: any worker may handle any message. -- Scrape-driven (VTXO) gauges are collected by `SystemCollector` at scrape - time, not the actor. -- The metrics server is **opt-in**: empty `ServerConfig.ListenAddr` disables - everything (no HTTP server, no actor spawn, no collector registration). -- `RegisterAll` uses `Register` + `AlreadyRegisteredError`, not `MustRegister`. +- All event-driven updates go through `MetricsActor.Receive`; counters live + in package-level Prometheus vectors, not actor state, so the worker pool is + safe for concurrent delivery. +- Scrape-driven gauges are collected by `SystemCollector` at scrape time, not + by the actor. +- The metrics server is opt-in: empty `ServerConfig.ListenAddr` disables the + HTTP server, actor spawn, and collector registration. +- `RegisterAll` uses `Register` + tolerates `AlreadyRegisteredError`, not + `MustRegister` (multiple daemons may share a process in tests). - Non-counter metrics must not use the `_total` suffix (promlinter enforced). -- Emission never blocks or fails the operation being recorded: - `Server.emitMetric` Tells through an `fn.Option[Sink]` and only debug-logs a - Tell error. +- Emission never blocks or fails the operation being recorded: emit sites + Tell through an `fn.Option[Sink]` and only debug-log a Tell error. ## Deep Docs -- [README.md](README.md) — Full metrics reference table (names, types, labels, - status enum values) for building Grafana dashboards. +- [README.md](README.md) — Full metrics reference table (names, types, + labels, status enum values) for building Grafana dashboards. +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/oor/AGENTS.md b/oor/AGENTS.md index 0bd1ecfbd..e4dfd00b5 100644 --- a/oor/AGENTS.md +++ b/oor/AGENTS.md @@ -2,410 +2,128 @@ ## Purpose -Client-side out-of-round (OOR) VTXO transfer coordination without waiting for -normal rounds, preserving deterministic transaction construction and crash-safe -resume semantics. +Client-side out-of-round (OOR) VTXO transfer coordination: lets a client send +VTXOs to one or more recipients without waiting for a normal round, while +keeping transaction construction deterministic and resume semantics +crash-safe. Built on `baselib/protofsm`: I/O is modeled as outbox requests +that a durable actor executes and feeds back as events. ## Key Types For field-level detail, use `go doc github.com/lightninglabs/darepo-client/oor.`. -State transitions and validation rules live under [Invariants](#invariants). -### Per-Session Actor Model (current) - -The daemon now runs **one durable actor per OOR session** instead of one global -actor. See [docs/oor_subsystem.md](../docs/oor_subsystem.md) for the full design. - -- `OORSessionActor` / `sessionBehavior` — one durable actor per session on the - Read/Commit execution path. The FSM emits outbox events as before, but the - actor handles them itself in one shared `driveOutbox` switch (sign inline, - enqueue cross-actor transport to serverconn, materialize incoming VTXOs, - schedule retries) rather than routing them through an `OutboxHandler`. -- `OORRegistryActor` — thin coordinator registered under the OOR service key, - with a **durable inbound mailbox** (Read/Stage/Commit path, mailbox id - `oor-client`): a server-push event is persisted before the ingress loop acks - the operator envelope, so a crash between ingress and the per-session child - replays the registry's idempotent spawn+forward instead of losing the event. - It routes each message to the right session's child (hot-path `DriveEvent` via - Tell; `StartTransfer`/`GetState` via promise handoff — the registry detaches - the caller's promise with `actor.DetachAskPromise` and the child's result - settles it through `OnComplete`, so the registry goroutine never parks on a - child's signing turn and concurrent admissions sign in parallel), dedups - outgoing transfers by idempotency key, lazily spawns children, - routes retry-timer `ResumeSessionRequest` expiries to the owning child - (unknown/terminal sessions are benign no-ops), reaps children on - `SessionTerminalNotification`, and `RestoreNonTerminal` respawns **and - resumes** in-flight sessions on boot: the restore runs as a registry message - (`RestoreNonTerminalRequest`) on the registry goroutine, and each restored - child is told a `ResumeSessionRequest` so it re-drives the outbox implied by - its restored state (retry timers are in-memory and do not survive restarts). -- `ActorIDForSession` / `SessionServiceKey` / `SessionRegistryStore` — - deterministic per-session mailbox id, the per-session receptionist key the - registry registers each live child under (the ingress fast path resolves it - to tell `DriveEventRequest`s straight into the child's durable mailbox, - falling back to the registry on a miss), and the control-plane store. A - session's full durable state lives in one `oor_session_registry` row - (queryable columns + an opaque resume snapshot); OOR does not use the - generic `fsm_checkpoints` blob. -- `SessionActorConfig` / `OORRegistryConfig` — per-session and coordinator - configuration. `IncomingHandler` reuses `LocalPersistenceOutboxHandler` so the - materialization resolvers are not reimplemented; `Signer input.Signer` signs - Ark and checkpoint PSBTs inline during the turn; optional `LedgerSink - fn.Option[ledger.Sink]` resolves the durable ledger actor (Tells issued inside - Commit join the turn tx); optional `IncomingVTXOObserver IncomingVTXONotifier` - fires after incoming VTXOs are durably materialized (lets daemon subsystems arm - work without depending on `oor`); `Limits ReceiveLimits` bounds incoming - receive payloads. - -The legacy single global actor and the separate signing-effect actor have been -deleted; all per-session state and all wallet signing now live on the -per-session durable actor's turn. - -### Session, FSM & Actor Infrastructure - -- `SessionID` — stable session identifier (Ark txid hash in v0). -- `Environment` — FSM environment exposing SessionID and external system - access. -- `OutboxHandler` — interface executing local-persistence outbox requests - (incoming metadata query filtering, VTXO materialization). Wired as the - `IncomingHandler` on both the session actor and the registry; its writes - join the turn transaction via the request context. Every other outbox - event is handled inline by the session actor's own `driveOutbox` switch. -- `SignArkPSBT` — signs Ark PSBT inputs on the checkpoint 2-of-2 collab - leaf using `MultiPrevOutFetcher` for BIP-341 sighashes across - multi-input transfers. Signing runs inline on the session actor's turn; +- `OORSessionActor` — one durable actor per OOR session (outgoing or + incoming). Its `driveOutbox` switch handles every outbox event inline + (signs Ark/checkpoint PSBTs, enqueues transport to `serverconn`, + materializes incoming VTXOs, schedules retries) on the Read/Commit turn; there is no separate signing actor. +- `OORRegistryActor` — durable coordinator registered under the OOR service + key. Routes messages to the owning session's child, lazily spawns + children, dedups outgoing transfers by idempotency key, reaps terminated + children, and respawns/resumes non-terminal sessions on boot. +- `Session` / `ReceiveSession` — outgoing/incoming FSM state containers; + `OutgoingSnapshot` / `IncomingSnapshot` are their durable, versioned + serializations. +- `OutboxHandler` / `LocalPersistenceOutboxHandler` — handles the local + persistence outbox events (mark-inputs-spent, incoming metadata query, + VTXO materialization, ack); everything else is handled inline by the + session actor. - `ReceiveLimits` / `DefaultReceiveLimits` — defense-in-depth bounds on - incoming receive (`MaxCheckpoints=64`, `MaxVTXOMatches=128`, - `MaxMailboxItems=10000`, `MaxMailboxScriptBytes=10000`). Zero fields - are normalized; the `newOORActorCodec` factory captures limits so - deserialization enforces them. -- `queueVTXOSent` / `queueVTXOsReceived` — internal ledger emitters - (gated on `fn.Some(LedgerSink)`). Staged into `pendingLedger` during - dispatch; `commitAck` Tells them to the durable ledger actor inside - the commit transaction (the ledger's `DurableMailbox.Send` joins the - ambient tx), so a committed turn can never lose its accounting. The - VTXO-manager and fraud-observer notifications stay post-commit - best-effort because both re-derive from the persisted VTXO rows at - boot. -- `NewRetryCallbackRef` — bridges timeout-actor expiry notifications - into OOR `ResumeSessionRequest` for event-driven retry. - -### Actor Messages (`OORDurableMsg` / `ActorMsg`) - -- `ResolveIncomingTransferRequest` — TLV-durable (`0x7016`); persisted - by the ingress route so phase-1 indexer resolution resumes after a - crash. -- `DriveEventRequest` — generic wrapper: `(Event, SessionID)`. Used by - outbox callbacks and durable unary response routes to feed events - back into a running FSM. -- `ListSessionsRequest` / `…Response` — TLV-durable (`0x7017`). - Carries `SessionDirection` filter and `PendingOnly`. Response is - `[]SessionSummary`. -- `SessionSummary` — diagnostic projection (SessionID, Direction, - Phase, Pending, RetryAfter, RetryReason, InputOutpoints, - InputAmountSat, RecipientCount). -- `SessionDirection` — enum (`All`, `Outgoing`, `Incoming`). - -### Outbox Events - -- `QueryIncomingTransferRequest` — emitted after persisting - `ReceiveResolving`; mapped to - `serverconn.SendListOORRecipientEventsByScriptRequest`. -- `QueryIncomingMetadataRequest` — emitted after - `IncomingTransferEvent`; mapped to - `serverconn.SendListVTXOsByScriptsRequest`. -- `MaterializeIncomingVTXOsRequest` — sent to the wallet/state layer to - persist incoming VTXO records (carries Ark PSBT, checkpoint PSBTs, - recipients, resolved `MetadataMatches`). -- `SendIncomingAckRequest` — asks transport to ack the incoming - transfer. -- `IncomingTransferNotification` — emitted alongside metadata query. -- `ScheduleRetryRequest` — retryable-outbox scheduling via the timeout - actor. - -### FSM Events & Incoming Receive States - -- Events: `IncomingTransferEvent`, `IncomingMetadataResolvedEvent`, - `IncomingHandledEvent`, `IncomingAckSentEvent`. -- `ReceiveState`: `ReceiveIdle` → `ReceiveResolving` (durable hint - persisted, waiting for phase-1 indexer outside the actor tx) → - `ReceiveNotified` (package received, awaiting materialization) → - `ReceiveAwaitingAck` (materialized, awaiting transport ack) → - `ReceiveCompleted`. `ReceiveResolving` arms a give-up timer - alongside its phase-1 query (`ResolveAttempts`, persisted): the - phase-1 query has no failure response on operator silence, so each - timer expiry (a `ResumeSessionRequest` driving a `RetryDueEvent`) - re-queries with backoff and, at `maxResolveRetries`, fails the - session terminally so it becomes reap-eligible and frees its - `r.incoming` concurrency slot. Without this an unanswered resolve - would pin a child forever. - -### Outbox Handler Chain & Callbacks - -- `LocalPersistenceOutboxHandler` — handles `MarkInputsSpentRequest`, - `QueryIncomingMetadataRequest`, `MaterializeIncomingVTXOsRequest`, - `SendIncomingAckRequest`; delegates everything else to `Next`. Also - implements `IncomingMetadataRecipientFilter` so the transport layer - can pre-filter owned recipients. -- `SpendCompleter` — `func(ctx, []wire.OutPoint) error` routing OOR - spend completion through the VTXO manager. `nil` ⇒ direct store - writes (migration compat). -- `IncomingClientKeyResolver` — `func(ctx, ArkRecipientOutput) - (keychain.KeyDescriptor, error)`. Returns - `ErrIncomingRecipientNotOwned` for outputs belonging to other - clients. -- `IncomingMetadataResolver` — `func(ctx, SessionID, - ArkRecipientOutput, *psbt.Packet, []*psbt.Packet) - (IncomingVTXOMetadata, error)`. -- `IncomingMetadataRecipientFilter` — `FilterIncomingMetadataRecipients`. -- `IncomingVTXONotifier` — `func(ctx, []*vtxo.Descriptor) error` for - non-actor consumers (systest, etc.) after durable materialization. -- `OutboxHandlerConfig` / `NewOutboxHandler` — shared factory for the - two-layer chain `LocalPersistenceOutboxHandler → SigningOutboxHandler`, - used identically by production darepod and systest. - -### Snapshot, Phase & Adapter Types - -- `OutgoingSnapshot` (Phase, ArkPSBT, TransferInputSnapshots, - RetryAfter, FailReason), `OutgoingPhase` (`ark_sign_requested`, - `submit_sent`, `cosigned`, `finalize_sent`, `local_vtxo_update`, - `completed`, `failed`). -- `IncomingSnapshot`, `IncomingPhase` (`resolve_pending`, - `materialize_pending`, `ack_pending`, `completed`, `failed`). - `IncomingSnapshot.MetadataAttempts uint32` — persisted retry count for - authoritative metadata resolution (phase-2 indexer query). Drives bounded - exponential backoff and terminal give-up in `handleReceiveOutboxError` - across restarts so a session whose VTXO never lands in the indexer stops - re-querying forever. Serialized as TLV record 19. -- `TransferInputSnapshot` — portable encoding of client-side signing - context required to finalize checkpoint PSBTs after restart. -- `IncomingVTXOMetadata` — lineage metadata for incoming OOR VTXOs - (`ChainDepth` = OOR checkpoint hop count). -- `IncomingMetadataMatch` — authoritative per-output metadata for one - materialized Ark output. -- `IncomingMetadataMatchesFromResponse` — filters a - `ListVTXOsByScriptsResponse` down to current-session outputs. -- `IncomingTransferEventFromResponse` — validates and converts a - `ListOORRecipientEventsByScriptResponse` payload into an - `IncomingTransferEvent`. -- `NewResolveIncomingTransferRequest` — converts a lightweight - `IncomingOOREvent` proto to a `ResolveIncomingTransferRequest` - (shared by darepod / systest). -- `IncomingResolveCorrelationID` / `IncomingMetadataCorrelationID` - (+ `Parse…`) — stable correlation IDs for phase-1 / phase-2 durable - queries. + incoming receive (`MaxCheckpoints`, `MaxVTXOMatches`, `MaxMailboxItems`, + `MaxMailboxScriptBytes`, `MaxConcurrentIncomingSessions`). ## Relationships -- **Depends on**: `baselib/protofsm`, `baselib/actor`, `serverconn`, - `lib/arkscript`, `ledger` (`Sink` + emission messages), `timeout` - (`TimeoutActor`), `lnd/input` (signer interface for inline checkpoint / - Ark signing on the session actor's turn). -- **Depended on by**: `darepod`. -- **Sends**: - - → `serverconn`: `SendSubmitPackageRequest`, - `SendFinalizePackageRequest`, `SendIncomingAckRequest`. - - → `serverconn` durable mailbox: - `QueryIncomingTransferRequest` → - `SendListOORRecipientEventsByScriptRequest`; - `QueryIncomingMetadataRequest` → - `SendListVTXOsByScriptsRequest`. - - → `db` (via outbox): `MarkInputsSpentRequest`. - - → `wallet`: `MaterializeIncomingVTXOsRequest`. - - → `vtxo` manager: `VTXOsMaterializedNotification`. - - → `ledger` (when `LedgerSink` is `fn.Some`): `VTXOSentMsg` on - `FinalizeAcceptedEvent`; `VTXOReceivedMsg{Source=SourceOOR}` per - materialized descriptor. Told inside the commit transaction so - the accounting lands atomically with the session snapshot. -- **Receives**: - - ← `serverconn` (`EventRouter`): `SubmitAcceptedEvent`, - `FinalizeAcceptedEvent`, `ResolveIncomingTransferRequest`. - - ← `serverconn` durable unary response routes: - `DriveEventRequest{IncomingTransferEvent}`, - `DriveEventRequest{IncomingMetadataResolvedEvent}`. - - ← local persistence callback path: - `DriveEventRequest{IncomingHandledEvent}`. - - ← API: `StartTransferRequest`, `DriveEventRequest`, - `RestoreSessionRequest`, `ResumeSessionRequest`, - `ListSessionsRequest`. - -## Multi-Tree Ancestry + Lineage Cap - -- `IncomingVTXOMetadata.Ancestry []vtxo.Ancestry` replaces the - singular `TreePath`. The durable mailbox TLV record is - `incomingMetadataMatchAncestryPathsRecordType`; per-entry layout is - `(TreePath, CommitmentTxID, InputIndices, TreeDepth)`. -- Server-side over-cap submit rejection surfaces as - `*oorpb.SubmitRejectedError{Code: OOR_REJECT_LINEAGE_TOO_LARGE}`; - `ClassifySubmitError` maps it to `*ErrLineageTooLarge` so wallet - callers can switch on the cause without depending on the proto type. +- **Depends on**: `baselib/protofsm` (FSM), `baselib/actor` (durable actor + framework), `serverconn` (submit/finalize/query transport), `vtxo` + (materialization + status), `ledger` (`Sink`, accounting emission), + `timeout` (`TimeoutActor` retry scheduling), `lib/arkscript` (checkpoint + policy, collab tapleaf), `arkrpc` (indexer response types), `lnd/input` + (signer interface for inline Ark/checkpoint signing). +- **Depended on by**: `darepod` (spawns the registry, wires config, drives + RPCs and event routing). +- **Messages to/from**: Sends `SendSubmitPackageRequest` / + `SendFinalizePackageRequest` / `SendIncomingAckRequest` and durable query + requests (`QueryIncomingTransferRequest`, `QueryIncomingMetadataRequest`) + -> `serverconn`; `MaterializeIncomingVTXOsRequest` -> wallet/VTXO store; + `VTXOSentMsg`/`VTXOReceivedMsg` -> `ledger` (when `LedgerSink` is set). + Receives `SubmitAcceptedEvent` / `FinalizeAcceptedEvent` / + `ResolveIncomingTransferRequest` <- `serverconn` event router; + `StartTransferRequest` / `DriveEventRequest` / `ListSessionsRequest` <- + `darepod` RPC layer. ## Invariants -- Checkpoint output collab path is 2-of-2 - `MultiSigCollabTapLeaf(clientKey, operatorKey)`, not single-sig. -- `signCustomCheckpointPSBT` re-verifies that the custom spend path - binds to the VTXO pkScript via `SpendPath.VerifyBindsToPkScript` - before signing — covers persisted `TransferInputSnapshot`s resumed - from disk that bypassed `BuildCustomTransferInputs`. -- Condition witness encoding is bounded by `maxConditionWitnessItems = - 64` and `maxConditionWitnessItemBytes = 520` (matches Bitcoin's - `MAX_SCRIPT_ELEMENT_SIZE`). Both encode/decode enforce this via - `wire.ReadVarBytes` so a crafted blob cannot cause large - allocations. Policy template decoding uses the separate - `arkscript.readVarBytes` capped at `MaxPolicyTemplateBytes` (64 KiB). -- Submit-time only does structural validation - (`ValidateSubmitPackage`); full script VM validation runs at - finalize (requires both signatures). -- Incoming ancestor packages have a per-ancestor checkpoint count cap - (`packageArtifactsFromRPC`, `maxAncestorPackages = 64`) to prevent - resource exhaustion from a misbehaving indexer. -- Indexer-supplied `tree_depth` is cross-checked against the - reconstructed path via `arkrpc.ValidateAncestryPathDepth` in - `ancestryFromRPC`. Truncated depth or under-reported CSV window - rejects the package. -- `validateIncomingPackageGraph` runs from - `IncomingTransferEventFromResponseWithLimits` after assembly as a - final defense-in-depth check before FSM dispatch. -- Point-of-no-return: server co-signing the checkpoint - transaction(s). After that, client must resume with byte-identical - co-signed PSBTs (deterministic construction). -- Transport events (submit / finalize / ack) are delivered directly - into the `serverconn` durable actor during the commit transaction: - serverconn is durable, so each `Tell` persists into its mailbox via - the ambient OOR turn tx and the message lands IFF the turn commits. - The wire send runs later on serverconn's own egress turn, outside - the OOR tx, and is retried by serverconn — no separate outbox - publisher hop. (The generic outbox publisher is still wired for the - registry's durable ask-response handoff, not for transport.) -- Outgoing finalize ordering: local input-spend completion runs inline - in dispatch with **no OOR writer tx held**, before the FSM advances to - `Completed` and before the package write is staged. The VTXO manager's - status write commits in the VTXO actor's own transaction (a second - writer), so it does **not** join the OOR turn tx; awaiting it under a - held OOR writer lock would deadlock on the single SQLite/Postgres - writer. Completion is non-atomic with the OOR snapshot but re-driven - idempotently on boot (resume re-emits `MarkInputsSpentRequest`; - `isPersistedSpent` absorbs the replay). -- Incoming receive never performs synchronous unary RPCs inside the - durable actor DB tx. Both phase-1 hint resolution and phase-2 - authoritative metadata lookup are durable `serverconn` query - messages, delivered back as fresh durable events. -- `LocalPersistenceOutboxHandler.CallbackRef` (on the inner - `SigningOutboxHandler`) receives async materialization results so - indexer queries run outside the actor tx, preventing SQLite - write-lock starvation. -- `handleMarkInputsSpent` skips non-local outpoints, routes the rest - to `CompleteSpend` (or direct store writes if `nil`). - `actor.ErrNoActorsAvailable` returns a retryable error. -- `handleMaterializeIncoming` only calls `NotifyIncomingVTXOs` - directly when `hasActorDBTx` is false; inside a durable actor tx, - notification is deferred to `notifyMaterializedVTXOs` via the - `IncomingHandledEvent` follow-up path so the manager sees - materialization exactly once. -- `ListSessionsRequest` sorts results deterministically by SessionID - string; direction / pending filters apply after projection. -- Snapshots version per direction: `OutgoingSnapshot.Version = 4`, - `IncomingSnapshot.Version = 1` (each serialized as TLV record type 1). - Restore requires a non-zero version (`snapshot version must be provided`). -- Self-transfer: a `ResolveIncomingTransferRequest` for a session - with an active outgoing session errors until the outgoing session - terminates; then the outgoing entry is deleted and an incoming - session is created in its place. -- Signing is inline and durable-by-construction: the session actor - signs Ark and checkpoint PSBTs within its Read/Commit turn, so the - signed transport outbox is persisted in the same transaction as the - FSM advance. A restart-duplicate event that reaches the actor after - the FSM has advanced is silently discarded by `DriveEventRequest`. -- `ReceiveLimits` are propagated through the `newOORActorCodec` factory - so every deserialized message enforces the same caps as the in-memory - path. The codec instance is shared per actor. -- `StartTransferRequest.IdempotencyKey`: when non-empty, the registry - dedups admission against the durable store via - `LookupActiveSessionByIdempotencyKey`, returning - `StartTransferResponse{Existing: true}` on hit. Failed sessions - never answer for a key (a partial UNIQUE index on - `oor_session_registry` enforces at most one live-or-completed row - per key), so a keyed retry after a failure admits a fresh session. - Empty key preserves the historical deterministic (Ark txid) session. -- Phantom-resident dedup guard: `handleStartTransfer` answers - `Existing: true` for a resident outgoing child ONLY after confirming a - durable row backs it via `GetSession`. On the production (detachable) - path a failed admission is reaped asynchronously by a - `SessionTerminalNotification`, so the row-less child lingers in - `r.active` until that notification is processed. Deduping against it - would wedge a same-input retry (sessionID = Ark txid): the follow-up - `DriveEvent` restores nothing and errors as an unknown session. On a - not-found row the registry drops the phantom synchronously on its own - goroutine and falls through to a fresh admission. -- Bounded detached-continuation wait: the promise handoff parks an - `OnComplete` goroutine on the caller's context, which unblocks only on - the child future resolving or the caller context being done. The - production StartTransfer call site derives its context from - `context.WithoutCancel`, so that caller context never cancels; a wedged - or never-resolving child turn would leak the continuation for the - daemon's lifetime. `completeAdmissionHandoff` and `routeAsk` therefore - wrap `detachedAsk.CallerCtx` in `context.WithTimeout(detachedWaitTimeout)` - (5m; a behavior field shrinks it in tests) before handing it to - `OnComplete`, so the goroutine always exits. The phantom-reap guard in - `completeAdmissionHandoff` keys off the wrapped wait context's error, - not the raw CallerCtx, so a deadline-exceeded wait (a wedged child) is - treated like a benign caller hang-up and does NOT reap a session that - may still be signing under its own receive-loop context. On the - registry's durable (Read/Stage/Commit) path `DetachedAsk.CallerCtx` is - the registry ACTOR's lifetime context, not the originating caller's (the - durable mailbox does not persist the caller's context with the Ask), so a - caller deadline never propagates into the detached continuation — it is - observed only by the caller's own `future.Await`. The - `context.WithTimeout(detachedWaitTimeout)` wrap is therefore the SOLE - bound on the continuation. -- Incoming concurrency cap (`MaxConcurrentIncomingSessions`) is - enforced in `ensureChild`, the choke point every resident-making - path funnels through (admission, lazy restore on a routed message, - boot restore), so the bound holds even on paths that skip - `handleResolveIncoming`'s pre-spawn check. `restoreNonTerminal` - restores oldest-first and treats an over-cap incoming row as a - non-fatal skip, so a corrupted backlog of more than the cap of - non-terminal incoming rows cannot wedge the subsystem on every boot; - outgoing sessions carry no cap. -- Terminal row retention: terminal rows (completed and failed, all - directions) are retained in `oor_session_registry` so failed sessions - stay visible to status RPCs and for diagnostics; `handleSessionTerminal` - only reaps the in-memory child, it does not delete the row. - `MaxConcurrentIncomingSessions` bounds the resident children an operator - can pin; a future bounded-retention sweep, if needed, should age out all - terminal rows uniformly rather than deleting one class at reap time. -- Idempotency-key dedup race: on Postgres the racing children do not - serialize on `oor_session_registry`, so the loser's snapshot upsert - collides on the partial UNIQUE index. `commitAck` returns that error so - the turn rolls back and redelivers; the redelivered `resolveKeyDedup` -- - running in a fresh tx where the winner's row is now committed -- sees the - winner and consumes cleanly as `Existing` (no special error - classification: any commit error redelivers, and the partial UNIQUE - index is the safety net against a duplicate row). The dedup loser wrote - no durable row, so the clean-dedup turn fires a - `SessionTerminalNotification`: the registry's reaper treats a no-row - session as reap-eligible and drops the orphaned child (goroutine, - mailbox, receptionist key) instead of leaking it until shutdown. -- Duplicate drive-event after reap: a late at-least-once duplicate - server push for a session that has reached a terminal snapshot and - been reaped misses the ingress fast path (key unregistered) and - routes through the registry. `handleDriveEvent` distinguishes a - present-but-terminal row (`sessionIsTerminal`) from a truly-unknown - one: the terminal case acks cleanly as an idempotent no-op, only a - genuinely-unknown session errors. Without this a normal duplicate - would Nack, retry to the cap, and dead-letter. -- Registry `Stop` runs `stopChildren` (which iterates the - unsynchronized `r.active` map) ONLY when the bounded `StopAndWait` - drain returns nil -- the path where `process()` has provably exited. - On a drain timeout the registry turn may still be mutating the map, - so `stopChildren` is skipped (children are torn down by actor-system - shutdown) to avoid a fatal concurrent map iteration and write. +- Checkpoint collab output is 2-of-2 + (`arkscript.MultiSigCollabTapLeaf(clientKey, operatorKey)`), never + single-sig; resumed custom-spend inputs are re-verified against the VTXO + pkScript before signing. +- Point-of-no-return is server co-signing of the checkpoint transaction(s): + after that, the client must resume with byte-identical co-signed PSBTs + (deterministic construction), not re-derive them. +- Signing is inline and durable-by-construction: the session actor signs + within its Read/Commit turn, so the signed transport outbox commits in the + same transaction as the FSM advance. +- Transport sends (submit/finalize/ack) are delivered into `serverconn`'s + durable mailbox inside the OOR commit transaction; the actual wire send + happens later on serverconn's own egress turn and is retried there — OOR + does not run a separate outbox publisher for transport. +- Incoming receive never performs a synchronous unary RPC inside the durable + actor's DB transaction; both phase-1 hint resolution and phase-2 + authoritative metadata lookup go through durable `serverconn` query + messages and return as fresh events. +- Snapshots are versioned per direction (`OutgoingSnapshot.Version = 4`, + `IncomingSnapshot.Version = 1`); restore rejects a zero version. +- `StartTransferRequest.IdempotencyKey` dedup relies on a partial UNIQUE + index on `oor_session_registry` (at most one live-or-completed row per + key); a failed session never blocks a keyed retry. +- `MaxConcurrentIncomingSessions` (default 1024) is enforced in the + registry's `ensureChild` choke point, the only path that makes a session + resident, so every admission path (RPC, routed message, boot restore) + shares the same bound. +- Witness/script decode bounds mirror consensus limits: + `maxConditionWitnessItems = 64` items of at most 520 bytes each (Bitcoin's + `MAX_SCRIPT_ELEMENT_SIZE`), enforced on both encode and decode. +- Terminal rows (completed and failed) are retained in + `oor_session_registry` for status/diagnostics; reaping only removes the + in-memory child, never the row. +- Outgoing finalize ordering: input-spend completion runs inline with no OOR + writer transaction held, because its write commits in the VTXO manager's + own transaction; awaiting that second writer under a held OOR writer lock + would deadlock the single SQLite/Postgres writer. +- The registry's detached-continuation wait on a spawned child (`OnComplete`) + is bounded solely by wrapping `DetachedAsk.CallerCtx` in + `context.WithTimeout(detachedWaitTimeout)` (5m); the phantom-reap guard + keys off that wrapped context's error, not the raw caller ctx, so a + timed-out wait is treated as a benign hang-up and never reaps a + still-signing session. +- Idempotency-key dedup on Postgres is a commit-race, not a pre-check: losing + children collide on the partial UNIQUE index in `commitAck`, roll back, and + redeliver; the redelivered `resolveKeyDedup` then sees the winner's + committed row and consumes cleanly as `Existing`. +- `handleStartTransfer` answers `Existing: true` for a resident outgoing + child only after confirming a durable row via `GetSession`; a row-less + phantom (pending async reap via `SessionTerminalNotification`) is dropped + synchronously and falls through to a fresh admission instead of wedging a + same-input retry. +- A late duplicate server push for a terminal, already-reaped session routes + through the registry; `handleDriveEvent` acks it as an idempotent no-op + (`sessionIsTerminal`) rather than erroring, since only a genuinely-unknown + session should Nack. +- Incoming resolve/metadata retries give up terminally once their persisted + attempt counts reach `maxResolveRetries` / `maxMetadataRetries` (20), + freeing the session's concurrency slot instead of pinning a child forever + on operator silence. +- Incoming ancestor packages are capped at `maxAncestorPackages = 64` + checkpoints, and indexer-supplied `tree_depth` is cross-checked against the + reconstructed path via `arkrpc.ValidateAncestryPathDepth`. +- Server-side lineage-cap rejection surfaces as a typed `*ErrLineageTooLarge` + via `ClassifySubmitError`, so wallet callers can switch on the cause + without depending on the `oorpb` proto type. ## Deep Docs - [oor/doc.go](doc.go) — Package overview. +- [docs/oor_subsystem.md](../docs/oor_subsystem.md) — Per-session actor + design in full. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/oor/CLAUDE.md b/oor/CLAUDE.md index 0bd1ecfbd..e4dfd00b5 100644 --- a/oor/CLAUDE.md +++ b/oor/CLAUDE.md @@ -2,410 +2,128 @@ ## Purpose -Client-side out-of-round (OOR) VTXO transfer coordination without waiting for -normal rounds, preserving deterministic transaction construction and crash-safe -resume semantics. +Client-side out-of-round (OOR) VTXO transfer coordination: lets a client send +VTXOs to one or more recipients without waiting for a normal round, while +keeping transaction construction deterministic and resume semantics +crash-safe. Built on `baselib/protofsm`: I/O is modeled as outbox requests +that a durable actor executes and feeds back as events. ## Key Types For field-level detail, use `go doc github.com/lightninglabs/darepo-client/oor.`. -State transitions and validation rules live under [Invariants](#invariants). -### Per-Session Actor Model (current) - -The daemon now runs **one durable actor per OOR session** instead of one global -actor. See [docs/oor_subsystem.md](../docs/oor_subsystem.md) for the full design. - -- `OORSessionActor` / `sessionBehavior` — one durable actor per session on the - Read/Commit execution path. The FSM emits outbox events as before, but the - actor handles them itself in one shared `driveOutbox` switch (sign inline, - enqueue cross-actor transport to serverconn, materialize incoming VTXOs, - schedule retries) rather than routing them through an `OutboxHandler`. -- `OORRegistryActor` — thin coordinator registered under the OOR service key, - with a **durable inbound mailbox** (Read/Stage/Commit path, mailbox id - `oor-client`): a server-push event is persisted before the ingress loop acks - the operator envelope, so a crash between ingress and the per-session child - replays the registry's idempotent spawn+forward instead of losing the event. - It routes each message to the right session's child (hot-path `DriveEvent` via - Tell; `StartTransfer`/`GetState` via promise handoff — the registry detaches - the caller's promise with `actor.DetachAskPromise` and the child's result - settles it through `OnComplete`, so the registry goroutine never parks on a - child's signing turn and concurrent admissions sign in parallel), dedups - outgoing transfers by idempotency key, lazily spawns children, - routes retry-timer `ResumeSessionRequest` expiries to the owning child - (unknown/terminal sessions are benign no-ops), reaps children on - `SessionTerminalNotification`, and `RestoreNonTerminal` respawns **and - resumes** in-flight sessions on boot: the restore runs as a registry message - (`RestoreNonTerminalRequest`) on the registry goroutine, and each restored - child is told a `ResumeSessionRequest` so it re-drives the outbox implied by - its restored state (retry timers are in-memory and do not survive restarts). -- `ActorIDForSession` / `SessionServiceKey` / `SessionRegistryStore` — - deterministic per-session mailbox id, the per-session receptionist key the - registry registers each live child under (the ingress fast path resolves it - to tell `DriveEventRequest`s straight into the child's durable mailbox, - falling back to the registry on a miss), and the control-plane store. A - session's full durable state lives in one `oor_session_registry` row - (queryable columns + an opaque resume snapshot); OOR does not use the - generic `fsm_checkpoints` blob. -- `SessionActorConfig` / `OORRegistryConfig` — per-session and coordinator - configuration. `IncomingHandler` reuses `LocalPersistenceOutboxHandler` so the - materialization resolvers are not reimplemented; `Signer input.Signer` signs - Ark and checkpoint PSBTs inline during the turn; optional `LedgerSink - fn.Option[ledger.Sink]` resolves the durable ledger actor (Tells issued inside - Commit join the turn tx); optional `IncomingVTXOObserver IncomingVTXONotifier` - fires after incoming VTXOs are durably materialized (lets daemon subsystems arm - work without depending on `oor`); `Limits ReceiveLimits` bounds incoming - receive payloads. - -The legacy single global actor and the separate signing-effect actor have been -deleted; all per-session state and all wallet signing now live on the -per-session durable actor's turn. - -### Session, FSM & Actor Infrastructure - -- `SessionID` — stable session identifier (Ark txid hash in v0). -- `Environment` — FSM environment exposing SessionID and external system - access. -- `OutboxHandler` — interface executing local-persistence outbox requests - (incoming metadata query filtering, VTXO materialization). Wired as the - `IncomingHandler` on both the session actor and the registry; its writes - join the turn transaction via the request context. Every other outbox - event is handled inline by the session actor's own `driveOutbox` switch. -- `SignArkPSBT` — signs Ark PSBT inputs on the checkpoint 2-of-2 collab - leaf using `MultiPrevOutFetcher` for BIP-341 sighashes across - multi-input transfers. Signing runs inline on the session actor's turn; +- `OORSessionActor` — one durable actor per OOR session (outgoing or + incoming). Its `driveOutbox` switch handles every outbox event inline + (signs Ark/checkpoint PSBTs, enqueues transport to `serverconn`, + materializes incoming VTXOs, schedules retries) on the Read/Commit turn; there is no separate signing actor. +- `OORRegistryActor` — durable coordinator registered under the OOR service + key. Routes messages to the owning session's child, lazily spawns + children, dedups outgoing transfers by idempotency key, reaps terminated + children, and respawns/resumes non-terminal sessions on boot. +- `Session` / `ReceiveSession` — outgoing/incoming FSM state containers; + `OutgoingSnapshot` / `IncomingSnapshot` are their durable, versioned + serializations. +- `OutboxHandler` / `LocalPersistenceOutboxHandler` — handles the local + persistence outbox events (mark-inputs-spent, incoming metadata query, + VTXO materialization, ack); everything else is handled inline by the + session actor. - `ReceiveLimits` / `DefaultReceiveLimits` — defense-in-depth bounds on - incoming receive (`MaxCheckpoints=64`, `MaxVTXOMatches=128`, - `MaxMailboxItems=10000`, `MaxMailboxScriptBytes=10000`). Zero fields - are normalized; the `newOORActorCodec` factory captures limits so - deserialization enforces them. -- `queueVTXOSent` / `queueVTXOsReceived` — internal ledger emitters - (gated on `fn.Some(LedgerSink)`). Staged into `pendingLedger` during - dispatch; `commitAck` Tells them to the durable ledger actor inside - the commit transaction (the ledger's `DurableMailbox.Send` joins the - ambient tx), so a committed turn can never lose its accounting. The - VTXO-manager and fraud-observer notifications stay post-commit - best-effort because both re-derive from the persisted VTXO rows at - boot. -- `NewRetryCallbackRef` — bridges timeout-actor expiry notifications - into OOR `ResumeSessionRequest` for event-driven retry. - -### Actor Messages (`OORDurableMsg` / `ActorMsg`) - -- `ResolveIncomingTransferRequest` — TLV-durable (`0x7016`); persisted - by the ingress route so phase-1 indexer resolution resumes after a - crash. -- `DriveEventRequest` — generic wrapper: `(Event, SessionID)`. Used by - outbox callbacks and durable unary response routes to feed events - back into a running FSM. -- `ListSessionsRequest` / `…Response` — TLV-durable (`0x7017`). - Carries `SessionDirection` filter and `PendingOnly`. Response is - `[]SessionSummary`. -- `SessionSummary` — diagnostic projection (SessionID, Direction, - Phase, Pending, RetryAfter, RetryReason, InputOutpoints, - InputAmountSat, RecipientCount). -- `SessionDirection` — enum (`All`, `Outgoing`, `Incoming`). - -### Outbox Events - -- `QueryIncomingTransferRequest` — emitted after persisting - `ReceiveResolving`; mapped to - `serverconn.SendListOORRecipientEventsByScriptRequest`. -- `QueryIncomingMetadataRequest` — emitted after - `IncomingTransferEvent`; mapped to - `serverconn.SendListVTXOsByScriptsRequest`. -- `MaterializeIncomingVTXOsRequest` — sent to the wallet/state layer to - persist incoming VTXO records (carries Ark PSBT, checkpoint PSBTs, - recipients, resolved `MetadataMatches`). -- `SendIncomingAckRequest` — asks transport to ack the incoming - transfer. -- `IncomingTransferNotification` — emitted alongside metadata query. -- `ScheduleRetryRequest` — retryable-outbox scheduling via the timeout - actor. - -### FSM Events & Incoming Receive States - -- Events: `IncomingTransferEvent`, `IncomingMetadataResolvedEvent`, - `IncomingHandledEvent`, `IncomingAckSentEvent`. -- `ReceiveState`: `ReceiveIdle` → `ReceiveResolving` (durable hint - persisted, waiting for phase-1 indexer outside the actor tx) → - `ReceiveNotified` (package received, awaiting materialization) → - `ReceiveAwaitingAck` (materialized, awaiting transport ack) → - `ReceiveCompleted`. `ReceiveResolving` arms a give-up timer - alongside its phase-1 query (`ResolveAttempts`, persisted): the - phase-1 query has no failure response on operator silence, so each - timer expiry (a `ResumeSessionRequest` driving a `RetryDueEvent`) - re-queries with backoff and, at `maxResolveRetries`, fails the - session terminally so it becomes reap-eligible and frees its - `r.incoming` concurrency slot. Without this an unanswered resolve - would pin a child forever. - -### Outbox Handler Chain & Callbacks - -- `LocalPersistenceOutboxHandler` — handles `MarkInputsSpentRequest`, - `QueryIncomingMetadataRequest`, `MaterializeIncomingVTXOsRequest`, - `SendIncomingAckRequest`; delegates everything else to `Next`. Also - implements `IncomingMetadataRecipientFilter` so the transport layer - can pre-filter owned recipients. -- `SpendCompleter` — `func(ctx, []wire.OutPoint) error` routing OOR - spend completion through the VTXO manager. `nil` ⇒ direct store - writes (migration compat). -- `IncomingClientKeyResolver` — `func(ctx, ArkRecipientOutput) - (keychain.KeyDescriptor, error)`. Returns - `ErrIncomingRecipientNotOwned` for outputs belonging to other - clients. -- `IncomingMetadataResolver` — `func(ctx, SessionID, - ArkRecipientOutput, *psbt.Packet, []*psbt.Packet) - (IncomingVTXOMetadata, error)`. -- `IncomingMetadataRecipientFilter` — `FilterIncomingMetadataRecipients`. -- `IncomingVTXONotifier` — `func(ctx, []*vtxo.Descriptor) error` for - non-actor consumers (systest, etc.) after durable materialization. -- `OutboxHandlerConfig` / `NewOutboxHandler` — shared factory for the - two-layer chain `LocalPersistenceOutboxHandler → SigningOutboxHandler`, - used identically by production darepod and systest. - -### Snapshot, Phase & Adapter Types - -- `OutgoingSnapshot` (Phase, ArkPSBT, TransferInputSnapshots, - RetryAfter, FailReason), `OutgoingPhase` (`ark_sign_requested`, - `submit_sent`, `cosigned`, `finalize_sent`, `local_vtxo_update`, - `completed`, `failed`). -- `IncomingSnapshot`, `IncomingPhase` (`resolve_pending`, - `materialize_pending`, `ack_pending`, `completed`, `failed`). - `IncomingSnapshot.MetadataAttempts uint32` — persisted retry count for - authoritative metadata resolution (phase-2 indexer query). Drives bounded - exponential backoff and terminal give-up in `handleReceiveOutboxError` - across restarts so a session whose VTXO never lands in the indexer stops - re-querying forever. Serialized as TLV record 19. -- `TransferInputSnapshot` — portable encoding of client-side signing - context required to finalize checkpoint PSBTs after restart. -- `IncomingVTXOMetadata` — lineage metadata for incoming OOR VTXOs - (`ChainDepth` = OOR checkpoint hop count). -- `IncomingMetadataMatch` — authoritative per-output metadata for one - materialized Ark output. -- `IncomingMetadataMatchesFromResponse` — filters a - `ListVTXOsByScriptsResponse` down to current-session outputs. -- `IncomingTransferEventFromResponse` — validates and converts a - `ListOORRecipientEventsByScriptResponse` payload into an - `IncomingTransferEvent`. -- `NewResolveIncomingTransferRequest` — converts a lightweight - `IncomingOOREvent` proto to a `ResolveIncomingTransferRequest` - (shared by darepod / systest). -- `IncomingResolveCorrelationID` / `IncomingMetadataCorrelationID` - (+ `Parse…`) — stable correlation IDs for phase-1 / phase-2 durable - queries. + incoming receive (`MaxCheckpoints`, `MaxVTXOMatches`, `MaxMailboxItems`, + `MaxMailboxScriptBytes`, `MaxConcurrentIncomingSessions`). ## Relationships -- **Depends on**: `baselib/protofsm`, `baselib/actor`, `serverconn`, - `lib/arkscript`, `ledger` (`Sink` + emission messages), `timeout` - (`TimeoutActor`), `lnd/input` (signer interface for inline checkpoint / - Ark signing on the session actor's turn). -- **Depended on by**: `darepod`. -- **Sends**: - - → `serverconn`: `SendSubmitPackageRequest`, - `SendFinalizePackageRequest`, `SendIncomingAckRequest`. - - → `serverconn` durable mailbox: - `QueryIncomingTransferRequest` → - `SendListOORRecipientEventsByScriptRequest`; - `QueryIncomingMetadataRequest` → - `SendListVTXOsByScriptsRequest`. - - → `db` (via outbox): `MarkInputsSpentRequest`. - - → `wallet`: `MaterializeIncomingVTXOsRequest`. - - → `vtxo` manager: `VTXOsMaterializedNotification`. - - → `ledger` (when `LedgerSink` is `fn.Some`): `VTXOSentMsg` on - `FinalizeAcceptedEvent`; `VTXOReceivedMsg{Source=SourceOOR}` per - materialized descriptor. Told inside the commit transaction so - the accounting lands atomically with the session snapshot. -- **Receives**: - - ← `serverconn` (`EventRouter`): `SubmitAcceptedEvent`, - `FinalizeAcceptedEvent`, `ResolveIncomingTransferRequest`. - - ← `serverconn` durable unary response routes: - `DriveEventRequest{IncomingTransferEvent}`, - `DriveEventRequest{IncomingMetadataResolvedEvent}`. - - ← local persistence callback path: - `DriveEventRequest{IncomingHandledEvent}`. - - ← API: `StartTransferRequest`, `DriveEventRequest`, - `RestoreSessionRequest`, `ResumeSessionRequest`, - `ListSessionsRequest`. - -## Multi-Tree Ancestry + Lineage Cap - -- `IncomingVTXOMetadata.Ancestry []vtxo.Ancestry` replaces the - singular `TreePath`. The durable mailbox TLV record is - `incomingMetadataMatchAncestryPathsRecordType`; per-entry layout is - `(TreePath, CommitmentTxID, InputIndices, TreeDepth)`. -- Server-side over-cap submit rejection surfaces as - `*oorpb.SubmitRejectedError{Code: OOR_REJECT_LINEAGE_TOO_LARGE}`; - `ClassifySubmitError` maps it to `*ErrLineageTooLarge` so wallet - callers can switch on the cause without depending on the proto type. +- **Depends on**: `baselib/protofsm` (FSM), `baselib/actor` (durable actor + framework), `serverconn` (submit/finalize/query transport), `vtxo` + (materialization + status), `ledger` (`Sink`, accounting emission), + `timeout` (`TimeoutActor` retry scheduling), `lib/arkscript` (checkpoint + policy, collab tapleaf), `arkrpc` (indexer response types), `lnd/input` + (signer interface for inline Ark/checkpoint signing). +- **Depended on by**: `darepod` (spawns the registry, wires config, drives + RPCs and event routing). +- **Messages to/from**: Sends `SendSubmitPackageRequest` / + `SendFinalizePackageRequest` / `SendIncomingAckRequest` and durable query + requests (`QueryIncomingTransferRequest`, `QueryIncomingMetadataRequest`) + -> `serverconn`; `MaterializeIncomingVTXOsRequest` -> wallet/VTXO store; + `VTXOSentMsg`/`VTXOReceivedMsg` -> `ledger` (when `LedgerSink` is set). + Receives `SubmitAcceptedEvent` / `FinalizeAcceptedEvent` / + `ResolveIncomingTransferRequest` <- `serverconn` event router; + `StartTransferRequest` / `DriveEventRequest` / `ListSessionsRequest` <- + `darepod` RPC layer. ## Invariants -- Checkpoint output collab path is 2-of-2 - `MultiSigCollabTapLeaf(clientKey, operatorKey)`, not single-sig. -- `signCustomCheckpointPSBT` re-verifies that the custom spend path - binds to the VTXO pkScript via `SpendPath.VerifyBindsToPkScript` - before signing — covers persisted `TransferInputSnapshot`s resumed - from disk that bypassed `BuildCustomTransferInputs`. -- Condition witness encoding is bounded by `maxConditionWitnessItems = - 64` and `maxConditionWitnessItemBytes = 520` (matches Bitcoin's - `MAX_SCRIPT_ELEMENT_SIZE`). Both encode/decode enforce this via - `wire.ReadVarBytes` so a crafted blob cannot cause large - allocations. Policy template decoding uses the separate - `arkscript.readVarBytes` capped at `MaxPolicyTemplateBytes` (64 KiB). -- Submit-time only does structural validation - (`ValidateSubmitPackage`); full script VM validation runs at - finalize (requires both signatures). -- Incoming ancestor packages have a per-ancestor checkpoint count cap - (`packageArtifactsFromRPC`, `maxAncestorPackages = 64`) to prevent - resource exhaustion from a misbehaving indexer. -- Indexer-supplied `tree_depth` is cross-checked against the - reconstructed path via `arkrpc.ValidateAncestryPathDepth` in - `ancestryFromRPC`. Truncated depth or under-reported CSV window - rejects the package. -- `validateIncomingPackageGraph` runs from - `IncomingTransferEventFromResponseWithLimits` after assembly as a - final defense-in-depth check before FSM dispatch. -- Point-of-no-return: server co-signing the checkpoint - transaction(s). After that, client must resume with byte-identical - co-signed PSBTs (deterministic construction). -- Transport events (submit / finalize / ack) are delivered directly - into the `serverconn` durable actor during the commit transaction: - serverconn is durable, so each `Tell` persists into its mailbox via - the ambient OOR turn tx and the message lands IFF the turn commits. - The wire send runs later on serverconn's own egress turn, outside - the OOR tx, and is retried by serverconn — no separate outbox - publisher hop. (The generic outbox publisher is still wired for the - registry's durable ask-response handoff, not for transport.) -- Outgoing finalize ordering: local input-spend completion runs inline - in dispatch with **no OOR writer tx held**, before the FSM advances to - `Completed` and before the package write is staged. The VTXO manager's - status write commits in the VTXO actor's own transaction (a second - writer), so it does **not** join the OOR turn tx; awaiting it under a - held OOR writer lock would deadlock on the single SQLite/Postgres - writer. Completion is non-atomic with the OOR snapshot but re-driven - idempotently on boot (resume re-emits `MarkInputsSpentRequest`; - `isPersistedSpent` absorbs the replay). -- Incoming receive never performs synchronous unary RPCs inside the - durable actor DB tx. Both phase-1 hint resolution and phase-2 - authoritative metadata lookup are durable `serverconn` query - messages, delivered back as fresh durable events. -- `LocalPersistenceOutboxHandler.CallbackRef` (on the inner - `SigningOutboxHandler`) receives async materialization results so - indexer queries run outside the actor tx, preventing SQLite - write-lock starvation. -- `handleMarkInputsSpent` skips non-local outpoints, routes the rest - to `CompleteSpend` (or direct store writes if `nil`). - `actor.ErrNoActorsAvailable` returns a retryable error. -- `handleMaterializeIncoming` only calls `NotifyIncomingVTXOs` - directly when `hasActorDBTx` is false; inside a durable actor tx, - notification is deferred to `notifyMaterializedVTXOs` via the - `IncomingHandledEvent` follow-up path so the manager sees - materialization exactly once. -- `ListSessionsRequest` sorts results deterministically by SessionID - string; direction / pending filters apply after projection. -- Snapshots version per direction: `OutgoingSnapshot.Version = 4`, - `IncomingSnapshot.Version = 1` (each serialized as TLV record type 1). - Restore requires a non-zero version (`snapshot version must be provided`). -- Self-transfer: a `ResolveIncomingTransferRequest` for a session - with an active outgoing session errors until the outgoing session - terminates; then the outgoing entry is deleted and an incoming - session is created in its place. -- Signing is inline and durable-by-construction: the session actor - signs Ark and checkpoint PSBTs within its Read/Commit turn, so the - signed transport outbox is persisted in the same transaction as the - FSM advance. A restart-duplicate event that reaches the actor after - the FSM has advanced is silently discarded by `DriveEventRequest`. -- `ReceiveLimits` are propagated through the `newOORActorCodec` factory - so every deserialized message enforces the same caps as the in-memory - path. The codec instance is shared per actor. -- `StartTransferRequest.IdempotencyKey`: when non-empty, the registry - dedups admission against the durable store via - `LookupActiveSessionByIdempotencyKey`, returning - `StartTransferResponse{Existing: true}` on hit. Failed sessions - never answer for a key (a partial UNIQUE index on - `oor_session_registry` enforces at most one live-or-completed row - per key), so a keyed retry after a failure admits a fresh session. - Empty key preserves the historical deterministic (Ark txid) session. -- Phantom-resident dedup guard: `handleStartTransfer` answers - `Existing: true` for a resident outgoing child ONLY after confirming a - durable row backs it via `GetSession`. On the production (detachable) - path a failed admission is reaped asynchronously by a - `SessionTerminalNotification`, so the row-less child lingers in - `r.active` until that notification is processed. Deduping against it - would wedge a same-input retry (sessionID = Ark txid): the follow-up - `DriveEvent` restores nothing and errors as an unknown session. On a - not-found row the registry drops the phantom synchronously on its own - goroutine and falls through to a fresh admission. -- Bounded detached-continuation wait: the promise handoff parks an - `OnComplete` goroutine on the caller's context, which unblocks only on - the child future resolving or the caller context being done. The - production StartTransfer call site derives its context from - `context.WithoutCancel`, so that caller context never cancels; a wedged - or never-resolving child turn would leak the continuation for the - daemon's lifetime. `completeAdmissionHandoff` and `routeAsk` therefore - wrap `detachedAsk.CallerCtx` in `context.WithTimeout(detachedWaitTimeout)` - (5m; a behavior field shrinks it in tests) before handing it to - `OnComplete`, so the goroutine always exits. The phantom-reap guard in - `completeAdmissionHandoff` keys off the wrapped wait context's error, - not the raw CallerCtx, so a deadline-exceeded wait (a wedged child) is - treated like a benign caller hang-up and does NOT reap a session that - may still be signing under its own receive-loop context. On the - registry's durable (Read/Stage/Commit) path `DetachedAsk.CallerCtx` is - the registry ACTOR's lifetime context, not the originating caller's (the - durable mailbox does not persist the caller's context with the Ask), so a - caller deadline never propagates into the detached continuation — it is - observed only by the caller's own `future.Await`. The - `context.WithTimeout(detachedWaitTimeout)` wrap is therefore the SOLE - bound on the continuation. -- Incoming concurrency cap (`MaxConcurrentIncomingSessions`) is - enforced in `ensureChild`, the choke point every resident-making - path funnels through (admission, lazy restore on a routed message, - boot restore), so the bound holds even on paths that skip - `handleResolveIncoming`'s pre-spawn check. `restoreNonTerminal` - restores oldest-first and treats an over-cap incoming row as a - non-fatal skip, so a corrupted backlog of more than the cap of - non-terminal incoming rows cannot wedge the subsystem on every boot; - outgoing sessions carry no cap. -- Terminal row retention: terminal rows (completed and failed, all - directions) are retained in `oor_session_registry` so failed sessions - stay visible to status RPCs and for diagnostics; `handleSessionTerminal` - only reaps the in-memory child, it does not delete the row. - `MaxConcurrentIncomingSessions` bounds the resident children an operator - can pin; a future bounded-retention sweep, if needed, should age out all - terminal rows uniformly rather than deleting one class at reap time. -- Idempotency-key dedup race: on Postgres the racing children do not - serialize on `oor_session_registry`, so the loser's snapshot upsert - collides on the partial UNIQUE index. `commitAck` returns that error so - the turn rolls back and redelivers; the redelivered `resolveKeyDedup` -- - running in a fresh tx where the winner's row is now committed -- sees the - winner and consumes cleanly as `Existing` (no special error - classification: any commit error redelivers, and the partial UNIQUE - index is the safety net against a duplicate row). The dedup loser wrote - no durable row, so the clean-dedup turn fires a - `SessionTerminalNotification`: the registry's reaper treats a no-row - session as reap-eligible and drops the orphaned child (goroutine, - mailbox, receptionist key) instead of leaking it until shutdown. -- Duplicate drive-event after reap: a late at-least-once duplicate - server push for a session that has reached a terminal snapshot and - been reaped misses the ingress fast path (key unregistered) and - routes through the registry. `handleDriveEvent` distinguishes a - present-but-terminal row (`sessionIsTerminal`) from a truly-unknown - one: the terminal case acks cleanly as an idempotent no-op, only a - genuinely-unknown session errors. Without this a normal duplicate - would Nack, retry to the cap, and dead-letter. -- Registry `Stop` runs `stopChildren` (which iterates the - unsynchronized `r.active` map) ONLY when the bounded `StopAndWait` - drain returns nil -- the path where `process()` has provably exited. - On a drain timeout the registry turn may still be mutating the map, - so `stopChildren` is skipped (children are torn down by actor-system - shutdown) to avoid a fatal concurrent map iteration and write. +- Checkpoint collab output is 2-of-2 + (`arkscript.MultiSigCollabTapLeaf(clientKey, operatorKey)`), never + single-sig; resumed custom-spend inputs are re-verified against the VTXO + pkScript before signing. +- Point-of-no-return is server co-signing of the checkpoint transaction(s): + after that, the client must resume with byte-identical co-signed PSBTs + (deterministic construction), not re-derive them. +- Signing is inline and durable-by-construction: the session actor signs + within its Read/Commit turn, so the signed transport outbox commits in the + same transaction as the FSM advance. +- Transport sends (submit/finalize/ack) are delivered into `serverconn`'s + durable mailbox inside the OOR commit transaction; the actual wire send + happens later on serverconn's own egress turn and is retried there — OOR + does not run a separate outbox publisher for transport. +- Incoming receive never performs a synchronous unary RPC inside the durable + actor's DB transaction; both phase-1 hint resolution and phase-2 + authoritative metadata lookup go through durable `serverconn` query + messages and return as fresh events. +- Snapshots are versioned per direction (`OutgoingSnapshot.Version = 4`, + `IncomingSnapshot.Version = 1`); restore rejects a zero version. +- `StartTransferRequest.IdempotencyKey` dedup relies on a partial UNIQUE + index on `oor_session_registry` (at most one live-or-completed row per + key); a failed session never blocks a keyed retry. +- `MaxConcurrentIncomingSessions` (default 1024) is enforced in the + registry's `ensureChild` choke point, the only path that makes a session + resident, so every admission path (RPC, routed message, boot restore) + shares the same bound. +- Witness/script decode bounds mirror consensus limits: + `maxConditionWitnessItems = 64` items of at most 520 bytes each (Bitcoin's + `MAX_SCRIPT_ELEMENT_SIZE`), enforced on both encode and decode. +- Terminal rows (completed and failed) are retained in + `oor_session_registry` for status/diagnostics; reaping only removes the + in-memory child, never the row. +- Outgoing finalize ordering: input-spend completion runs inline with no OOR + writer transaction held, because its write commits in the VTXO manager's + own transaction; awaiting that second writer under a held OOR writer lock + would deadlock the single SQLite/Postgres writer. +- The registry's detached-continuation wait on a spawned child (`OnComplete`) + is bounded solely by wrapping `DetachedAsk.CallerCtx` in + `context.WithTimeout(detachedWaitTimeout)` (5m); the phantom-reap guard + keys off that wrapped context's error, not the raw caller ctx, so a + timed-out wait is treated as a benign hang-up and never reaps a + still-signing session. +- Idempotency-key dedup on Postgres is a commit-race, not a pre-check: losing + children collide on the partial UNIQUE index in `commitAck`, roll back, and + redeliver; the redelivered `resolveKeyDedup` then sees the winner's + committed row and consumes cleanly as `Existing`. +- `handleStartTransfer` answers `Existing: true` for a resident outgoing + child only after confirming a durable row via `GetSession`; a row-less + phantom (pending async reap via `SessionTerminalNotification`) is dropped + synchronously and falls through to a fresh admission instead of wedging a + same-input retry. +- A late duplicate server push for a terminal, already-reaped session routes + through the registry; `handleDriveEvent` acks it as an idempotent no-op + (`sessionIsTerminal`) rather than erroring, since only a genuinely-unknown + session should Nack. +- Incoming resolve/metadata retries give up terminally once their persisted + attempt counts reach `maxResolveRetries` / `maxMetadataRetries` (20), + freeing the session's concurrency slot instead of pinning a child forever + on operator silence. +- Incoming ancestor packages are capped at `maxAncestorPackages = 64` + checkpoints, and indexer-supplied `tree_depth` is cross-checked against the + reconstructed path via `arkrpc.ValidateAncestryPathDepth`. +- Server-side lineage-cap rejection surfaces as a typed `*ErrLineageTooLarge` + via `ClassifySubmitError`, so wallet callers can switch on the cause + without depending on the `oorpb` proto type. ## Deep Docs - [oor/doc.go](doc.go) — Package overview. +- [docs/oor_subsystem.md](../docs/oor_subsystem.md) — Per-session actor + design in full. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/p-models/AGENTS.md b/p-models/AGENTS.md index 7eb9d2296..2d20dfff7 100644 --- a/p-models/AGENTS.md +++ b/p-models/AGENTS.md @@ -1,29 +1,48 @@ # p-models -This tree contains executable P models and bridge checks. Keep model-specific -files under a named subdirectory such as `durableactor/`; keep shared runner -scripts under `scripts/`. +## Purpose -## Commands +Executable P-language state-machine models plus Go bridge tests, used to +check concurrency and crash-recovery invariants that plain unit tests +struggle to cover across independent actors. Currently models the durable +actor mailbox (correlation-key FIFO claim, lease/ack/nack, Stage/Commit +exactly-once). -| Command | Purpose | -|---------|---------| -| `./p-models/scripts/check.sh` | Compile the durable actor model, run P checks, then run the Go bridge | -| `p compile -pp p-models/durableactor/infra.pproj` | Compile only the durable actor P project | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxCorrelationKeyFIFO` | Run the default durable mailbox P test case | -| `go test ./p-models/durableactor/bridge` | Replay checked-in traces against the real Go store | +## Key Types -## Layout +- `durableactor/` — the P model project (`infra.pproj`) and its traces for + the durable mailbox: enqueue idempotence, per-mailbox/priority/order lease + selection, per-correlation-key FIFO blocking, ack/nack/lease-expiry/ + dead-letter, and Stage/Commit replay-safety. +- `durableactor/bridge` — Go package (`mailbox_trace.go` + + `crash_restart_test.go`, `outbox_fold_test.go`) that replays checked-in + traces against the real `db/actordelivery` store, keeping the model + connected to the shipped implementation. +- `scripts/check.sh` — compiles the P project, runs the green test cases + (must find zero bugs) and the counterexample cases (must find exactly the + expected bug), then runs the Go bridge tests. -- `durableactor/` — durable actor mailbox model, tests, traces, and bridge. -- `scripts/` — top-level orchestration scripts. -- `PGenerated/` and `PCheckerOutput/` are generated at repo root and ignored. +## Relationships -## Rules +- **Depends on**: `db/actordelivery` (bridge tests replay traces against the + real delivery store), the external P checker toolchain (`dotnet tool + install --global P`). +- **Depended on by**: none — this is verification tooling, not imported by + production code. -- Models should encode ideal contracts first, then implementation profiles. -- Keep known-bad or counterexample tests as separate test cases so the default - suite stays green. -- Bridge tests should exercise real Go code where possible instead of - reimplementing production semantics. -- Add traces for every model scenario that should be replayed against Go. +## Invariants + +- Models state the ideal contract first (e.g. `PerCorrelationKeyFIFO`); a + known-bad profile (e.g. `LegacyAvailableAtOrder`) is kept only as a + separate counterexample test case, never mixed into the default green + suite. +- Every model scenario with a real implementation path gets a bridge or + trace-replay test, so the P spec cannot silently drift from the Go code. +- `check.sh`'s negative test cases must find the bug they exist to catch; a + clean run there is itself a regression, not a pass. + +## Deep Docs + +- [README.md](README.md) — Full P-model background, layout, and running + instructions. +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/p-models/CLAUDE.md b/p-models/CLAUDE.md index 7eb9d2296..2d20dfff7 100644 --- a/p-models/CLAUDE.md +++ b/p-models/CLAUDE.md @@ -1,29 +1,48 @@ # p-models -This tree contains executable P models and bridge checks. Keep model-specific -files under a named subdirectory such as `durableactor/`; keep shared runner -scripts under `scripts/`. +## Purpose -## Commands +Executable P-language state-machine models plus Go bridge tests, used to +check concurrency and crash-recovery invariants that plain unit tests +struggle to cover across independent actors. Currently models the durable +actor mailbox (correlation-key FIFO claim, lease/ack/nack, Stage/Commit +exactly-once). -| Command | Purpose | -|---------|---------| -| `./p-models/scripts/check.sh` | Compile the durable actor model, run P checks, then run the Go bridge | -| `p compile -pp p-models/durableactor/infra.pproj` | Compile only the durable actor P project | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxCorrelationKeyFIFO` | Run the default durable mailbox P test case | -| `go test ./p-models/durableactor/bridge` | Replay checked-in traces against the real Go store | +## Key Types -## Layout +- `durableactor/` — the P model project (`infra.pproj`) and its traces for + the durable mailbox: enqueue idempotence, per-mailbox/priority/order lease + selection, per-correlation-key FIFO blocking, ack/nack/lease-expiry/ + dead-letter, and Stage/Commit replay-safety. +- `durableactor/bridge` — Go package (`mailbox_trace.go` + + `crash_restart_test.go`, `outbox_fold_test.go`) that replays checked-in + traces against the real `db/actordelivery` store, keeping the model + connected to the shipped implementation. +- `scripts/check.sh` — compiles the P project, runs the green test cases + (must find zero bugs) and the counterexample cases (must find exactly the + expected bug), then runs the Go bridge tests. -- `durableactor/` — durable actor mailbox model, tests, traces, and bridge. -- `scripts/` — top-level orchestration scripts. -- `PGenerated/` and `PCheckerOutput/` are generated at repo root and ignored. +## Relationships -## Rules +- **Depends on**: `db/actordelivery` (bridge tests replay traces against the + real delivery store), the external P checker toolchain (`dotnet tool + install --global P`). +- **Depended on by**: none — this is verification tooling, not imported by + production code. -- Models should encode ideal contracts first, then implementation profiles. -- Keep known-bad or counterexample tests as separate test cases so the default - suite stays green. -- Bridge tests should exercise real Go code where possible instead of - reimplementing production semantics. -- Add traces for every model scenario that should be replayed against Go. +## Invariants + +- Models state the ideal contract first (e.g. `PerCorrelationKeyFIFO`); a + known-bad profile (e.g. `LegacyAvailableAtOrder`) is kept only as a + separate counterexample test case, never mixed into the default green + suite. +- Every model scenario with a real implementation path gets a bridge or + trace-replay test, so the P spec cannot silently drift from the Go code. +- `check.sh`'s negative test cases must find the bug they exist to catch; a + clean run there is itself a regression, not a pass. + +## Deep Docs + +- [README.md](README.md) — Full P-model background, layout, and running + instructions. +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/p-models/durableactor/AGENTS.md b/p-models/durableactor/AGENTS.md index ac2f5da7b..1e2ed1734 100644 --- a/p-models/durableactor/AGENTS.md +++ b/p-models/durableactor/AGENTS.md @@ -1,57 +1,54 @@ # p-models/durableactor -This package models the durable actor mailbox from distributed-systems first -principles: durable enqueue, lease ownership, retry scheduling, ack/nack token -validation, dead-letter/removal, idempotent delivery identity, -per-correlation-key FIFO, the Read/Commit consume step (lease-fenced -exactly-once effect application under lease-expiry-during-IO), and the CDC -outbox fold (the target enqueue and the outbox completion committing as one -transaction, with no-lost-message and exactly-once-delivery guarantees). - -## Files - -- `infra.pproj` — P project for durable actor infrastructure checks. -- `src/mailbox_fifo.p` — ideal mailbox spec plus claim-ordering profiles. -- `src/ingress_fold.p` — connection-actor ingress cursor spec: the persisted - PullCursor must never cover an envelope whose local enqueue did not commit - (the transactional dispatch fold makes batch enqueues + cursor one atomic - commit). -- `test/mailbox_fifo_test.p` — green conformance tests and separate - counterexample tests. -- `test/ingress_fold_test.p` — green atomic-fold drain test plus the two - cursor-loss counterexamples (eager in-memory cursor after rollback; - checkpoint commit ordered before the enqueue commits). -- `traces/*.json` — concrete scenarios replayed by the Go bridge. -- `bridge/` — Go conformance harness against the real `db/actordelivery` - SQLite store and claim SQL. - -## Commands - -| Command | Purpose | -|---------|---------| -| `./p-models/scripts/check.sh` | Full default check: P model plus Go bridge | -| `p compile -pp p-models/durableactor/infra.pproj` | Compile this model | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxCorrelationKeyFIFO` | Run green durable mailbox tests | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxReadCommitFence` | Run the green Read/Commit exactly-once-effect test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxLegacyReorderCounterexample` | Demonstrate the old ordering bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxUnfencedCommitCounterexample` | Demonstrate the unfenced-commit double-apply bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStageCommitExactlyOnce` | Run the green Stage-then-Commit replay-safety test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStagedDoubleBroadcastCounterexample` | Demonstrate the unstable-broadcast double-broadcast bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStaleStageRegressesCounterexample` | Demonstrate the unfenced-stage checkpoint regression bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressFoldNoLoss` | Run the green transactional ingress-fold no-loss test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressEagerCursorCounterexample` | Demonstrate the eager-cursor-after-rollback message loss | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressCheckpointFirstCounterexample` | Demonstrate the checkpoint-before-enqueue message loss | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcOutboxFold` | Run the green transactional outbox-fold test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcOutboxSplitWriteCounterexample` | Demonstrate the split-write lost-message bug | -| `go test ./p-models/durableactor/bridge` | Replay traces and the outbox-fold/crash-restart bridge tests against Go | - -## Modeling Guidance - -- Treat the P model as the ideal specification; only add implementation modes - when they clarify a bug or migration path. -- New correctness properties should usually be expressed both as a direct P - scenario and as a bridge trace. -- Use `0` as the model's NULL correlation key. Non-zero keys are per-lane - FIFO domains scoped by mailbox id. -- Keep the default test case green. Put intentional known-bad checks in a - separate test case with "Counterexample" in the name. +## Purpose + +Executable P model of the durable actor mailbox: durable enqueue, lease +ownership, retry/backoff, ack/nack token validation, dead-letter, per- +correlation-key FIFO, the Read/Commit exactly-once consume step, the Stage +persist-before-broadcast primitive, and the CDC outbox/ingress folds. Treated +as the ideal specification the Go implementation must conform to. + +## Key Types + +- `DurableMailboxSpec` (`src/mailbox_fifo.p`) — stateful machine modeling the + mailbox: enqueue, lease/peek claim, ack/nack (by token or by id), dead + letter, and the fenced `eDurableMailboxCommit` / `eDurableMailboxStage` + consume steps. +- `OutboxFoldSpec` (`src/mailbox_fifo.p`) — models the CDC outbox: target + enqueue and outbox completion as one atomic fold. +- `IngressCursorCoversOnlyCommittedEnvelopes` (`src/ingress_fold.p`) — spec + monitor guarding the connection-actor ingress cursor: the persisted cursor + must never cover an envelope whose local enqueue did not commit. +- Safety/liveness monitors (`src/mailbox_fifo.p`): `SameKeyFIFOClaimsRespectLiveHead`, + `MailboxKeyedWorkEventuallyDrains`, `LeaseFencedCommitAppliesEffectAtMostOnce`, + `StagedEffectAppliedAtMostOnceUnderReplay`, `CheckpointAdvancesMonotonically`. + +## Relationships + +- **Depends on**: nothing in-repo (self-contained P sources); conceptually + models `baselib/actor` (Read/Commit, Stage) and `db/actordelivery` (claim + SQL, cursor persistence). +- **Depended on by**: `p-models/durableactor/bridge` (replays `traces/*.json` + scenarios from this model against the real `db/actordelivery` store). + +## Invariants + +- Keep the default P test case green; put intentional known-bad checks in a + separate "Counterexample" test case (`test/mailbox_fifo_test.p`, + `test/ingress_fold_test.p`). +- `0` is the model's NULL correlation key; non-zero keys are per-lane FIFO + domains scoped by mailbox id. +- Every safety/liveness monitor above is opt-in per test case (`assert + in { ... }`) — P does not activate them globally. +- New correctness properties should be expressed both as a direct P scenario + and as a `traces/*.json` bridge trace. + +## Deep Docs + +- [README.md](README.md) — Full model walkthrough, monitor semantics, trace + authoring notes, and `p check` / `go test` commands. +- [p-models/CLAUDE.md](../CLAUDE.md) — Top-level p-models layout and shared + commands. +- [docs/durable_actor_architecture.md](../../docs/durable_actor_architecture.md) + — Durable actor internals the model conforms to. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/p-models/durableactor/CLAUDE.md b/p-models/durableactor/CLAUDE.md index ac2f5da7b..1e2ed1734 100644 --- a/p-models/durableactor/CLAUDE.md +++ b/p-models/durableactor/CLAUDE.md @@ -1,57 +1,54 @@ # p-models/durableactor -This package models the durable actor mailbox from distributed-systems first -principles: durable enqueue, lease ownership, retry scheduling, ack/nack token -validation, dead-letter/removal, idempotent delivery identity, -per-correlation-key FIFO, the Read/Commit consume step (lease-fenced -exactly-once effect application under lease-expiry-during-IO), and the CDC -outbox fold (the target enqueue and the outbox completion committing as one -transaction, with no-lost-message and exactly-once-delivery guarantees). - -## Files - -- `infra.pproj` — P project for durable actor infrastructure checks. -- `src/mailbox_fifo.p` — ideal mailbox spec plus claim-ordering profiles. -- `src/ingress_fold.p` — connection-actor ingress cursor spec: the persisted - PullCursor must never cover an envelope whose local enqueue did not commit - (the transactional dispatch fold makes batch enqueues + cursor one atomic - commit). -- `test/mailbox_fifo_test.p` — green conformance tests and separate - counterexample tests. -- `test/ingress_fold_test.p` — green atomic-fold drain test plus the two - cursor-loss counterexamples (eager in-memory cursor after rollback; - checkpoint commit ordered before the enqueue commits). -- `traces/*.json` — concrete scenarios replayed by the Go bridge. -- `bridge/` — Go conformance harness against the real `db/actordelivery` - SQLite store and claim SQL. - -## Commands - -| Command | Purpose | -|---------|---------| -| `./p-models/scripts/check.sh` | Full default check: P model plus Go bridge | -| `p compile -pp p-models/durableactor/infra.pproj` | Compile this model | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxCorrelationKeyFIFO` | Run green durable mailbox tests | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxReadCommitFence` | Run the green Read/Commit exactly-once-effect test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxLegacyReorderCounterexample` | Demonstrate the old ordering bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxUnfencedCommitCounterexample` | Demonstrate the unfenced-commit double-apply bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStageCommitExactlyOnce` | Run the green Stage-then-Commit replay-safety test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStagedDoubleBroadcastCounterexample` | Demonstrate the unstable-broadcast double-broadcast bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcMailboxStaleStageRegressesCounterexample` | Demonstrate the unfenced-stage checkpoint regression bug | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressFoldNoLoss` | Run the green transactional ingress-fold no-loss test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressEagerCursorCounterexample` | Demonstrate the eager-cursor-after-rollback message loss | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcIngressCheckpointFirstCounterexample` | Demonstrate the checkpoint-before-enqueue message loss | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcOutboxFold` | Run the green transactional outbox-fold test | -| `p check PGenerated/PChecker/net8.0/MailboxInfraModels.dll --testcase tcOutboxSplitWriteCounterexample` | Demonstrate the split-write lost-message bug | -| `go test ./p-models/durableactor/bridge` | Replay traces and the outbox-fold/crash-restart bridge tests against Go | - -## Modeling Guidance - -- Treat the P model as the ideal specification; only add implementation modes - when they clarify a bug or migration path. -- New correctness properties should usually be expressed both as a direct P - scenario and as a bridge trace. -- Use `0` as the model's NULL correlation key. Non-zero keys are per-lane - FIFO domains scoped by mailbox id. -- Keep the default test case green. Put intentional known-bad checks in a - separate test case with "Counterexample" in the name. +## Purpose + +Executable P model of the durable actor mailbox: durable enqueue, lease +ownership, retry/backoff, ack/nack token validation, dead-letter, per- +correlation-key FIFO, the Read/Commit exactly-once consume step, the Stage +persist-before-broadcast primitive, and the CDC outbox/ingress folds. Treated +as the ideal specification the Go implementation must conform to. + +## Key Types + +- `DurableMailboxSpec` (`src/mailbox_fifo.p`) — stateful machine modeling the + mailbox: enqueue, lease/peek claim, ack/nack (by token or by id), dead + letter, and the fenced `eDurableMailboxCommit` / `eDurableMailboxStage` + consume steps. +- `OutboxFoldSpec` (`src/mailbox_fifo.p`) — models the CDC outbox: target + enqueue and outbox completion as one atomic fold. +- `IngressCursorCoversOnlyCommittedEnvelopes` (`src/ingress_fold.p`) — spec + monitor guarding the connection-actor ingress cursor: the persisted cursor + must never cover an envelope whose local enqueue did not commit. +- Safety/liveness monitors (`src/mailbox_fifo.p`): `SameKeyFIFOClaimsRespectLiveHead`, + `MailboxKeyedWorkEventuallyDrains`, `LeaseFencedCommitAppliesEffectAtMostOnce`, + `StagedEffectAppliedAtMostOnceUnderReplay`, `CheckpointAdvancesMonotonically`. + +## Relationships + +- **Depends on**: nothing in-repo (self-contained P sources); conceptually + models `baselib/actor` (Read/Commit, Stage) and `db/actordelivery` (claim + SQL, cursor persistence). +- **Depended on by**: `p-models/durableactor/bridge` (replays `traces/*.json` + scenarios from this model against the real `db/actordelivery` store). + +## Invariants + +- Keep the default P test case green; put intentional known-bad checks in a + separate "Counterexample" test case (`test/mailbox_fifo_test.p`, + `test/ingress_fold_test.p`). +- `0` is the model's NULL correlation key; non-zero keys are per-lane FIFO + domains scoped by mailbox id. +- Every safety/liveness monitor above is opt-in per test case (`assert + in { ... }`) — P does not activate them globally. +- New correctness properties should be expressed both as a direct P scenario + and as a `traces/*.json` bridge trace. + +## Deep Docs + +- [README.md](README.md) — Full model walkthrough, monitor semantics, trace + authoring notes, and `p check` / `go test` commands. +- [p-models/CLAUDE.md](../CLAUDE.md) — Top-level p-models layout and shared + commands. +- [docs/durable_actor_architecture.md](../../docs/durable_actor_architecture.md) + — Durable actor internals the model conforms to. +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. diff --git a/round/AGENTS.md b/round/AGENTS.md index d055c8b8f..ea87cd8ed 100644 --- a/round/AGENTS.md +++ b/round/AGENTS.md @@ -121,9 +121,12 @@ state transitions and validation rules live under [Invariants](#invariants). ## Relationships -- **Depends on**: `baselib/protofsm` (FSM engine), `lib/tree`, - `lib/types`, `lib/arkscript`, `wallet`, `ledger` (`Sink` + - `VTXOReceivedMsg` / `Source*` constants), `timeout`, `google/uuid`. +- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor + primitives: `ActorRef`, `ActorSystem`, `BaseMessage`), `lib/actormsg` + (mailbox marker interfaces), `lib/tree`, `lib/types`, `lib/arkscript`, + `lib/bip322` (join-round BIP-322 auth signing), `rpc/roundpb` (wire proto + types via `FromProto`), `wallet`, `ledger` (`Sink` + `VTXOReceivedMsg` / + `Source*` constants), `timeout`, `google/uuid`. - **Depended on by**: `vtxo`, `db`, `darepod`. - **Sends → `serverconn`**: `JoinRoundRequest`, `JoinRoundAcceptOutbox`, `JoinRoundRejectOutbox`, @@ -251,5 +254,3 @@ state transitions and validation rules live under [Invariants](#invariants). - [round/README.md](README.md) — Full state machine walkthrough with diagrams. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - \ No newline at end of file diff --git a/round/CLAUDE.md b/round/CLAUDE.md index d055c8b8f..ea87cd8ed 100644 --- a/round/CLAUDE.md +++ b/round/CLAUDE.md @@ -121,9 +121,12 @@ state transitions and validation rules live under [Invariants](#invariants). ## Relationships -- **Depends on**: `baselib/protofsm` (FSM engine), `lib/tree`, - `lib/types`, `lib/arkscript`, `wallet`, `ledger` (`Sink` + - `VTXOReceivedMsg` / `Source*` constants), `timeout`, `google/uuid`. +- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor + primitives: `ActorRef`, `ActorSystem`, `BaseMessage`), `lib/actormsg` + (mailbox marker interfaces), `lib/tree`, `lib/types`, `lib/arkscript`, + `lib/bip322` (join-round BIP-322 auth signing), `rpc/roundpb` (wire proto + types via `FromProto`), `wallet`, `ledger` (`Sink` + `VTXOReceivedMsg` / + `Source*` constants), `timeout`, `google/uuid`. - **Depended on by**: `vtxo`, `db`, `darepod`. - **Sends → `serverconn`**: `JoinRoundRequest`, `JoinRoundAcceptOutbox`, `JoinRoundRejectOutbox`, @@ -251,5 +254,3 @@ state transitions and validation rules live under [Invariants](#invariants). - [round/README.md](README.md) — Full state machine walkthrough with diagrams. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - \ No newline at end of file diff --git a/rpc/AGENTS.md b/rpc/AGENTS.md index 8ccb250a7..5fd8351e0 100644 --- a/rpc/AGENTS.md +++ b/rpc/AGENTS.md @@ -17,9 +17,11 @@ Client-side RPC message definitions and HTTP transport in sub-packages: - **Depends on**: nothing for generated sub-packages (proto definitions). `rpc/restclient` depends on `arkrpc`, `daemonrpc`, `mailbox/pb`, `rpc/swapclientrpc`, `rpc/walletdkrpc`, and `swaprpc`. -- **Depended on by**: `round`, `oor`, `serverconn` (generated types); - `sdk/walletdk`, `sdk/swaps`, `swapclientserver`, `darepod` - (`rpc/restclient` REST transport). +- **Depended on by**: `round`, `oor`, `db` (roundpb/oorpb message types); + `sdk/walletdk`, `swapwallet`, `swapclientserver`, `darepod`, + `cmd/darepocli` (swapclientrpc/walletdkrpc service stubs); `sdk/walletdk`, + `sdk/swaps`, `swapclientserver`, `darepod` (`rpc/restclient` REST + transport). ## Invariants diff --git a/rpc/CLAUDE.md b/rpc/CLAUDE.md index 8ccb250a7..5fd8351e0 100644 --- a/rpc/CLAUDE.md +++ b/rpc/CLAUDE.md @@ -17,9 +17,11 @@ Client-side RPC message definitions and HTTP transport in sub-packages: - **Depends on**: nothing for generated sub-packages (proto definitions). `rpc/restclient` depends on `arkrpc`, `daemonrpc`, `mailbox/pb`, `rpc/swapclientrpc`, `rpc/walletdkrpc`, and `swaprpc`. -- **Depended on by**: `round`, `oor`, `serverconn` (generated types); - `sdk/walletdk`, `sdk/swaps`, `swapclientserver`, `darepod` - (`rpc/restclient` REST transport). +- **Depended on by**: `round`, `oor`, `db` (roundpb/oorpb message types); + `sdk/walletdk`, `swapwallet`, `swapclientserver`, `darepod`, + `cmd/darepocli` (swapclientrpc/walletdkrpc service stubs); `sdk/walletdk`, + `sdk/swaps`, `swapclientserver`, `darepod` (`rpc/restclient` REST + transport). ## Invariants diff --git a/rpc/roundpb/AGENTS.md b/rpc/roundpb/AGENTS.md index f5b3bf12d..a978cad00 100644 --- a/rpc/roundpb/AGENTS.md +++ b/rpc/roundpb/AGENTS.md @@ -3,14 +3,15 @@ ## Purpose Generated protobuf/gRPC stubs for the round protocol, plus hand-written -`service.go` containing the canonical mailbox method name constants used -for routing client↔server round messages through the durable transport -layer. +support code: `service.go` (mailbox method name constants), `convert.go` +(proto <-> Go domain-type conversions, including the security-sensitive +`TreeFromProto` VTXO-tree deserializer), and `version.go` (the round flow +version guard). ## Key Types All `*.pb.go` files are generated — never edit directly; regenerate with -`make rpc`. The manually-maintained `service.go` defines: +`make rpc`. The hand-written files define: - `ServiceName` — Fully-qualified protobuf service name (`"round.v1.RoundService"`) used for mailbox event routing. @@ -21,6 +22,16 @@ All `*.pb.go` files are generated — never edit directly; regenerate with `MethodRejectQuote`, `MethodSubmitNonces`, `MethodSubmitPartialSigs`, `MethodSubmitForfeitSigs` (boarding input sigs), `MethodSubmitVTXOForfeitSigs` (VTXO forfeit sigs). +- `TreeFromProto` / `TreeToProto` — Convert between `*VTXOTree` proto and + `lib/tree.Tree`; `TreeFromProto` takes `WithMaxTreeNodes` to bound the + deserialized node count (`DefaultMaxTreeNodes` = 50,000). +- `OutpointFromProto`/`ToProto`, `TxOutFromProto`/`ToProto`, + `PSBTFromBytes`/`ToBytes`, `MsgTxFromBytes`/`ToBytes`, + `SchnorrSigFromBytes`/`ToBytes` — wire/proto ⇄ Go conversions for the + round protocol's payload types. +- `FlowVersion` / `FlowVersionV1` / `ValidateFlowVersion` — the per-round + choreography version stamped by the operator and validated by the + client; fails closed on any version this build does not understand. `MethodSubmitForfeitSigs` and `MethodSubmitVTXOForfeitSigs` are distinct wire methods for two different payload types; see `round/CLAUDE.md` for @@ -29,15 +40,24 @@ distinction. ## Relationships -- **Depends on**: nothing (generated proto types only). -- **Depended on by**: `round` (outbox routing, `FromProto` helpers), - `serverconn` (mailbox method dispatch), `darepod` (proto conversion). +- **Depends on**: `lib/tree`, `lib/types` (conversion targets in + `convert.go`); otherwise generated proto types only. +- **Depended on by**: `round` (outbox routing, proto conversions, flow + version), `db` (persisting round/VTXO proto blobs), `darepod` (proto + conversion, flow version). ## Invariants - **Never edit generated code** — regenerate via `make rpc`. - Method name constants in `service.go` must match the proto service definition; mismatches silently drop events at the mailbox router. +- `TreeFromProto` enforces a pre-order invariant (child index > parent + index) and bounds child/output indices; this is what prevents a + malicious server from encoding cycles or out-of-range references in a + `VTXOTree` and DoS-ing tree traversal. Do not relax these checks. +- `ValidateFlowVersion` must reject any `FlowVersion` other than the + versions this build implements (currently only `FlowVersionV1`); never + make it permissive by default. ## Deep Docs diff --git a/rpc/roundpb/CLAUDE.md b/rpc/roundpb/CLAUDE.md index f5b3bf12d..a978cad00 100644 --- a/rpc/roundpb/CLAUDE.md +++ b/rpc/roundpb/CLAUDE.md @@ -3,14 +3,15 @@ ## Purpose Generated protobuf/gRPC stubs for the round protocol, plus hand-written -`service.go` containing the canonical mailbox method name constants used -for routing client↔server round messages through the durable transport -layer. +support code: `service.go` (mailbox method name constants), `convert.go` +(proto <-> Go domain-type conversions, including the security-sensitive +`TreeFromProto` VTXO-tree deserializer), and `version.go` (the round flow +version guard). ## Key Types All `*.pb.go` files are generated — never edit directly; regenerate with -`make rpc`. The manually-maintained `service.go` defines: +`make rpc`. The hand-written files define: - `ServiceName` — Fully-qualified protobuf service name (`"round.v1.RoundService"`) used for mailbox event routing. @@ -21,6 +22,16 @@ All `*.pb.go` files are generated — never edit directly; regenerate with `MethodRejectQuote`, `MethodSubmitNonces`, `MethodSubmitPartialSigs`, `MethodSubmitForfeitSigs` (boarding input sigs), `MethodSubmitVTXOForfeitSigs` (VTXO forfeit sigs). +- `TreeFromProto` / `TreeToProto` — Convert between `*VTXOTree` proto and + `lib/tree.Tree`; `TreeFromProto` takes `WithMaxTreeNodes` to bound the + deserialized node count (`DefaultMaxTreeNodes` = 50,000). +- `OutpointFromProto`/`ToProto`, `TxOutFromProto`/`ToProto`, + `PSBTFromBytes`/`ToBytes`, `MsgTxFromBytes`/`ToBytes`, + `SchnorrSigFromBytes`/`ToBytes` — wire/proto ⇄ Go conversions for the + round protocol's payload types. +- `FlowVersion` / `FlowVersionV1` / `ValidateFlowVersion` — the per-round + choreography version stamped by the operator and validated by the + client; fails closed on any version this build does not understand. `MethodSubmitForfeitSigs` and `MethodSubmitVTXOForfeitSigs` are distinct wire methods for two different payload types; see `round/CLAUDE.md` for @@ -29,15 +40,24 @@ distinction. ## Relationships -- **Depends on**: nothing (generated proto types only). -- **Depended on by**: `round` (outbox routing, `FromProto` helpers), - `serverconn` (mailbox method dispatch), `darepod` (proto conversion). +- **Depends on**: `lib/tree`, `lib/types` (conversion targets in + `convert.go`); otherwise generated proto types only. +- **Depended on by**: `round` (outbox routing, proto conversions, flow + version), `db` (persisting round/VTXO proto blobs), `darepod` (proto + conversion, flow version). ## Invariants - **Never edit generated code** — regenerate via `make rpc`. - Method name constants in `service.go` must match the proto service definition; mismatches silently drop events at the mailbox router. +- `TreeFromProto` enforces a pre-order invariant (child index > parent + index) and bounds child/output indices; this is what prevents a + malicious server from encoding cycles or out-of-range references in a + `VTXOTree` and DoS-ing tree traversal. Do not relax these checks. +- `ValidateFlowVersion` must reject any `FlowVersion` other than the + versions this build implements (currently only `FlowVersionV1`); never + make it permissive by default. ## Deep Docs diff --git a/rpc/walletdkrpc/AGENTS.md b/rpc/walletdkrpc/AGENTS.md index de6a2e727..b8d4fd7e0 100644 --- a/rpc/walletdkrpc/AGENTS.md +++ b/rpc/walletdkrpc/AGENTS.md @@ -2,11 +2,13 @@ ## Purpose -Generated gRPC stubs for `walletdkrpc.WalletService` — the highest-level -RPC surface in the daemon's API stack. The service is a small, flat, -swap-vocabulary-free wallet API that lives ABOVE `daemonrpc` and -`swapclientrpc` and composes them; the seven verbs map 1:1 to what a -user does day-to-day. +Generated gRPC stubs for `walletdkrpc.WalletService` (plus the technical +drill-down `WalletInspectionService`) — the highest-level RPC surface in +the daemon's API stack. The service is a small, flat, swap-vocabulary-free +wallet API that lives ABOVE `daemonrpc` and `swapclientrpc` and composes +them; seven verbs map 1:1 to what a user does day-to-day, plus supporting +methods. `failure_reasons.go` is hand-written: it defines the wallet +rejection taxonomy shared by the daemon-side error mapper and SDK clients. Proto source: `rpc/walletdkrpc/wallet.proto`. @@ -20,12 +22,20 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. | `Send` | Dispatch a prepared send; consumes `send_intent_id` | | `Recv` | Inbound Lightning invoice (offchain receive) | | `List` | Unified wallet view; `ListView` selects activity/vtxos/onchain | -| `Balance` | Flat balance (confirmed / pending_in / pending_out) | -| `Exit` | Cooperative leave by default; unilateral unroll only with the exact force-ack | | `Deposit` | Fresh boarding address (used by `recv --onchain`) | +| `Balance` | Flat balance (confirmed / pending_in / pending_out) | | `Status` | Daemon + wallet readiness summary | +| `GetExitPlan` | Preview unilateral-exit readiness/funding for one VTXO | +| `SweepWallet` | Preview/broadcast a backing-wallet sweep to an address | +| `Exit` | Cooperative leave by default; unilateral unroll only with the exact force-ack | | `ExitStatus` | Phase of an exit job (proxies `GetUnrollStatus`) | -| `SubscribeWallet` | Streams normalized `WalletEntry` updates | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits | +| `SubscribeWallet` | Streams normalized `WalletEntry` updates (resumable via cursor) | + +`WalletInspectionService.InspectActivity` is a separate service exposing a +technical trace (ledger rows, swap/VTXO correlation) for one `WalletEntry` +id; unlike `List` it may leak internal correlators, so it is kept out of +`WalletService`. ## Key Messages @@ -43,18 +53,26 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. forfeiting lifecycle detail). - `OnchainTx` — Wallet-facing flat on-chain row (no debit/credit accounts, no round/session correlators). -- `ExitJobStatus` — Wallet-facing projection of - `daemonrpc.UnrollJobStatus`. +- `ExitJobStatus` — Enum collapsing the underlying unroll job phases to a + short wallet-facing status; shared by `ExitPlanEntry`, + `ExitStatusResponse`, and `ExitSummaryItem`. +- `FailureDomain` / `Reason*` constants (`failure_reasons.go`) — the + `google.rpc.ErrorInfo` domain/reason wire contract for failed wallet + RPCs; existing reason values MUST NOT be renamed. ## Relationships -- **Depends on**: nothing (proto definitions). +- **Depends on**: nothing (proto definitions; `failure_reasons.go` has no + imports). - **Depended on by**: - `swapwallet` (implements the service server-side; consumes the generated stubs). - `cmd/darepocli/darepoclicommands` (the seven top-level CLI verbs - dial `walletdkrpc.WalletService`). + plus `sweep-wallet`, `exit plan`/`exit summary`, and `activity + inspect` dial `WalletService`/`WalletInspectionService`). - `sdk/walletdk` (gomobile-friendly SDK wraps the same stubs). + - `darepod` (RPC auth wiring), `rpc/restclient` (REST transport + adapter over the same service stubs). ## Invariants @@ -62,7 +80,7 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. - The walletdkrpc layer is the highest-level RPC surface; new wallet verbs land HERE first and admin proxies pull from `daemonrpc`. Internal correlators MUST NOT leak from `daemonrpc` into walletdkrpc - responses. + responses (that is what `WalletInspectionService` is for instead). - `Create`, `Unlock`, `Exit`, and `ExitStatus` are admin-shape proxies that work BEFORE the swap subsystem is live; the server-side implementation (in `swapwallet/admin.go`) MUST NOT depend on the @@ -71,9 +89,9 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. the field keep getting the merged WalletEntry stream. - `ListResponse.body` is a oneof; agents see a tagged union per view rather than a polymorphic blob. -- `Status` and `Deposit` are kept in the proto for programmatic - callers (and `recv --onchain` plumbs through `Deposit`); they are - NOT surfaced as top-level CLI verbs. +- `failure_reasons.go` values are a wire contract: existing `Reason*` + constants MUST NOT be renamed since clients match on them; add new + values instead. ## Deep Docs diff --git a/rpc/walletdkrpc/CLAUDE.md b/rpc/walletdkrpc/CLAUDE.md index de6a2e727..b8d4fd7e0 100644 --- a/rpc/walletdkrpc/CLAUDE.md +++ b/rpc/walletdkrpc/CLAUDE.md @@ -2,11 +2,13 @@ ## Purpose -Generated gRPC stubs for `walletdkrpc.WalletService` — the highest-level -RPC surface in the daemon's API stack. The service is a small, flat, -swap-vocabulary-free wallet API that lives ABOVE `daemonrpc` and -`swapclientrpc` and composes them; the seven verbs map 1:1 to what a -user does day-to-day. +Generated gRPC stubs for `walletdkrpc.WalletService` (plus the technical +drill-down `WalletInspectionService`) — the highest-level RPC surface in +the daemon's API stack. The service is a small, flat, swap-vocabulary-free +wallet API that lives ABOVE `daemonrpc` and `swapclientrpc` and composes +them; seven verbs map 1:1 to what a user does day-to-day, plus supporting +methods. `failure_reasons.go` is hand-written: it defines the wallet +rejection taxonomy shared by the daemon-side error mapper and SDK clients. Proto source: `rpc/walletdkrpc/wallet.proto`. @@ -20,12 +22,20 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. | `Send` | Dispatch a prepared send; consumes `send_intent_id` | | `Recv` | Inbound Lightning invoice (offchain receive) | | `List` | Unified wallet view; `ListView` selects activity/vtxos/onchain | -| `Balance` | Flat balance (confirmed / pending_in / pending_out) | -| `Exit` | Cooperative leave by default; unilateral unroll only with the exact force-ack | | `Deposit` | Fresh boarding address (used by `recv --onchain`) | +| `Balance` | Flat balance (confirmed / pending_in / pending_out) | | `Status` | Daemon + wallet readiness summary | +| `GetExitPlan` | Preview unilateral-exit readiness/funding for one VTXO | +| `SweepWallet` | Preview/broadcast a backing-wallet sweep to an address | +| `Exit` | Cooperative leave by default; unilateral unroll only with the exact force-ack | | `ExitStatus` | Phase of an exit job (proxies `GetUnrollStatus`) | -| `SubscribeWallet` | Streams normalized `WalletEntry` updates | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits | +| `SubscribeWallet` | Streams normalized `WalletEntry` updates (resumable via cursor) | + +`WalletInspectionService.InspectActivity` is a separate service exposing a +technical trace (ledger rows, swap/VTXO correlation) for one `WalletEntry` +id; unlike `List` it may leak internal correlators, so it is kept out of +`WalletService`. ## Key Messages @@ -43,18 +53,26 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. forfeiting lifecycle detail). - `OnchainTx` — Wallet-facing flat on-chain row (no debit/credit accounts, no round/session correlators). -- `ExitJobStatus` — Wallet-facing projection of - `daemonrpc.UnrollJobStatus`. +- `ExitJobStatus` — Enum collapsing the underlying unroll job phases to a + short wallet-facing status; shared by `ExitPlanEntry`, + `ExitStatusResponse`, and `ExitSummaryItem`. +- `FailureDomain` / `Reason*` constants (`failure_reasons.go`) — the + `google.rpc.ErrorInfo` domain/reason wire contract for failed wallet + RPCs; existing reason values MUST NOT be renamed. ## Relationships -- **Depends on**: nothing (proto definitions). +- **Depends on**: nothing (proto definitions; `failure_reasons.go` has no + imports). - **Depended on by**: - `swapwallet` (implements the service server-side; consumes the generated stubs). - `cmd/darepocli/darepoclicommands` (the seven top-level CLI verbs - dial `walletdkrpc.WalletService`). + plus `sweep-wallet`, `exit plan`/`exit summary`, and `activity + inspect` dial `WalletService`/`WalletInspectionService`). - `sdk/walletdk` (gomobile-friendly SDK wraps the same stubs). + - `darepod` (RPC auth wiring), `rpc/restclient` (REST transport + adapter over the same service stubs). ## Invariants @@ -62,7 +80,7 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. - The walletdkrpc layer is the highest-level RPC surface; new wallet verbs land HERE first and admin proxies pull from `daemonrpc`. Internal correlators MUST NOT leak from `daemonrpc` into walletdkrpc - responses. + responses (that is what `WalletInspectionService` is for instead). - `Create`, `Unlock`, `Exit`, and `ExitStatus` are admin-shape proxies that work BEFORE the swap subsystem is live; the server-side implementation (in `swapwallet/admin.go`) MUST NOT depend on the @@ -71,9 +89,9 @@ Proto source: `rpc/walletdkrpc/wallet.proto`. the field keep getting the merged WalletEntry stream. - `ListResponse.body` is a oneof; agents see a tagged union per view rather than a polymorphic blob. -- `Status` and `Deposit` are kept in the proto for programmatic - callers (and `recv --onchain` plumbs through `Deposit`); they are - NOT surfaced as top-level CLI verbs. +- `failure_reasons.go` values are a wire contract: existing `Reason*` + constants MUST NOT be renamed since clients match on them; add new + values instead. ## Deep Docs diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md index 1e0e7e755..28bbe2847 100644 --- a/scripts/AGENTS.md +++ b/scripts/AGENTS.md @@ -2,10 +2,16 @@ ## Purpose -Build, verification, and maintenance scripts including schema registry -verification, commit message linting, and documentation cross-link validation. +Build, lint, codegen, and release helper scripts (bash and Python) invoked +from Makefile targets. Covers Go/migration version checks, formatting +(`llformat-files.sh`), protobuf/sqlc codegen wrappers, doc cross-link +checks, commit-message linting, custom-linter build/install, and release +tagging. `check-sample-darepod-conf/` and `verify-schema-registry/` are +separate Go tools with their own docs. ## Relationships -- **Depends on**: nothing. -- **Depended on by**: Makefile targets (`make doc-check`, `make commitmsg-lint`, `make schema-check`). +- **Depends on**: nothing (shell/Python scripts, no repo package imports). +- **Depended on by**: Makefile targets (`make lint`, `make fmt`, + `make doc-check`, `make commitmsg-lint`, `make schema-check`, + `make sample-conf-check`, `make rpc`, `make sqlc`, `make install-custom-gcl`). diff --git a/scripts/CLAUDE.md b/scripts/CLAUDE.md index 1e0e7e755..28bbe2847 100644 --- a/scripts/CLAUDE.md +++ b/scripts/CLAUDE.md @@ -2,10 +2,16 @@ ## Purpose -Build, verification, and maintenance scripts including schema registry -verification, commit message linting, and documentation cross-link validation. +Build, lint, codegen, and release helper scripts (bash and Python) invoked +from Makefile targets. Covers Go/migration version checks, formatting +(`llformat-files.sh`), protobuf/sqlc codegen wrappers, doc cross-link +checks, commit-message linting, custom-linter build/install, and release +tagging. `check-sample-darepod-conf/` and `verify-schema-registry/` are +separate Go tools with their own docs. ## Relationships -- **Depends on**: nothing. -- **Depended on by**: Makefile targets (`make doc-check`, `make commitmsg-lint`, `make schema-check`). +- **Depends on**: nothing (shell/Python scripts, no repo package imports). +- **Depended on by**: Makefile targets (`make lint`, `make fmt`, + `make doc-check`, `make commitmsg-lint`, `make schema-check`, + `make sample-conf-check`, `make rpc`, `make sqlc`, `make install-custom-gcl`). diff --git a/scripts/check-sample-darepod-conf/AGENTS.md b/scripts/check-sample-darepod-conf/AGENTS.md index 2acbcc4ff..6da26f9c6 100644 --- a/scripts/check-sample-darepod-conf/AGENTS.md +++ b/scripts/check-sample-darepod-conf/AGENTS.md @@ -2,24 +2,44 @@ ## Purpose -CI validation tool that verifies `sample-darepod.conf` documents every -config option exposed by the daemon. It runs `darepod --help` to collect -the canonical flag list, then checks that each non-skipped flag appears at -least once in the sample conf file. Fails with a diff-style report when a -flag is undocumented. +CI validation tool (`main` package) that verifies `sample-darepod.conf` +documents every daemon config option with its current default value. It +combines two sources of truth: `darepod.DefaultConfig()`, walked via +reflection over `mapstructure` tags, and `darepod --help` output (for +CLI-only flags such as `bitcoind.*` that don't live in the config struct). +It cross-checks source-registered flags in `cmd/darepod/main.go` against +`--help` to catch parser drift, then diffs the combined expected set +against the sample file's commented `# key=value` entries. Run via `make +sample-conf-check`. + +## Key Functions + +- `expectedConfigKeys` / `collectConfigKeys` — Recursively reflect over + `darepod.DefaultConfig()` using `mapstructure` tags to build the expected + key -> default-value map. +- `addDaemonFlagKeys` — Adds CLI-only flags from `darepod --help` and + verifies every flag registered in `cmd/darepod/main.go` appears in help + output. +- `parseSampleConfig` — Parses `sample-darepod.conf`; fails if any entry is + an uncommented live config line. +- `checkSampleConfig` — Diffs expected vs. sample: reports missing keys, + unknown/stale keys, and mismatched default values. ## Relationships -- **Depends on**: `darepod` (for the flag surface via `--help`). -- **Depended on by**: Makefile / CI (invoked by `make doc-check` or equivalent - sample-conf check target). +- **Depends on**: `darepod` (`DefaultConfig()` for the config surface; also + shells out to `go run ./cmd/darepod --help` for CLI-only flags). +- **Depended on by**: `make sample-conf-check` Makefile / CI target. ## Invariants -- Flags in `skippedFlags` (`configfile`, `help`, `version`) are excluded from - the check — they are tool flags with no meaningful conf-file representation. -- The tool reads `defaultConfFile` (`sample-darepod.conf`) from the repo root - and `mainFile` (`cmd/darepod/main.go`) to locate the daemon entry point. +- Flags in `skippedFlags` (`configfile`, `help`, `version`) are excluded — + tool flags with no meaningful conf-file representation. +- Every entry in `sample-darepod.conf` must stay commented (`# key=value`); + a live (uncommented) line is a hard error. +- `registeredDaemonFlags` assumes daemon flags in `cmd/darepod/main.go` are + registered with literal string arguments; helper/variable-based + registration needs a parser update alongside that refactor. ## Deep Docs diff --git a/scripts/check-sample-darepod-conf/CLAUDE.md b/scripts/check-sample-darepod-conf/CLAUDE.md index 2acbcc4ff..6da26f9c6 100644 --- a/scripts/check-sample-darepod-conf/CLAUDE.md +++ b/scripts/check-sample-darepod-conf/CLAUDE.md @@ -2,24 +2,44 @@ ## Purpose -CI validation tool that verifies `sample-darepod.conf` documents every -config option exposed by the daemon. It runs `darepod --help` to collect -the canonical flag list, then checks that each non-skipped flag appears at -least once in the sample conf file. Fails with a diff-style report when a -flag is undocumented. +CI validation tool (`main` package) that verifies `sample-darepod.conf` +documents every daemon config option with its current default value. It +combines two sources of truth: `darepod.DefaultConfig()`, walked via +reflection over `mapstructure` tags, and `darepod --help` output (for +CLI-only flags such as `bitcoind.*` that don't live in the config struct). +It cross-checks source-registered flags in `cmd/darepod/main.go` against +`--help` to catch parser drift, then diffs the combined expected set +against the sample file's commented `# key=value` entries. Run via `make +sample-conf-check`. + +## Key Functions + +- `expectedConfigKeys` / `collectConfigKeys` — Recursively reflect over + `darepod.DefaultConfig()` using `mapstructure` tags to build the expected + key -> default-value map. +- `addDaemonFlagKeys` — Adds CLI-only flags from `darepod --help` and + verifies every flag registered in `cmd/darepod/main.go` appears in help + output. +- `parseSampleConfig` — Parses `sample-darepod.conf`; fails if any entry is + an uncommented live config line. +- `checkSampleConfig` — Diffs expected vs. sample: reports missing keys, + unknown/stale keys, and mismatched default values. ## Relationships -- **Depends on**: `darepod` (for the flag surface via `--help`). -- **Depended on by**: Makefile / CI (invoked by `make doc-check` or equivalent - sample-conf check target). +- **Depends on**: `darepod` (`DefaultConfig()` for the config surface; also + shells out to `go run ./cmd/darepod --help` for CLI-only flags). +- **Depended on by**: `make sample-conf-check` Makefile / CI target. ## Invariants -- Flags in `skippedFlags` (`configfile`, `help`, `version`) are excluded from - the check — they are tool flags with no meaningful conf-file representation. -- The tool reads `defaultConfFile` (`sample-darepod.conf`) from the repo root - and `mainFile` (`cmd/darepod/main.go`) to locate the daemon entry point. +- Flags in `skippedFlags` (`configfile`, `help`, `version`) are excluded — + tool flags with no meaningful conf-file representation. +- Every entry in `sample-darepod.conf` must stay commented (`# key=value`); + a live (uncommented) line is a hard error. +- `registeredDaemonFlags` assumes daemon flags in `cmd/darepod/main.go` are + registered with literal string arguments; helper/variable-based + registration needs a parser update alongside that refactor. ## Deep Docs diff --git a/scripts/verify-schema-registry/AGENTS.md b/scripts/verify-schema-registry/AGENTS.md index b272bbe4c..685753a78 100644 --- a/scripts/verify-schema-registry/AGENTS.md +++ b/scripts/verify-schema-registry/AGENTS.md @@ -9,19 +9,21 @@ success, 1 on drift. Run via `make schema-check`. ## Key Functions -- `main()` — Entry point: parses the CLI package, extracts all three sets, runs +- `main()` — Parses the CLI package, extracts all three sets, filters + schema-introspection/MCP-server commands out of the cobra set, runs both subset checks, and prints a summary or error list. - `extractSchemaMethods(pkg)` — Walks `methodRegistry()` body looking for `Method` key-value fields in composite literals; returns sorted method names. - `extractMCPToolNames(pkg)` — Walks all `mcp.AddTool[T](...)` calls and extracts the `Name` field from the `&mcp.Tool{}` argument; returns sorted tool names. -- `extractCobraLeafCommands(pkg)` — Walks all `new*Cmd()` functions to build a - dotted command-path tree (e.g. `fees.estimate`); returns leaf commands that - have a `RunE` handler; excludes schema-introspection and MCP server commands. -- `checkSubset(setA, setB, transform)` — Verifies every element of setA (after - optional name transform) appears in setB. One-directional: setB may have extra - entries (some schema methods are CLI-only). +- `extractCobraLeafCommands(pkg)` — Walks all `new*Cmd()` functions, builds a + parent/child tree from `AddCommand` calls starting at `newRootCmd`, and + returns dotted `Use`-field paths (e.g. `fees.estimate`) for commands that + set `RunE`. +- `checkSubset(nameA, setA, nameB, setB, transform)` — Verifies every element + of setA (after `transform`) appears in setB; one-directional, setB may have + extra entries. ## Relationships @@ -33,10 +35,12 @@ success, 1 on drift. Run via `make schema-check`. - MCP tool names use `namespace_method` format; schema registry uses `namespace.method`. `mcpToSchema` converts by replacing the first underscore. -- Cobra command paths use dotted notation matching the schema registry key. -- The check is one-directional for MCP vs schema: every MCP tool must have a - schema entry, but schema entries for sensitive CLI-only operations (wallet key - material, etc.) need not have an MCP tool. +- `schemaToCobra` is the identity function: cobra `Use` paths must already + match the schema registry key verbatim. +- Both checks are one-directional: every MCP tool must map to a schema entry, + and every RPC cobra command (after excluding `schema`/`mcp`-prefixed meta + commands) must map to a schema entry. Schema entries may have no MCP/cobra + counterpart (e.g. sensitive CLI-only wallet operations). ## Deep Docs diff --git a/scripts/verify-schema-registry/CLAUDE.md b/scripts/verify-schema-registry/CLAUDE.md index b272bbe4c..685753a78 100644 --- a/scripts/verify-schema-registry/CLAUDE.md +++ b/scripts/verify-schema-registry/CLAUDE.md @@ -9,19 +9,21 @@ success, 1 on drift. Run via `make schema-check`. ## Key Functions -- `main()` — Entry point: parses the CLI package, extracts all three sets, runs +- `main()` — Parses the CLI package, extracts all three sets, filters + schema-introspection/MCP-server commands out of the cobra set, runs both subset checks, and prints a summary or error list. - `extractSchemaMethods(pkg)` — Walks `methodRegistry()` body looking for `Method` key-value fields in composite literals; returns sorted method names. - `extractMCPToolNames(pkg)` — Walks all `mcp.AddTool[T](...)` calls and extracts the `Name` field from the `&mcp.Tool{}` argument; returns sorted tool names. -- `extractCobraLeafCommands(pkg)` — Walks all `new*Cmd()` functions to build a - dotted command-path tree (e.g. `fees.estimate`); returns leaf commands that - have a `RunE` handler; excludes schema-introspection and MCP server commands. -- `checkSubset(setA, setB, transform)` — Verifies every element of setA (after - optional name transform) appears in setB. One-directional: setB may have extra - entries (some schema methods are CLI-only). +- `extractCobraLeafCommands(pkg)` — Walks all `new*Cmd()` functions, builds a + parent/child tree from `AddCommand` calls starting at `newRootCmd`, and + returns dotted `Use`-field paths (e.g. `fees.estimate`) for commands that + set `RunE`. +- `checkSubset(nameA, setA, nameB, setB, transform)` — Verifies every element + of setA (after `transform`) appears in setB; one-directional, setB may have + extra entries. ## Relationships @@ -33,10 +35,12 @@ success, 1 on drift. Run via `make schema-check`. - MCP tool names use `namespace_method` format; schema registry uses `namespace.method`. `mcpToSchema` converts by replacing the first underscore. -- Cobra command paths use dotted notation matching the schema registry key. -- The check is one-directional for MCP vs schema: every MCP tool must have a - schema entry, but schema entries for sensitive CLI-only operations (wallet key - material, etc.) need not have an MCP tool. +- `schemaToCobra` is the identity function: cobra `Use` paths must already + match the schema registry key verbatim. +- Both checks are one-directional: every MCP tool must map to a schema entry, + and every RPC cobra command (after excluding `schema`/`mcp`-prefixed meta + commands) must map to a schema entry. Schema entries may have no MCP/cobra + counterpart (e.g. sensitive CLI-only wallet operations). ## Deep Docs diff --git a/sdk/ark/AGENTS.md b/sdk/ark/AGENTS.md index f5097469e..c93d416a3 100644 --- a/sdk/ark/AGENTS.md +++ b/sdk/ark/AGENTS.md @@ -13,81 +13,60 @@ transport, without duplicating Ark runtime behavior. transport shutdown and, in embedded mode, exposes `Wait()` for the daemon run result. Constructed via `DialRemote`, `StartEmbedded`, `WrapDaemonClient`, or `WrapDaemonServer`. -- `RemoteConfig` — Remote daemon dialing config. Secure by default: callers - must provide transport credentials or explicitly opt into insecure - transport for local development. -- `EmbeddedConfig` — In-process daemon hosting config. Currently passes - through a cloned `*darepod.Config`; the SDK hides transport and lifecycle - management, not the full daemon config surface. -- `InProcessConfig` — Config for `WrapDaemonServer`. Wraps an - already-running `daemonrpc.DaemonServiceServer` behind a private - bufconn-backed gRPC server. Holds the `DaemonServer`, optional - `BufferSize`, `DialOptions`, and `ServerOptions`. The returned `Client` - owns only the private transport, not the supplied daemon runtime. -- `WrapDaemonServer` — Constructor that creates a `Client` facade - over an in-process daemon RPC implementation without dialing the daemon's - public network listener. Used for tight in-process integration where the - host already owns the daemon runtime. -- `WrapDaemonClient` — Constructor that creates a `Client` from an - already-connected `daemonrpc.DaemonServiceClient` and a caller-supplied - `closeFn`. -- `Info` / `ServerInfo` / `Seed` / `WalletInitResult` — SDK-owned typed - models for daemon status and wallet bootstrap flows. -- `VTXOInfo` — Typed VTXO view (Outpoint, AmountSat, Status, BatchExpiry, - RoundID, CreatedHeight, etc.) returned by `ListLiveVTXOs` / - `ListSpentVTXOs`. -- `ReceiveInfo` — Typed receive destination (PkScript, PubKeyXOnly) returned - by `NewReceiveScript` / `AllocateReceiveScript`. -- `IndexedOORSessionInfo` — Indexed OOR session view (ArkPSBT, - CheckpointPSBTs) returned by `GetIndexedOORSession` lookups. -- `CustomOORInput` — Caller-specified OOR input carrying a policy template, - spend path, and UTXO info for `SendOORWithCustomInputs`. -- Policy/OOR helpers such as `SendOORWithPolicy`, `SendOORWithCustomInputs`, - typed indexed VTXO lookups, and typed receive-script decoding belong here - so higher-level packages do not rebuild daemonrpc adapters. +- `RemoteConfig` / `EmbeddedConfig` / `InProcessConfig` — Dial, in-process + hosting, and bufconn-wrapping configs for the three constructors above. + `RemoteConfig` is secure by default: callers must supply transport + credentials or explicitly set `AllowInsecure`. +- `Info` / `ServerInfo` / `Seed` / `WalletInitResult` / `WalletState` — + SDK-owned models for daemon status, cached operator terms, and wallet + bootstrap flows. `Info.WalletReady()` checks `WalletState == + WalletStateReady`. +- `VTXOInfo` / `VTXOExpiryInfo` — Typed VTXO view and expiry classification + returned by `ListLiveVTXOs`, `ListSpentVTXOs`, `GetVTXOExpiryInfo`. +- `ReceiveInfo` — Typed receive destination returned by `NewReceiveScript` / + `AllocateReceiveScript`. +- `CustomOORInput` / `TaprootScriptSignature` / `PreparedOOR` / + `PreparedOORCustomInput` / `OORSendResult` / `IndexedOORSessionInfo` — + OOR request/response models used by `SendOORWithCustomInputs`, + `PrepareOORWithCustomInputs`, `SignOORCustomInput`, + `SendOORWithPolicyAndKeyDetails`, and `GetIndexedOORSession`. +- VHTLC recovery passthroughs: `ArmVHTLCRecovery`, `EscalateVHTLCRecovery`, + `CancelVHTLCRecovery`, `GetVHTLCRecoveryStatus` — durable dormant-recovery + lifecycle for higher-level swap FSMs; return `daemonrpc` types directly. - Receive-auth helpers: `ReceiveAuthKey`, `SignReceiveAuthMessage`, - `SignReceiveAuthMessageCompact`, `ReceiveAuthECDH` — delegate payment-scoped - signing and Sphinx ECDH operations to the daemon wallet without exposing the - raw private key to the SDK caller. Used by `sdk/swaps` for receive invoice - signing and onion decoding. -- `OORSessionDirection` — Enum (`OORSessionDirectionAll`, - `OORSessionDirectionOutgoing`, `OORSessionDirectionIncoming`) for - filtering local OOR session listings. -- `ListOORSessionsRequest` — Filter struct: `PendingOnly bool`, - `Direction OORSessionDirection`. -- `OORSessionInfo` — Typed view of one locally persisted OOR session: - `SessionID`, `Direction`, `Phase`, `Pending`, `RetryAfter`, - `RetryReason`, `InputOutpoints`, `InputAmountSat`, `RecipientCount`. -- `ListLocalOORSessions(ctx, ListOORSessionsRequest) ([]OORSessionInfo, - error)` — Typed wrapper converting proto response to SDK types. -- `ListPendingOORSessions(ctx) ([]OORSessionInfo, error)` — Convenience - wrapper calling `ListLocalOORSessions` with `PendingOnly: true`. -- `ListOORSessions` — Lower-level passthrough returning the raw - `*daemonrpc.ListOORSessionsResponse`; `ListLocalOORSessions` is preferred - for new callers. + `SignReceiveAuthMessageCompact`, `ReceiveAuthECDH` — delegate + payment-scoped signing and Sphinx ECDH to the daemon wallet without + exposing raw key material. Used by `sdk/swaps` for receive invoice signing + and onion decoding. +- `GetOORSession` — Single-session lookup of the daemon's local durable OOR + transfer status, returning `*daemonrpc.OORSessionInfo`. +- `Board`, `ListRounds`, `WatchRounds`, `EstimateFee`, `GetFeeHistory` — Round + and fee passthroughs returning `daemonrpc` request/response types directly. ## Relationships - **Depends on**: `daemonrpc`, `darepod` (embedded mode only), gRPC, `google.golang.org/grpc/test/bufconn` (in-process transport). - **Depended on by**: `sdk/swaps` (type aliases, receive-auth RPCs, OOR - helpers), Go hosts that want remote, embedded, or in-process Ark client - access. + helpers), `swapclientserver`, Go hosts that want remote, embedded, or + in-process Ark client access. ## Invariants - `Client` is safe for concurrent use. - `darepod` remains the canonical Ark runtime; `sdk/ark` must not reimplement wallet, round, OOR, or persistence behavior. -- Embedded startup must not mutate the caller's daemon config. +- Embedded startup must not mutate the caller's daemon config + (`cloneDaemonConfig` deep-copies reference-typed fields; update it when + `darepod.Config` gains new reference fields). - Embedded startup waits until the in-process daemon is accepting RPCs before returning. - Embedded `Wait()` returns a blocking channel that surfaces the daemon's - terminal run error. -- `Close()` is idempotent and bounds embedded shutdown wait time. -- `WrapDaemonServer` owns only the private bufconn transport and gRPC server; - it does not own the caller's `DaemonServer` runtime. `Close()` tears down - only the private transport. + terminal run error; remote clients return an already-closed channel. +- `Close()` is idempotent and bounds embedded/in-process shutdown wait time. +- `WrapDaemonServer` owns only the private bufconn transport and gRPC + server; it does not own the caller's `DaemonServer` runtime. `Close()` + tears down only the private transport. - `ServerInfo` is a bootstrap-time operator-terms snapshot; refresh after reconnect is not wired through yet. - Pre-1.0, some methods intentionally return `daemonrpc` protobuf types @@ -95,3 +74,9 @@ transport, without duplicating Ark runtime behavior. models. - Receive-auth signing and ECDH are always delegated to the daemon; the SDK never holds raw private key material for receive-auth operations. + +## Deep Docs + +- [docs/sdk_layered_architecture.md](../../docs/sdk_layered_architecture.md) + — Layered SDK architecture, error categorization, daemonrpc versioning +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/ark/CLAUDE.md b/sdk/ark/CLAUDE.md index f5097469e..c93d416a3 100644 --- a/sdk/ark/CLAUDE.md +++ b/sdk/ark/CLAUDE.md @@ -13,81 +13,60 @@ transport, without duplicating Ark runtime behavior. transport shutdown and, in embedded mode, exposes `Wait()` for the daemon run result. Constructed via `DialRemote`, `StartEmbedded`, `WrapDaemonClient`, or `WrapDaemonServer`. -- `RemoteConfig` — Remote daemon dialing config. Secure by default: callers - must provide transport credentials or explicitly opt into insecure - transport for local development. -- `EmbeddedConfig` — In-process daemon hosting config. Currently passes - through a cloned `*darepod.Config`; the SDK hides transport and lifecycle - management, not the full daemon config surface. -- `InProcessConfig` — Config for `WrapDaemonServer`. Wraps an - already-running `daemonrpc.DaemonServiceServer` behind a private - bufconn-backed gRPC server. Holds the `DaemonServer`, optional - `BufferSize`, `DialOptions`, and `ServerOptions`. The returned `Client` - owns only the private transport, not the supplied daemon runtime. -- `WrapDaemonServer` — Constructor that creates a `Client` facade - over an in-process daemon RPC implementation without dialing the daemon's - public network listener. Used for tight in-process integration where the - host already owns the daemon runtime. -- `WrapDaemonClient` — Constructor that creates a `Client` from an - already-connected `daemonrpc.DaemonServiceClient` and a caller-supplied - `closeFn`. -- `Info` / `ServerInfo` / `Seed` / `WalletInitResult` — SDK-owned typed - models for daemon status and wallet bootstrap flows. -- `VTXOInfo` — Typed VTXO view (Outpoint, AmountSat, Status, BatchExpiry, - RoundID, CreatedHeight, etc.) returned by `ListLiveVTXOs` / - `ListSpentVTXOs`. -- `ReceiveInfo` — Typed receive destination (PkScript, PubKeyXOnly) returned - by `NewReceiveScript` / `AllocateReceiveScript`. -- `IndexedOORSessionInfo` — Indexed OOR session view (ArkPSBT, - CheckpointPSBTs) returned by `GetIndexedOORSession` lookups. -- `CustomOORInput` — Caller-specified OOR input carrying a policy template, - spend path, and UTXO info for `SendOORWithCustomInputs`. -- Policy/OOR helpers such as `SendOORWithPolicy`, `SendOORWithCustomInputs`, - typed indexed VTXO lookups, and typed receive-script decoding belong here - so higher-level packages do not rebuild daemonrpc adapters. +- `RemoteConfig` / `EmbeddedConfig` / `InProcessConfig` — Dial, in-process + hosting, and bufconn-wrapping configs for the three constructors above. + `RemoteConfig` is secure by default: callers must supply transport + credentials or explicitly set `AllowInsecure`. +- `Info` / `ServerInfo` / `Seed` / `WalletInitResult` / `WalletState` — + SDK-owned models for daemon status, cached operator terms, and wallet + bootstrap flows. `Info.WalletReady()` checks `WalletState == + WalletStateReady`. +- `VTXOInfo` / `VTXOExpiryInfo` — Typed VTXO view and expiry classification + returned by `ListLiveVTXOs`, `ListSpentVTXOs`, `GetVTXOExpiryInfo`. +- `ReceiveInfo` — Typed receive destination returned by `NewReceiveScript` / + `AllocateReceiveScript`. +- `CustomOORInput` / `TaprootScriptSignature` / `PreparedOOR` / + `PreparedOORCustomInput` / `OORSendResult` / `IndexedOORSessionInfo` — + OOR request/response models used by `SendOORWithCustomInputs`, + `PrepareOORWithCustomInputs`, `SignOORCustomInput`, + `SendOORWithPolicyAndKeyDetails`, and `GetIndexedOORSession`. +- VHTLC recovery passthroughs: `ArmVHTLCRecovery`, `EscalateVHTLCRecovery`, + `CancelVHTLCRecovery`, `GetVHTLCRecoveryStatus` — durable dormant-recovery + lifecycle for higher-level swap FSMs; return `daemonrpc` types directly. - Receive-auth helpers: `ReceiveAuthKey`, `SignReceiveAuthMessage`, - `SignReceiveAuthMessageCompact`, `ReceiveAuthECDH` — delegate payment-scoped - signing and Sphinx ECDH operations to the daemon wallet without exposing the - raw private key to the SDK caller. Used by `sdk/swaps` for receive invoice - signing and onion decoding. -- `OORSessionDirection` — Enum (`OORSessionDirectionAll`, - `OORSessionDirectionOutgoing`, `OORSessionDirectionIncoming`) for - filtering local OOR session listings. -- `ListOORSessionsRequest` — Filter struct: `PendingOnly bool`, - `Direction OORSessionDirection`. -- `OORSessionInfo` — Typed view of one locally persisted OOR session: - `SessionID`, `Direction`, `Phase`, `Pending`, `RetryAfter`, - `RetryReason`, `InputOutpoints`, `InputAmountSat`, `RecipientCount`. -- `ListLocalOORSessions(ctx, ListOORSessionsRequest) ([]OORSessionInfo, - error)` — Typed wrapper converting proto response to SDK types. -- `ListPendingOORSessions(ctx) ([]OORSessionInfo, error)` — Convenience - wrapper calling `ListLocalOORSessions` with `PendingOnly: true`. -- `ListOORSessions` — Lower-level passthrough returning the raw - `*daemonrpc.ListOORSessionsResponse`; `ListLocalOORSessions` is preferred - for new callers. + `SignReceiveAuthMessageCompact`, `ReceiveAuthECDH` — delegate + payment-scoped signing and Sphinx ECDH to the daemon wallet without + exposing raw key material. Used by `sdk/swaps` for receive invoice signing + and onion decoding. +- `GetOORSession` — Single-session lookup of the daemon's local durable OOR + transfer status, returning `*daemonrpc.OORSessionInfo`. +- `Board`, `ListRounds`, `WatchRounds`, `EstimateFee`, `GetFeeHistory` — Round + and fee passthroughs returning `daemonrpc` request/response types directly. ## Relationships - **Depends on**: `daemonrpc`, `darepod` (embedded mode only), gRPC, `google.golang.org/grpc/test/bufconn` (in-process transport). - **Depended on by**: `sdk/swaps` (type aliases, receive-auth RPCs, OOR - helpers), Go hosts that want remote, embedded, or in-process Ark client - access. + helpers), `swapclientserver`, Go hosts that want remote, embedded, or + in-process Ark client access. ## Invariants - `Client` is safe for concurrent use. - `darepod` remains the canonical Ark runtime; `sdk/ark` must not reimplement wallet, round, OOR, or persistence behavior. -- Embedded startup must not mutate the caller's daemon config. +- Embedded startup must not mutate the caller's daemon config + (`cloneDaemonConfig` deep-copies reference-typed fields; update it when + `darepod.Config` gains new reference fields). - Embedded startup waits until the in-process daemon is accepting RPCs before returning. - Embedded `Wait()` returns a blocking channel that surfaces the daemon's - terminal run error. -- `Close()` is idempotent and bounds embedded shutdown wait time. -- `WrapDaemonServer` owns only the private bufconn transport and gRPC server; - it does not own the caller's `DaemonServer` runtime. `Close()` tears down - only the private transport. + terminal run error; remote clients return an already-closed channel. +- `Close()` is idempotent and bounds embedded/in-process shutdown wait time. +- `WrapDaemonServer` owns only the private bufconn transport and gRPC + server; it does not own the caller's `DaemonServer` runtime. `Close()` + tears down only the private transport. - `ServerInfo` is a bootstrap-time operator-terms snapshot; refresh after reconnect is not wired through yet. - Pre-1.0, some methods intentionally return `daemonrpc` protobuf types @@ -95,3 +74,9 @@ transport, without duplicating Ark runtime behavior. models. - Receive-auth signing and ECDH are always delegated to the daemon; the SDK never holds raw private key material for receive-auth operations. + +## Deep Docs + +- [docs/sdk_layered_architecture.md](../../docs/sdk_layered_architecture.md) + — Layered SDK architecture, error categorization, daemonrpc versioning +- [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map diff --git a/sdk/swaps/AGENTS.md b/sdk/swaps/AGENTS.md index 949f0b6be..e27a523a2 100644 --- a/sdk/swaps/AGENTS.md +++ b/sdk/swaps/AGENTS.md @@ -58,15 +58,35 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/s - `SwapServerConn` / `GRPCSwapServerConn` — remote swap-server gRPC (`RequestChannelID`, `CreateInSwap`). - `DaemonConn` — wallet operations (OOR sends, VTXO lookups, key - queries, receive-auth signing/ECDH) provided by the Ark daemon. - Includes `ReceiveAuthKey`, `SignReceiveAuthMessage[Compact]`, and - `ReceiveAuthECDH` for payment-scoped auth. -- `InvoiceCreator` — BOLT-11 invoice building; `CreateInvoiceWithKey` - for invoices signed with a `ReceiveAuthKey`. + queries, receive-auth signing/ECDH, VHTLC recovery arm/escalate/ + cancel, forfeit signing) provided by the Ark daemon. Includes + `ReceiveAuthKey`, `SignReceiveAuthMessage[Compact]`, + `ReceiveAuthECDH`, and `SignVTXOForfeit`. +- `InvoiceCreator` — BOLT-11 invoice building interface; `CreateInvoiceWithKey` + for invoices signed with a `ReceiveAuthKey`. `InvoiceGenerator` is the + production implementation (delegates to lnd's `invoicesrpc.AddInvoice`); + `DirectInvoiceCreator` and `NewEphemeralInvoiceGenerator` are + source-compatible wrappers around it. - `PayState` / `ReceiveState` — typed FSM enums with `IsTerminal()` / `String()`. `ReceiveState` includes `ReceiveStateHTLCEventAccepted`. - `VHTLCConfig`, `InSwapConfig`, `RouteHint` — server-negotiation DTOs. `SwapSummary` — flat list view for persisted sessions. +- `RecoveryPolicy` / `DefaultRecoveryPolicy` — governs auto-escalation + from cooperative vHTLC retry to daemon-owned on-chain recovery + (arm/escalate/cancel via `DaemonConn`'s VHTLC recovery RPCs). +- `ForfeitSignaturePayload` / `ForfeitParticipantSignature` / + `OutSwapForfeitSignatureReceiver` / `OutSwapForfeitSignatureNotification` + — server-pushed out-swap vHTLC refresh signing requests. + `ForfeitSignaturePayloadFromVTXORequest` converts the `vtxo` package's + connector-bound sign request into the swap-server transcript shape; + `SignVTXOForfeitRequestFromPayload` maps it back to the daemon's local + signing RPC. +- `CreateCreditRequest` / `RedeemCreditRequest` / `CreditOperation` / + `CreditRedemption` / `CreditSnapshot` / `CreditLedgerEntry` — + server-authoritative "credit" account (a dust-clearing sat balance, + not a VTXO) accessed via `SwapClient.CreateCredit` / `RedeemCredit` / + `ListCredits`; requires the configured `SwapServerConn` to also + implement the unexported `creditServerConn` interface. - `OutSwapMailboxID` — derives a per-receive mailbox ID from the client identity key and invoice payment hash. - Error sentinels (exported): `ErrSwapExpired`, `ErrSwapRefunded`, @@ -78,16 +98,18 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/s - **Depends on**: `lib/arkscript` (vHTLC policy + claim/refund tapscript paths), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs), - `mailbox/pb` (edge pull/ack), `serverconn` (`CompoundMailboxID`, - `PubKeyMailboxID`), `db/migrate` + `db/sqlc`, `sdk/swaps/sqlc`, + `vtxo` (forfeit sign-request conversion), `mailbox/pb` (edge + pull/ack), `serverconn` + `serverconn/mailboxpull` (`CompoundMailboxID`, + `PubKeyMailboxID`, mailbox pull backoff), `db` + `db/migrate` + + `db/sqlc`, `sdk/swaps/sqlc`, `rpc/restclient` (REST swap-server conn), `loop/fsm` (FSM engine), `lightning-onion` (Sphinx ECDH). -- **Depended on by**: `cmd/darepocli/darepoclicommands` (`pay` / - `receive` commands). +- **Depended on by**: `swapclientserver` (RPC surface backing the + daemon's `pay`/`receive`/credit gRPC and CLI commands). ## Sends / Receives Both FSMs tick via `loopfsm.StateMachine.SendEvent(ctx, OnAdvance, -nil)`. Pay calls `DaemonConn.SendOORWithPolicy` to fund and +nil)`. Pay calls `DaemonConn.SendOORWithPolicyDetails` to fund and `SendOORWithCustomInputs` to refund. Receive calls `SendOORWithCustomInputs` to claim via the preimage spend path. @@ -125,9 +147,17 @@ result into an `IncomingVHTLCNotification`. the SDK never holds the raw private key for receive-auth. - Error sentinels (`ErrSwapExpired`, `ErrSwapRefunded`, `ErrSwapSummaryNotFound`) are exported; callers use `errors.Is`. +- The credit ledger is server-authoritative; local state only records + what the wallet asked for. Always treat `ListCredits` as the source + of truth after a retry or restart, not any locally cached operation. +- `RecoveryPolicy.MaxFeeRateSatPerKW` is captured at arm time and + stored on the recovery row, so a later, looser default cannot + silently raise the exit-spend fee cap for an already-armed job. ## Deep Docs +- [docs/swap_system.md](../../docs/swap_system.md) — Full walkthrough of + the vHTLC swap system, from CLI to wire. +- [docs/credit_system.md](../../docs/credit_system.md) — Credit ledger + semantics and dust-clearing model. - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/swaps/CLAUDE.md b/sdk/swaps/CLAUDE.md index 949f0b6be..e27a523a2 100644 --- a/sdk/swaps/CLAUDE.md +++ b/sdk/swaps/CLAUDE.md @@ -58,15 +58,35 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/s - `SwapServerConn` / `GRPCSwapServerConn` — remote swap-server gRPC (`RequestChannelID`, `CreateInSwap`). - `DaemonConn` — wallet operations (OOR sends, VTXO lookups, key - queries, receive-auth signing/ECDH) provided by the Ark daemon. - Includes `ReceiveAuthKey`, `SignReceiveAuthMessage[Compact]`, and - `ReceiveAuthECDH` for payment-scoped auth. -- `InvoiceCreator` — BOLT-11 invoice building; `CreateInvoiceWithKey` - for invoices signed with a `ReceiveAuthKey`. + queries, receive-auth signing/ECDH, VHTLC recovery arm/escalate/ + cancel, forfeit signing) provided by the Ark daemon. Includes + `ReceiveAuthKey`, `SignReceiveAuthMessage[Compact]`, + `ReceiveAuthECDH`, and `SignVTXOForfeit`. +- `InvoiceCreator` — BOLT-11 invoice building interface; `CreateInvoiceWithKey` + for invoices signed with a `ReceiveAuthKey`. `InvoiceGenerator` is the + production implementation (delegates to lnd's `invoicesrpc.AddInvoice`); + `DirectInvoiceCreator` and `NewEphemeralInvoiceGenerator` are + source-compatible wrappers around it. - `PayState` / `ReceiveState` — typed FSM enums with `IsTerminal()` / `String()`. `ReceiveState` includes `ReceiveStateHTLCEventAccepted`. - `VHTLCConfig`, `InSwapConfig`, `RouteHint` — server-negotiation DTOs. `SwapSummary` — flat list view for persisted sessions. +- `RecoveryPolicy` / `DefaultRecoveryPolicy` — governs auto-escalation + from cooperative vHTLC retry to daemon-owned on-chain recovery + (arm/escalate/cancel via `DaemonConn`'s VHTLC recovery RPCs). +- `ForfeitSignaturePayload` / `ForfeitParticipantSignature` / + `OutSwapForfeitSignatureReceiver` / `OutSwapForfeitSignatureNotification` + — server-pushed out-swap vHTLC refresh signing requests. + `ForfeitSignaturePayloadFromVTXORequest` converts the `vtxo` package's + connector-bound sign request into the swap-server transcript shape; + `SignVTXOForfeitRequestFromPayload` maps it back to the daemon's local + signing RPC. +- `CreateCreditRequest` / `RedeemCreditRequest` / `CreditOperation` / + `CreditRedemption` / `CreditSnapshot` / `CreditLedgerEntry` — + server-authoritative "credit" account (a dust-clearing sat balance, + not a VTXO) accessed via `SwapClient.CreateCredit` / `RedeemCredit` / + `ListCredits`; requires the configured `SwapServerConn` to also + implement the unexported `creditServerConn` interface. - `OutSwapMailboxID` — derives a per-receive mailbox ID from the client identity key and invoice payment hash. - Error sentinels (exported): `ErrSwapExpired`, `ErrSwapRefunded`, @@ -78,16 +98,18 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/s - **Depends on**: `lib/arkscript` (vHTLC policy + claim/refund tapscript paths), `sdk/ark` (type aliases), `swaprpc` (gRPC stubs), - `mailbox/pb` (edge pull/ack), `serverconn` (`CompoundMailboxID`, - `PubKeyMailboxID`), `db/migrate` + `db/sqlc`, `sdk/swaps/sqlc`, + `vtxo` (forfeit sign-request conversion), `mailbox/pb` (edge + pull/ack), `serverconn` + `serverconn/mailboxpull` (`CompoundMailboxID`, + `PubKeyMailboxID`, mailbox pull backoff), `db` + `db/migrate` + + `db/sqlc`, `sdk/swaps/sqlc`, `rpc/restclient` (REST swap-server conn), `loop/fsm` (FSM engine), `lightning-onion` (Sphinx ECDH). -- **Depended on by**: `cmd/darepocli/darepoclicommands` (`pay` / - `receive` commands). +- **Depended on by**: `swapclientserver` (RPC surface backing the + daemon's `pay`/`receive`/credit gRPC and CLI commands). ## Sends / Receives Both FSMs tick via `loopfsm.StateMachine.SendEvent(ctx, OnAdvance, -nil)`. Pay calls `DaemonConn.SendOORWithPolicy` to fund and +nil)`. Pay calls `DaemonConn.SendOORWithPolicyDetails` to fund and `SendOORWithCustomInputs` to refund. Receive calls `SendOORWithCustomInputs` to claim via the preimage spend path. @@ -125,9 +147,17 @@ result into an `IncomingVHTLCNotification`. the SDK never holds the raw private key for receive-auth. - Error sentinels (`ErrSwapExpired`, `ErrSwapRefunded`, `ErrSwapSummaryNotFound`) are exported; callers use `errors.Is`. +- The credit ledger is server-authoritative; local state only records + what the wallet asked for. Always treat `ListCredits` as the source + of truth after a retry or restart, not any locally cached operation. +- `RecoveryPolicy.MaxFeeRateSatPerKW` is captured at arm time and + stored on the recovery row, so a later, looser default cannot + silently raise the exit-spend fee cap for an already-armed job. ## Deep Docs +- [docs/swap_system.md](../../docs/swap_system.md) — Full walkthrough of + the vHTLC swap system, from CLI to wire. +- [docs/credit_system.md](../../docs/credit_system.md) — Credit ledger + semantics and dust-clearing model. - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/walletdk/AGENTS.md b/sdk/walletdk/AGENTS.md index 73da35ef1..e04ffc58c 100644 --- a/sdk/walletdk/AGENTS.md +++ b/sdk/walletdk/AGENTS.md @@ -33,6 +33,12 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w waits for gRPC readiness, and returns a ready `*Client`. The daemon lifetime is owned by walletdk's `runCtx`, not the caller's `ctx`, so a tight startup deadline cancels dialing only. +- `Connect(ctx, ConnectConfig)` — dials an already-running external + daemon instead of embedding one. `ConnectConfig.Transport` selects + `TransportGRPC` (default) or `TransportREST`; `Insecure`, + `TLSCertPath`, and `MacaroonPath` configure auth. `Client.Stop`/ + `Close` on a `Connect`-built client releases only the transport, not + a daemon runtime. - `Option` — functional option accepted as variadic trailing args. Options apply **after** the `Config`/`DaemonConfig` merge and after `configureSwapRuntime` / `configureWalletRPC`, so they can override @@ -49,8 +55,10 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w bounded send and the swept total for sweep-all), `DepositRequest`/`Result` (boarding address + initial `Entry`), `ListRequest`, `ListResult` (tagged union on `View`, populates one of `Activity`/`VTXOs`/`Onchain`), - `ActivityList`, `VTXOInventory`, `OnchainHistory`, `Entry`, - `WalletVTXO`, `OnchainTx`. + `ActivityList`, `VTXOInventory`, `OnchainHistory`, `Entry` + (optional `Progress *EntryProgress` and `Request *EntryRequest` + sub-objects, both nil when absent; `Request` is a `Type`-tagged + union over lightning/onchain/ark), `WalletVTXO`, `OnchainTx`. - `ExitRequest` / `ExitResult` / `ExitStatusRequest` / `ExitStatusResult` / `ExitJobStatus` — exit DTOs. `ExitRequest` carries the target outpoint plus an optional on-chain `Destination` @@ -67,6 +75,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w populated by current behavior. Status strings are the wrapper-owned lowercase set (`pending`/`materializing`/`csv_pending`/`sweeping`/`completed`/`failed`/`unspecified`). +- `GetExitPlanRequest`/`Result`, `ExitPlanEntry` — previews backing-wallet + funding needed before `Exit` can start, per outpoint and aggregated. +- `ExitSummaryRequest`/`Result`, `ExitSummaryEntry` — wallet-wide + portfolio of in-progress (non-terminal) exits plus aggregate totals. +- `SweepWalletRequest`/`Result`, `WalletSweepInput` — preview or + broadcast a full backing-wallet sweep to one destination address. +- `CreditPreview`, `SendRail`, `SendQuoteStatus` — embedded in + `PrepareSendResult`; describe whether a prepared send will draw on + sat-native server credits and how complete the prepare-time quote is. - `ErrWalletRPCUnavailable` — sentinel returned by every wallet method on builds without the `walletdkrpc` tag. - `ErrSwapRuntimeUnavailable` — back-compat alias for @@ -87,11 +104,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w | `List` | Unified history view (Activity / VTXOs / Onchain) as a tagged-union `ListResult`. | | `Exit` | Trigger cooperative leave or unilateral unroll for a VTXO. | | `ExitStatus` | Query the phase of an exit job. | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits plus aggregate totals. | +| `GetExitPlan` | Preview backing-wallet funding needed before `Exit` can start. | +| `SweepWallet` | Preview or broadcast a full backing-wallet sweep. | | `Status` | Wallet readiness, balance, pending-entry count. | | `Subscribe` | Stream wallet activity (`Entry`) updates. | -| `Stop` / `Close` | Shut down the embedded daemon, release the private transport. | +| `Stop` / `Close` | Shut down the embedded daemon, or release the transport for a `Connect`-built client. | | `Wait` | Single shared channel yielding the daemon's terminal run error. | -| `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | +| `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` / `BtcwalletRPC` / `BtcwalletVersionRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | ## Relationships @@ -99,11 +119,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w (wallet, balance, info, address RPCs + direct paths for `CreateWallet`/`UnlockWallet`), `rpc/walletdkrpc` (unified wallet API the seven verbs target), `rpc/swapclientrpc` (raw-swap escape hatch), - `swapclientserver` (registered as daemon-side swap subserver in - `swapruntime` builds), `swapwallet` (daemon-side walletdkrpc subserver - in `walletdkrpc` builds), `google.golang.org/grpc/test/bufconn`. -- **Depended on by**: host Go apps, gomobile / React Native / WASM - bridges, and `cmd/walletdk-tui`. + `rpc/restclient` (REST transport for `Connect`), `rpcauth` (macaroon / + TLS-cert helpers for `Connect`), `swapclientserver` (registered as + daemon-side swap subserver in `swapruntime` builds), `swapwallet` + (daemon-side walletdkrpc subserver in `walletdkrpc` builds), + `google.golang.org/grpc/test/bufconn`. +- **Depended on by**: host Go apps directly, and `sdk/walletdk/mobile` + (gomobile bridge consumed by `cmd/walletdk-wasm` and React + Native/mobile hosts). - **Sends** → `darepod` (in-process via bufconn): all daemon RPCs are routed across the private gRPC connection, not the daemon's public listener. @@ -142,15 +165,21 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w the wrapper boundary on builds without the `walletdkrpc` tag, before any RPC is attempted. `ErrSwapRuntimeUnavailable` is an alias for source-level compatibility with older swap-only callers. -- `Entry.Kind`/`Entry.Status` / `ListResult.View` / - `WalletVTXO.Status` / `OnchainTx.Kind` / `ExitJobStatus` are - wrapper-owned lowercase strings (not proto enums). Projection lives - in `convert.go`, intentionally decoupled from proto enum - renumbering. +- `Entry.Kind`/`Entry.Status` / `Entry.Progress.Phase` / + `Entry.Request.Type` / `ListResult.View` / `WalletVTXO.Status` / + `OnchainTx.Kind` / `ExitJobStatus` are wrapper-owned lowercase + strings (not proto enums). Projection lives in `convert.go`, + intentionally decoupled from proto enum renumbering. - `ListResult` is a discriminated union: read the variant named by `View` and treat the others as `nil`. Exhaustiveness is not enforced at compile time — switch on `View` rather than chaining nil checks. +- `Entry.Progress` and `Entry.Request` are optional pointers: both are + `nil` when the daemon supplied no progress hint / persisted no + request, so nil-check before dereferencing. `Entry.Request` is a + discriminated union — read the variant named by `Type` + (`lightning`/`onchain`/`ark`) and treat the other fields as zero, + the same idiom as `ListResult.View`. - `Wait()` is single-reader: same shared channel on every call. The channel delivers the daemon's terminal run error then closes; a closed channel reads as the zero error indefinitely. @@ -177,5 +206,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w - [swapclientserver/CLAUDE.md](../../swapclientserver/CLAUDE.md) — Daemon-side swap subserver (`-tags swapruntime`). - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/sdk/walletdk/CLAUDE.md b/sdk/walletdk/CLAUDE.md index c435f39ae..e04ffc58c 100644 --- a/sdk/walletdk/CLAUDE.md +++ b/sdk/walletdk/CLAUDE.md @@ -33,6 +33,12 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w waits for gRPC readiness, and returns a ready `*Client`. The daemon lifetime is owned by walletdk's `runCtx`, not the caller's `ctx`, so a tight startup deadline cancels dialing only. +- `Connect(ctx, ConnectConfig)` — dials an already-running external + daemon instead of embedding one. `ConnectConfig.Transport` selects + `TransportGRPC` (default) or `TransportREST`; `Insecure`, + `TLSCertPath`, and `MacaroonPath` configure auth. `Client.Stop`/ + `Close` on a `Connect`-built client releases only the transport, not + a daemon runtime. - `Option` — functional option accepted as variadic trailing args. Options apply **after** the `Config`/`DaemonConfig` merge and after `configureSwapRuntime` / `configureWalletRPC`, so they can override @@ -69,6 +75,15 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w populated by current behavior. Status strings are the wrapper-owned lowercase set (`pending`/`materializing`/`csv_pending`/`sweeping`/`completed`/`failed`/`unspecified`). +- `GetExitPlanRequest`/`Result`, `ExitPlanEntry` — previews backing-wallet + funding needed before `Exit` can start, per outpoint and aggregated. +- `ExitSummaryRequest`/`Result`, `ExitSummaryEntry` — wallet-wide + portfolio of in-progress (non-terminal) exits plus aggregate totals. +- `SweepWalletRequest`/`Result`, `WalletSweepInput` — preview or + broadcast a full backing-wallet sweep to one destination address. +- `CreditPreview`, `SendRail`, `SendQuoteStatus` — embedded in + `PrepareSendResult`; describe whether a prepared send will draw on + sat-native server credits and how complete the prepare-time quote is. - `ErrWalletRPCUnavailable` — sentinel returned by every wallet method on builds without the `walletdkrpc` tag. - `ErrSwapRuntimeUnavailable` — back-compat alias for @@ -89,11 +104,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w | `List` | Unified history view (Activity / VTXOs / Onchain) as a tagged-union `ListResult`. | | `Exit` | Trigger cooperative leave or unilateral unroll for a VTXO. | | `ExitStatus` | Query the phase of an exit job. | +| `ExitSummary` | Wallet-wide portfolio of in-progress exits plus aggregate totals. | +| `GetExitPlan` | Preview backing-wallet funding needed before `Exit` can start. | +| `SweepWallet` | Preview or broadcast a full backing-wallet sweep. | | `Status` | Wallet readiness, balance, pending-entry count. | | `Subscribe` | Stream wallet activity (`Entry`) updates. | -| `Stop` / `Close` | Shut down the embedded daemon, release the private transport. | +| `Stop` / `Close` | Shut down the embedded daemon, or release the transport for a `Connect`-built client. | | `Wait` | Single shared channel yielding the daemon's terminal run error. | -| `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | +| `GRPCConn` / `ArkRPC` / `SwapRPC` / `WalletRPC` / `BtcwalletRPC` / `BtcwalletVersionRPC` | Escape hatches to the underlying private gRPC conn and raw clients. | ## Relationships @@ -101,11 +119,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w (wallet, balance, info, address RPCs + direct paths for `CreateWallet`/`UnlockWallet`), `rpc/walletdkrpc` (unified wallet API the seven verbs target), `rpc/swapclientrpc` (raw-swap escape hatch), - `swapclientserver` (registered as daemon-side swap subserver in - `swapruntime` builds), `swapwallet` (daemon-side walletdkrpc subserver - in `walletdkrpc` builds), `google.golang.org/grpc/test/bufconn`. -- **Depended on by**: host Go apps, gomobile / React Native / WASM - bridges, and `cmd/walletdk-tui`. + `rpc/restclient` (REST transport for `Connect`), `rpcauth` (macaroon / + TLS-cert helpers for `Connect`), `swapclientserver` (registered as + daemon-side swap subserver in `swapruntime` builds), `swapwallet` + (daemon-side walletdkrpc subserver in `walletdkrpc` builds), + `google.golang.org/grpc/test/bufconn`. +- **Depended on by**: host Go apps directly, and `sdk/walletdk/mobile` + (gomobile bridge consumed by `cmd/walletdk-wasm` and React + Native/mobile hosts). - **Sends** → `darepod` (in-process via bufconn): all daemon RPCs are routed across the private gRPC connection, not the daemon's public listener. @@ -185,5 +206,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/sdk/w - [swapclientserver/CLAUDE.md](../../swapclientserver/CLAUDE.md) — Daemon-side swap subserver (`-tags swapruntime`). - [ARCHITECTURE.md](../../ARCHITECTURE.md) — System-wide package map. - - diff --git a/serverconn/AGENTS.md b/serverconn/AGENTS.md index 365942130..c976c55f6 100644 --- a/serverconn/AGENTS.md +++ b/serverconn/AGENTS.md @@ -10,6 +10,7 @@ background ingress polling with event routing. - `Runtime` — Main entry point wrapping DurableActor, ServerConnectionActor, and UnaryFacade. The egress DurableActor runs on the Read/Commit (`TxBehavior`) path: each handler builds its envelope and calls `Edge.Send` with NO SQLite writer held, then a short lease-fenced Commit folds the ack + dedup. It runs as a competing-consumer pool of `ConnectorConfig.EgressWorkers` worker loops, so the round and out-of-round actors' sends proceed concurrently; the single ingress puller is separate and unaffected. - `ServerConnectionActor` — Core behavior handling egress messages and the ingress loop. Dispatches `DurableUnaryQuery` values generically via `buildDurableUnary`. +- `ArkVersionNegotiator` — Single home for Ark protocol version selection (`ark_version.go`). `Bootstrap` performs the one bootstrap `GetInfo` over the operator's **direct** ArkService connection (`ArkVersionGetInfoClient`, never the mailbox edge) and returns the response + selected version; the daemon parses domain terms from the same response. The free function `ValidateRefreshSelection(resp, boundVersion)` enforces that a refresh-only `GetInfo` keeps the runtime bound (returns a permanent `*StatusError` on drift/disable). Enabled versions are derived from the response's ACTIVE `ArkVersionPolicy` entries. - `UnaryFacade` — Implements `mailboxrpc.RPCClient` for generated RPC stubs (low-latency path). Also provides `AwaitRPCTimeout` for bounded waits. - `ConnectorConfig` — Wiring configuration (edge address, mailbox IDs, dispatchers, store, durable unary builder, `EgressWorkers`). `EgressWorkers` sizes the egress worker pool (default `DefaultEgressWorkers` = 4); `<= 1` keeps the legacy single sender. The `DurableUnaryBuilder` field must be set to handle `DurableUnaryQuery` message types; otherwise those messages are rejected. The `AuthSignature` field holds the Schnorr auth sig injected into every outbound envelope via `mergeAuthHeaders` (auth header always wins over caller-provided headers). - `PubKeyMailboxID` — Derives canonical mailbox ID from a public key (hex-encoded compressed SEC). Panics on nil. @@ -17,6 +18,19 @@ background ingress polling with event routing. - `SignMailboxAuth` / `VerifyMailboxAuth` / `ParseMailboxPubKey` — Schnorr sign/verify helpers for pubkey-derived mailbox identity. - `AuthHeaderKey` — Envelope header key (`x-mailbox-auth-sig`) for the Schnorr auth signature. - `GenerateClientTLSCert` — Creates an ephemeral P-256 mTLS client cert with the secp256k1 identity pubkey hex as Subject CN. Returns error on nil key. +- `EventRouter` — Registry mapping inbound `ServiceMethod`s to typed actor + dispatch. `AddRoute`/`NewEventRoute` register durable actor-message routes; + `AddEnvelopeRoute` registers raw-envelope handlers (e.g. shared RPC methods + where a stale response is dropped via `ErrEnvelopeHandled` instead of + delivered). +- `MailboxTLSBindDigest`/`Message`, `SignMailboxTLSBind`/`VerifyMailboxTLSBind`, + `TLSBindHeaderKey` — Binds the ephemeral mTLS leaf cert's SPKI to the + secp256k1 identity via a BIP-340 Schnorr signature, complementing + `GenerateClientTLSCert` (the cert alone proves nothing; this signature + proves the TLS key and the identity key are held by the same party). +- `NewAuthenticatedMailboxClient` — `mailboxpb.MailboxServiceClient` decorator + that signs and attaches the `x-mailbox-auth-sig` header to every `Send` + before forwarding to the wrapped edge transport. - `AckState` — Four-cursor watermark state machine (PullCursor, DispatchCommittedTo, AckTarget, AckCommittedTo). - `SendUnaryRequest` — Durable typed unary request that becomes a real unary RPC after commit. The response arrives via KIND_RESPONSE and, if no in-memory waiter exists, falls back to durable route dispatch via the EventRouter. - `DurableUnaryRequestBuilder` — Interface for proof-gated request-body construction. Implementations build the actual proto request (e.g., with signed proofs) at send time, not at persist time. The interface is provided via `ConnectorConfig.DurableUnaryBuilder`. @@ -34,8 +48,10 @@ background ingress polling with event routing. ## Relationships -- **Depends on**: `baselib/actor` (DurableActor infrastructure), `mailbox/*` (Envelope, RpcMeta, MailboxServiceClient). -- **Depended on by**: `round` (outbound RPCs), `oor` (durable transport), `darepod` (wiring). +- **Depends on**: `baselib/actor` (DurableActor infrastructure), `mailbox/*` (Envelope, RpcMeta, MailboxServiceClient), `arkrpc` (`GetInfo` request/response + `ArkVersionPolicy` for version negotiation). +- **Depended on by**: `round` (outbound RPCs), `oor` (durable transport), + `darepod` (wiring), `sdk/swaps` (`CompoundMailboxID`, `PubKeyMailboxID`), + `swapclientserver`. - **Sends (egress → remote mailbox)**: - `SendClientEventRequest` (durable): wraps `JoinRoundRequest`, `JoinRoundAccept`, `JoinRoundReject`, `SubmitNoncesRequest`, `SubmitPartialSigRequest`, `SubmitForfeitSigRequest`. `JoinRoundAccept` / `JoinRoundReject` are the explicit responses to a server-issued seal-time `JoinRoundQuote` (#270); both echo the `quote_id` so the server can drop stale responses after a reseal. - `SendRPCRequest` (unary, non-durable): low-latency request-response RPCs @@ -63,10 +79,18 @@ background ingress polling with event routing. per-key FIFO lane key regardless of whether the message was constructed fresh or decoded from TLV. The `cachedCorrelationKey` field is populated during `Decode` via `tlv.TlvType8` so restarts do not lose FIFO routing. +- Every outbound envelope is stamped with the runtime-bound mailbox + transport and Ark protocol version pair (`stampEnvelopeVersions`/ + `versionStampingMailboxClient`), overwriting any caller-provided value. + Every inbound envelope is checked against the same bound pair + (`validateInboundEnvelope`); a mismatch is always a permanent + `*mailboxconn.StatusError` — there is no legacy zero-version fallback, + since client and operator are always deployed with a negotiated version. ## Deep Docs - [serverconn/README.md](README.md) — Architecture, usage guide, crash recovery paths. - [docs/mailbox_architecture.md](../docs/mailbox_architecture.md) — Three-layer mailbox system. +- [docs/mailbox_transport_serverconn_clientconn.md](../docs/mailbox_transport_serverconn_clientconn.md) — Transport split between serverconn (client-side) and clientconn (server-side). - [docs/durable_actor_architecture.md](../docs/durable_actor_architecture.md) — Durable actor internals. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/serverconn/CLAUDE.md b/serverconn/CLAUDE.md index 4a53b12b3..c976c55f6 100644 --- a/serverconn/CLAUDE.md +++ b/serverconn/CLAUDE.md @@ -18,6 +18,19 @@ background ingress polling with event routing. - `SignMailboxAuth` / `VerifyMailboxAuth` / `ParseMailboxPubKey` — Schnorr sign/verify helpers for pubkey-derived mailbox identity. - `AuthHeaderKey` — Envelope header key (`x-mailbox-auth-sig`) for the Schnorr auth signature. - `GenerateClientTLSCert` — Creates an ephemeral P-256 mTLS client cert with the secp256k1 identity pubkey hex as Subject CN. Returns error on nil key. +- `EventRouter` — Registry mapping inbound `ServiceMethod`s to typed actor + dispatch. `AddRoute`/`NewEventRoute` register durable actor-message routes; + `AddEnvelopeRoute` registers raw-envelope handlers (e.g. shared RPC methods + where a stale response is dropped via `ErrEnvelopeHandled` instead of + delivered). +- `MailboxTLSBindDigest`/`Message`, `SignMailboxTLSBind`/`VerifyMailboxTLSBind`, + `TLSBindHeaderKey` — Binds the ephemeral mTLS leaf cert's SPKI to the + secp256k1 identity via a BIP-340 Schnorr signature, complementing + `GenerateClientTLSCert` (the cert alone proves nothing; this signature + proves the TLS key and the identity key are held by the same party). +- `NewAuthenticatedMailboxClient` — `mailboxpb.MailboxServiceClient` decorator + that signs and attaches the `x-mailbox-auth-sig` header to every `Send` + before forwarding to the wrapped edge transport. - `AckState` — Four-cursor watermark state machine (PullCursor, DispatchCommittedTo, AckTarget, AckCommittedTo). - `SendUnaryRequest` — Durable typed unary request that becomes a real unary RPC after commit. The response arrives via KIND_RESPONSE and, if no in-memory waiter exists, falls back to durable route dispatch via the EventRouter. - `DurableUnaryRequestBuilder` — Interface for proof-gated request-body construction. Implementations build the actual proto request (e.g., with signed proofs) at send time, not at persist time. The interface is provided via `ConnectorConfig.DurableUnaryBuilder`. @@ -36,7 +49,9 @@ background ingress polling with event routing. ## Relationships - **Depends on**: `baselib/actor` (DurableActor infrastructure), `mailbox/*` (Envelope, RpcMeta, MailboxServiceClient), `arkrpc` (`GetInfo` request/response + `ArkVersionPolicy` for version negotiation). -- **Depended on by**: `round` (outbound RPCs), `oor` (durable transport), `darepod` (wiring). +- **Depended on by**: `round` (outbound RPCs), `oor` (durable transport), + `darepod` (wiring), `sdk/swaps` (`CompoundMailboxID`, `PubKeyMailboxID`), + `swapclientserver`. - **Sends (egress → remote mailbox)**: - `SendClientEventRequest` (durable): wraps `JoinRoundRequest`, `JoinRoundAccept`, `JoinRoundReject`, `SubmitNoncesRequest`, `SubmitPartialSigRequest`, `SubmitForfeitSigRequest`. `JoinRoundAccept` / `JoinRoundReject` are the explicit responses to a server-issued seal-time `JoinRoundQuote` (#270); both echo the `quote_id` so the server can drop stale responses after a reseal. - `SendRPCRequest` (unary, non-durable): low-latency request-response RPCs @@ -64,10 +79,18 @@ background ingress polling with event routing. per-key FIFO lane key regardless of whether the message was constructed fresh or decoded from TLV. The `cachedCorrelationKey` field is populated during `Decode` via `tlv.TlvType8` so restarts do not lose FIFO routing. +- Every outbound envelope is stamped with the runtime-bound mailbox + transport and Ark protocol version pair (`stampEnvelopeVersions`/ + `versionStampingMailboxClient`), overwriting any caller-provided value. + Every inbound envelope is checked against the same bound pair + (`validateInboundEnvelope`); a mismatch is always a permanent + `*mailboxconn.StatusError` — there is no legacy zero-version fallback, + since client and operator are always deployed with a negotiated version. ## Deep Docs - [serverconn/README.md](README.md) — Architecture, usage guide, crash recovery paths. - [docs/mailbox_architecture.md](../docs/mailbox_architecture.md) — Three-layer mailbox system. +- [docs/mailbox_transport_serverconn_clientconn.md](../docs/mailbox_transport_serverconn_clientconn.md) — Transport split between serverconn (client-side) and clientconn (server-side). - [docs/durable_actor_architecture.md](../docs/durable_actor_architecture.md) — Durable actor internals. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/swapclientserver/AGENTS.md b/swapclientserver/AGENTS.md index 7c2f1f378..9082c5449 100644 --- a/swapclientserver/AGENTS.md +++ b/swapclientserver/AGENTS.md @@ -16,10 +16,13 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. not per-RPC), the process-local `active` worker map, and the `subscribers` map for `SubscribeSwaps` streaming. - `swapRuntimeClient` — Narrow interface over `sdk/swaps.SwapClient` that the - subserver uses for all RPC handlers and worker restarts. Methods: `StartPayViaLightning`, - `StartReceiveViaLightning`, `ResumePayViaLightning`, - `ResumeReceiveViaLightning`, `GetSwapSummary`, `ListSwapSummaries`. Keeps - the subserver unit-testable without running real swap FSMs. + subserver uses for all RPC handlers and worker restarts. Methods: + `QuotePayViaLightning`, `StartPayViaLightning`, `StartReceiveViaLightning`, + `ResumePayViaLightning`, `ResumeReceiveViaLightning`, `GetSwapSummary`, + `ListSwapSummaries`. Credit operations (`CreateCredit`, `RedeemCredit`, + `ListCredits`) are optional and reached via runtime type assertion, not + part of the interface. Keeps the subserver unit-testable without running + real swap FSMs. - `swapClientAdapter` — Thin production adapter that forwards calls to `*swaps.SwapClient`. - `paySwapSession` / `receiveSwapSession` — Minimal session interfaces @@ -29,6 +32,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. - `receiveSessionAdapter` — Adds method accessors over `sdk/swaps.ReceiveSession` so both production code and tests share the same interface without exposing struct fields. +- `creditServerBridge` / `creditDaemonBridge` — Adapt the subserver and the + in-process Ark/daemon facade to the `credit` package's `CreditServer` / + `CreditDaemon` interfaces, so the credit durable-actor subsystem reuses this + package's account-key resolution, payment-hash dedup, and worker registry + instead of duplicating them. - `Register(ctx, grpcServer, rpcServer, cfg)` — Top-level entry point called by a `swapruntime`-tagged `darepod` binary. Opens the daemon-owned SQLite swap store, dials `swapdk-server`, creates an in-process Ark SDK facade over @@ -36,15 +44,20 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. `MailboxOutSwapEventReceiver` (empty mailbox ID — receiver derives the per-swap mailbox from client identity + payment hash) on the `SwapClient` so out-swap HTLC events flow over the mailbox transport, - registers the gRPC subserver, calls `resumePending`, and returns a cleanup - function. + publishes `cfg.Swap.Backend`/`CreditServer`/`CreditDaemon` bridges, + registers the gRPC subserver, calls `resumePending` (unless + `cfg.Swap.SuppressResume`), and returns a cleanup function. ## RPC Methods | RPC | Description | |-----|-------------| +| `QuotePay` | Preview a pay swap without creating durable state or a worker | | `StartPay` | Persist a pay swap, start or reuse its daemon worker, return summary | | `StartReceive` | Persist a receive swap, start or reuse its daemon worker, return invoice + summary | +| `CreateCredit` | Start a durable server-owned credit funding operation | +| `RedeemCredit` | Materialize available credits back into an Ark output | +| `ListCredits` | Return the server-authoritative credit account snapshot | | `ResumeSwap` | Manual wake-up for a persisted swap (idempotent if worker already active) | | `ListSwaps` | List persisted swap summaries; optionally filter to pending only | | `GetSwap` | Fetch one persisted summary by hex payment hash | @@ -52,10 +65,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. ## Relationships -- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session - types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade), `darepod` - (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `rpc/swapclientrpc` - (generated gRPC stubs + proto types). +- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session, + credit types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade), + `darepod` (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `credit` + (`CreditServer`/`CreditDaemon` interfaces bridged for the credit actor + subsystem), `rpc/swapclientrpc` (generated gRPC stubs + proto types). - **Depended on by**: `cmd/darepod` (calls `swapclientserver.Register` when built with the `swapruntime` tag), `cmd/darepocli/darepoclicommands` (swap RPC CLI commands under `swapruntime`). @@ -63,8 +77,9 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. `ResumePayViaLightning` / `ResumeReceiveViaLightning` — CLI disconnect does not cancel the worker because the subserver uses `rootCtx`, not the RPC context. -- **Receives**: ← API: `StartPay`, `StartReceive`, `ResumeSwap`, `ListSwaps`, - `GetSwap`, `SubscribeSwaps` from gRPC callers. +- **Receives**: ← API: `QuotePay`, `StartPay`, `StartReceive`, `CreateCredit`, + `RedeemCredit`, `ListCredits`, `ResumeSwap`, `ListSwaps`, `GetSwap`, + `SubscribeSwaps` from gRPC callers. ## Invariants @@ -77,15 +92,20 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. - `SubscribeSwaps` subscribers are best-effort, buffered (16), and non-blocking. Slow subscribers may miss a terminal-state update; they can recover current state with `GetSwap` or `ListSwaps`. -- `Register` calls `resumePending` synchronously before returning so the - daemon gRPC server begins accepting calls with all prior sessions already - driven by a worker. +- `Register` calls `resumePending` synchronously before returning (unless + `cfg.Swap.SuppressResume`, in which case a higher subserver drives + `ResumePending` itself) so the daemon gRPC server begins accepting calls + with all prior sessions already driven by a worker. - Swap state, persistence, and protocol behavior are never duplicated in this layer — they stay in `sdk/swaps`. This package is a worker registry and RPC facade only. - `idempotency_key` on `StartPay` / `StartReceive` is explicitly reserved and returns `Unimplemented` to guard against accidental duplicate-start - assumptions. + assumptions; by contrast `CreateCredit` / `RedeemCredit` require a + caller-supplied `idempotency_key` today. +- `CreateCredit`/`RedeemCredit`/`ListCredits` type-assert `s.client` for the + optional credit methods and return `Unimplemented` if the underlying + `swapRuntimeClient` does not support credits. - `SetOutSwapEventReceiver` must run before any receive worker is started: `SwapClient` captures the receiver into the per-swap worker at start time, so a late install would leave already-running workers using whatever diff --git a/swapclientserver/CLAUDE.md b/swapclientserver/CLAUDE.md index 7c2f1f378..9082c5449 100644 --- a/swapclientserver/CLAUDE.md +++ b/swapclientserver/CLAUDE.md @@ -16,10 +16,13 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. not per-RPC), the process-local `active` worker map, and the `subscribers` map for `SubscribeSwaps` streaming. - `swapRuntimeClient` — Narrow interface over `sdk/swaps.SwapClient` that the - subserver uses for all RPC handlers and worker restarts. Methods: `StartPayViaLightning`, - `StartReceiveViaLightning`, `ResumePayViaLightning`, - `ResumeReceiveViaLightning`, `GetSwapSummary`, `ListSwapSummaries`. Keeps - the subserver unit-testable without running real swap FSMs. + subserver uses for all RPC handlers and worker restarts. Methods: + `QuotePayViaLightning`, `StartPayViaLightning`, `StartReceiveViaLightning`, + `ResumePayViaLightning`, `ResumeReceiveViaLightning`, `GetSwapSummary`, + `ListSwapSummaries`. Credit operations (`CreateCredit`, `RedeemCredit`, + `ListCredits`) are optional and reached via runtime type assertion, not + part of the interface. Keeps the subserver unit-testable without running + real swap FSMs. - `swapClientAdapter` — Thin production adapter that forwards calls to `*swaps.SwapClient`. - `paySwapSession` / `receiveSwapSession` — Minimal session interfaces @@ -29,6 +32,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. - `receiveSessionAdapter` — Adds method accessors over `sdk/swaps.ReceiveSession` so both production code and tests share the same interface without exposing struct fields. +- `creditServerBridge` / `creditDaemonBridge` — Adapt the subserver and the + in-process Ark/daemon facade to the `credit` package's `CreditServer` / + `CreditDaemon` interfaces, so the credit durable-actor subsystem reuses this + package's account-key resolution, payment-hash dedup, and worker registry + instead of duplicating them. - `Register(ctx, grpcServer, rpcServer, cfg)` — Top-level entry point called by a `swapruntime`-tagged `darepod` binary. Opens the daemon-owned SQLite swap store, dials `swapdk-server`, creates an in-process Ark SDK facade over @@ -36,15 +44,20 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. `MailboxOutSwapEventReceiver` (empty mailbox ID — receiver derives the per-swap mailbox from client identity + payment hash) on the `SwapClient` so out-swap HTLC events flow over the mailbox transport, - registers the gRPC subserver, calls `resumePending`, and returns a cleanup - function. + publishes `cfg.Swap.Backend`/`CreditServer`/`CreditDaemon` bridges, + registers the gRPC subserver, calls `resumePending` (unless + `cfg.Swap.SuppressResume`), and returns a cleanup function. ## RPC Methods | RPC | Description | |-----|-------------| +| `QuotePay` | Preview a pay swap without creating durable state or a worker | | `StartPay` | Persist a pay swap, start or reuse its daemon worker, return summary | | `StartReceive` | Persist a receive swap, start or reuse its daemon worker, return invoice + summary | +| `CreateCredit` | Start a durable server-owned credit funding operation | +| `RedeemCredit` | Materialize available credits back into an Ark output | +| `ListCredits` | Return the server-authoritative credit account snapshot | | `ResumeSwap` | Manual wake-up for a persisted swap (idempotent if worker already active) | | `ListSwaps` | List persisted swap summaries; optionally filter to pending only | | `GetSwap` | Fetch one persisted summary by hex payment hash | @@ -52,10 +65,11 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. ## Relationships -- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session - types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade), `darepod` - (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `rpc/swapclientrpc` - (generated gRPC stubs + proto types). +- **Depends on**: `sdk/swaps` (swap FSM, `SwapClient`, `Store`, session, + credit types), `sdk/ark` (`WrapDaemonServer`, in-process Ark facade), + `darepod` (`RPCServer`, `Config`, `SwapConfig`, `SwapSubsystem`), `credit` + (`CreditServer`/`CreditDaemon` interfaces bridged for the credit actor + subsystem), `rpc/swapclientrpc` (generated gRPC stubs + proto types). - **Depended on by**: `cmd/darepod` (calls `swapclientserver.Register` when built with the `swapruntime` tag), `cmd/darepocli/darepoclicommands` (swap RPC CLI commands under `swapruntime`). @@ -63,8 +77,9 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. `ResumePayViaLightning` / `ResumeReceiveViaLightning` — CLI disconnect does not cancel the worker because the subserver uses `rootCtx`, not the RPC context. -- **Receives**: ← API: `StartPay`, `StartReceive`, `ResumeSwap`, `ListSwaps`, - `GetSwap`, `SubscribeSwaps` from gRPC callers. +- **Receives**: ← API: `QuotePay`, `StartPay`, `StartReceive`, `CreateCredit`, + `RedeemCredit`, `ListCredits`, `ResumeSwap`, `ListSwaps`, `GetSwap`, + `SubscribeSwaps` from gRPC callers. ## Invariants @@ -77,15 +92,20 @@ protocol behavior remain entirely inside `sdk/swaps` and `swapdk-server`. - `SubscribeSwaps` subscribers are best-effort, buffered (16), and non-blocking. Slow subscribers may miss a terminal-state update; they can recover current state with `GetSwap` or `ListSwaps`. -- `Register` calls `resumePending` synchronously before returning so the - daemon gRPC server begins accepting calls with all prior sessions already - driven by a worker. +- `Register` calls `resumePending` synchronously before returning (unless + `cfg.Swap.SuppressResume`, in which case a higher subserver drives + `ResumePending` itself) so the daemon gRPC server begins accepting calls + with all prior sessions already driven by a worker. - Swap state, persistence, and protocol behavior are never duplicated in this layer — they stay in `sdk/swaps`. This package is a worker registry and RPC facade only. - `idempotency_key` on `StartPay` / `StartReceive` is explicitly reserved and returns `Unimplemented` to guard against accidental duplicate-start - assumptions. + assumptions; by contrast `CreateCredit` / `RedeemCredit` require a + caller-supplied `idempotency_key` today. +- `CreateCredit`/`RedeemCredit`/`ListCredits` type-assert `s.client` for the + optional credit methods and return `Unimplemented` if the underlying + `swapRuntimeClient` does not support credits. - `SetOutSwapEventReceiver` must run before any receive worker is started: `SwapClient` captures the receiver into the per-swap worker at start time, so a late install would leave already-running workers using whatever diff --git a/swapwallet/AGENTS.md b/swapwallet/AGENTS.md index 81ca50136..0edef20b5 100644 --- a/swapwallet/AGENTS.md +++ b/swapwallet/AGENTS.md @@ -5,9 +5,12 @@ Daemon-side implementation of the `walletdkrpc.WalletService` gRPC subserver. It composes the swap subsystem (`swapclientserver`), the cooperative-leave RPC, the daemon's wallet/admin surface, the boarding -ledger, and the unilateral-exit registry into one flat user-facing API: -the seven wallet verbs (Create, Unlock, Send, Recv, List, Balance, -Exit) plus the supporting Deposit / Status / SubscribeWallet methods. +ledger, the `credit` durable-actor subsystem, and the unilateral-exit +registry into one flat user-facing API: the seven wallet verbs (Create, +Unlock, Send, Recv, List, Balance, Exit) plus the supporting Deposit / +Status / SubscribeWallet methods. Sends/receives that are sub-dust or +otherwise credit-eligible are routed through the credit registry +transparently — the caller only sees wallet vocabulary. The whole package lives behind `//go:build walletdkrpc && swapruntime` so default builds avoid the swap executor's dependency graph. @@ -24,17 +27,21 @@ default builds avoid the swap executor's dependency graph. never cancel in-flight work. - `Deps` — Composition struct: `SwapBackend` (in-Go swap runtime), `SwapService` (gRPC-shaped swap subserver handle), `RPCServer` - (narrow daemonrpc contract), `ChainParams` (Bitcoin network — used to + (narrow daemonrpc contract), `CreditRegistry` (lazy `actor.ActorRef` into + the `credit` durable-actor subsystem; nil disables credit-backed routing), + `ChainParams` (Bitcoin network — used to validate BOLT-11 invoice decoding in `PrepareSend` so a cross-network - invoice is rejected before a send intent is issued), plus wallet-level + invoice is rejected before a send intent is issued), `ActivityStore` + (canonical activity-log projector), plus wallet-level deadline, list-limit, and subscribe-buffer knobs. - `RPCServer` interface — Narrow contract over `*darepod.RPCServer` - covering every daemonrpc method swapwallet composes against: - LeaveVTXOs, SendOnChain, ListVTXOs, ListTransactions, NewAddress, GetInfo, - GetBalance, GenSeed, InitWallet, UnlockWallet, Unroll, - GetUnrollStatus, JoinNextRound. The admin-shape methods (GenSeed/InitWallet/ - UnlockWallet/Unroll/GetUnrollStatus) are reachable BEFORE the swap - runtime is live. + covering every daemonrpc/darepod method swapwallet composes against: + LeaveVTXOs, SendOnChain, SendOOR, ListVTXOs, ListTransactions, GetInfo, + EstimateFee, GetBalance, NewAddress, NewWalletAddress, ListWalletUnspent, + GenSeed, InitWallet, UnlockWallet, Unroll, GetUnrollStatus, ExitSummary, + GetExitPlan, SweepWallet, JoinNextRound. The admin-shape methods + (GenSeed/InitWallet/UnlockWallet/Unroll/GetUnrollStatus) are reachable + BEFORE the swap runtime is live. - `WalletEntry` (re-exported from walletdkrpc) — Flat row type the entire history/streaming surface returns. Every internal correlator (session_id, round_id, settlement_type, mailbox subtype) is dropped @@ -48,12 +55,15 @@ default builds avoid the swap executor's dependency graph. - **Depends on**: - `rpc/walletdkrpc` (generated gRPC stubs and request/response shapes) - `daemonrpc` (admin RPCs proxied by Create/Unlock/Exit and the - backends consumed for LeaveVTXOs, ListVTXOs, ListTransactions, + backends consumed for LeaveVTXOs, SendOOR, ListVTXOs, ListTransactions, GetBalance, NewAddress) - `rpc/swapclientrpc` (swap-subsystem gRPC shape; ListSwaps, StartPay, StartReceive) - `swapclientserver` (typed `Backend` handle and runtime resume) - - `darepod` (`SwapBackend` interface) + - `credit` (`CreditMsg`/`CreditResp`, `StartCreditPayRequest`, + `ListCreditOpsRequest` — durable credit-backed pay/recv routing and the + credit projector poll) + - `darepod` (`SwapBackend`, `ActivityStore` interfaces) - `ledger` (account name constants for OOR ledger projection) - `btclog/v2` (subsystem logger) - **Depended on by**: @@ -64,14 +74,20 @@ default builds avoid the swap executor's dependency graph. - → daemonrpc (in-process via RPCServer): `InitWalletRequest`, `UnlockWalletRequest`, `GenSeedRequest`, `UnrollRequest`, `GetUnrollStatusRequest`, `LeaveVTXOsRequest`, - `SendOnChainRequest`, `JoinNextRoundRequest`, `ListVTXOsRequest`, - `ListTransactionsRequest`, `NewAddressRequest`, `GetBalanceRequest`, - `GetInfoRequest` + `SendOnChainRequest`, `SendOORRequest`, `JoinNextRoundRequest`, + `ListVTXOsRequest`, `ListTransactionsRequest`, `NewAddressRequest`, + `GetBalanceRequest`, `GetInfoRequest` - → swapclientrpc (in-process via SwapService): `StartPayRequest`, `StartReceiveRequest`, `ListSwapsRequest`, `SubscribeSwapsRequest` - **Receives**: - - ← API: `walletdkrpc.{Create,Unlock,Send,Recv,List,Balance,Deposit, - Status,Exit,ExitStatus,SubscribeWallet}Request` + - ← API: `walletdkrpc.{Create,Unlock,PrepareSend,Send,Recv,List,Balance, + Deposit,Status,GetExitPlan,SweepWallet,Exit,ExitStatus,ExitSummary, + SubscribeWallet,InspectActivity}Request` +- **Messages to/from**: Sends `credit.StartCreditPayRequest` / + `credit.ListCreditOpsRequest` -> `credit` registry actor (via + `Deps.CreditRegistry.Ask`); the credit projector loop polls + `ListCreditOpsResponse` <- `credit` to fan terminal credit-op state onto + `WalletEntry` rows. ## Invariants @@ -79,13 +95,32 @@ default builds avoid the swap executor's dependency graph. admin-shape: they reach daemonrpc via the injected `RPCServer` and DO NOT depend on `Runtime`, router, recv, or history. Create and Unlock must work before the swap subsystem is live. +- Credit-backed routing is nil-safe: `Deps.CreditRegistry == nil` disables + credit-only sends (falls back with `ErrSwapBackendUnavailable`) and the + credit projector loop is a no-op, so builds without the credit subsystem + wired pay nothing extra. +- A pay is **credit-only** (owned solely by the credit projector) when the + server pins it to credit or `creditCoversSat` (overflow-safe) shows applied + credit + planned top-up covers the full principal; otherwise it is + **mixed** and the swap monitor loop stays the single terminal authority for + the shared payment-hash row — the credit projector must never emit for a + mixed pay. +- The credit projector polls on a coarse 5s tick + (`creditProjectInterval`) and only re-emits an operation when its + `credit.State` changed since the last poll, keyed by `OpID` in an + in-process (non-durable) map that starts empty on restart. - Background goroutines (monitor loop, deadline watcher, resume sweep) are anchored to the daemon root context, NEVER to RPC-call contexts. An RPC client disconnect cannot cancel in-flight work. -- `WalletEntry.id` is the stable canonical id for SEND-invoice and - RECV (Lightning payment_hash) across the entire pending → terminal - lifecycle. EXIT and DEPOSIT rows do not yet share an id between - pending and confirmed in v1; see `doc.go`. +- `WalletEntry.id` is the stable canonical id across the entire pending + → terminal lifecycle for SEND-invoice and RECV (Lightning + payment_hash), on-chain-send / cooperative-leave EXIT (the daemon's + leave-job id / `send_job_id`), and DEPOSIT (`deposit-
`, keyed + on the allocated boarding address surfaced on the confirmed history + row). A unilateral EXIT still keys by the consumed VTXO outpoint. The + pending → COMPLETE transition for EXIT/DEPOSIT lands via the + derive/backfill pass; live cross-restart reconciliation is C2. See + `doc.go`. - Onchain SEND is routed through `RPCServer.SendOnChain` which delegates to `wallet.SendOnChainRequest`. Two modes: **sweep-all** (non-empty `SweepOutpoints` — drains those VTXOs exactly, no change, leave output @@ -109,10 +144,17 @@ default builds avoid the swap executor's dependency graph. stuck row appears as FAILED even when the caller asks for `pending_only=false`. - **DEPOSIT rows backed by the `wallet_utxo_created` ledger event** - mirror the ledger confirmation status. Confirmed on-chain boarding - deposits surface as `ENTRY_STATUS_COMPLETE`, while unconfirmed - boarding funds are represented by the synthetic - `boarding-unconfirmed` pending row from `GetBalance`. + mirror the ledger confirmation status and are keyed + `deposit-
` from the confirmed row's + `TransactionHistoryEntry.boarding_address`; every UTXO paid to that + address is SUMMED into one row (`sumDepositsByAddress`) so a reused + address shows its total. `Deposit` does NOT project a row — allocating + an address is not a pending deposit — it only returns that id so a + caller can correlate the eventual confirmed row. Per-address is the + CONFIRMED phase only: unconfirmed boarding funds have no per-address + source (the daemon exposes only aggregate `boarding_unconfirmed_sat`), + so they surface via `Balance` and the single derive-path-only + `boarding-unconfirmed` row, never projected into the store. - **`Balance` projection** maps daemonrpc fields onto the walletdkrpc shape: `confirmed_sat` is VTXO-only (`vtxo_balance_sat`), `pending_in_sat` sums `boarding_confirmed_sat + diff --git a/swapwallet/CLAUDE.md b/swapwallet/CLAUDE.md index 735c0a003..0edef20b5 100644 --- a/swapwallet/CLAUDE.md +++ b/swapwallet/CLAUDE.md @@ -5,9 +5,12 @@ Daemon-side implementation of the `walletdkrpc.WalletService` gRPC subserver. It composes the swap subsystem (`swapclientserver`), the cooperative-leave RPC, the daemon's wallet/admin surface, the boarding -ledger, and the unilateral-exit registry into one flat user-facing API: -the seven wallet verbs (Create, Unlock, Send, Recv, List, Balance, -Exit) plus the supporting Deposit / Status / SubscribeWallet methods. +ledger, the `credit` durable-actor subsystem, and the unilateral-exit +registry into one flat user-facing API: the seven wallet verbs (Create, +Unlock, Send, Recv, List, Balance, Exit) plus the supporting Deposit / +Status / SubscribeWallet methods. Sends/receives that are sub-dust or +otherwise credit-eligible are routed through the credit registry +transparently — the caller only sees wallet vocabulary. The whole package lives behind `//go:build walletdkrpc && swapruntime` so default builds avoid the swap executor's dependency graph. @@ -24,17 +27,21 @@ default builds avoid the swap executor's dependency graph. never cancel in-flight work. - `Deps` — Composition struct: `SwapBackend` (in-Go swap runtime), `SwapService` (gRPC-shaped swap subserver handle), `RPCServer` - (narrow daemonrpc contract), `ChainParams` (Bitcoin network — used to + (narrow daemonrpc contract), `CreditRegistry` (lazy `actor.ActorRef` into + the `credit` durable-actor subsystem; nil disables credit-backed routing), + `ChainParams` (Bitcoin network — used to validate BOLT-11 invoice decoding in `PrepareSend` so a cross-network - invoice is rejected before a send intent is issued), plus wallet-level + invoice is rejected before a send intent is issued), `ActivityStore` + (canonical activity-log projector), plus wallet-level deadline, list-limit, and subscribe-buffer knobs. - `RPCServer` interface — Narrow contract over `*darepod.RPCServer` - covering every daemonrpc method swapwallet composes against: - LeaveVTXOs, SendOnChain, ListVTXOs, ListTransactions, NewAddress, GetInfo, - GetBalance, GenSeed, InitWallet, UnlockWallet, Unroll, - GetUnrollStatus, JoinNextRound. The admin-shape methods (GenSeed/InitWallet/ - UnlockWallet/Unroll/GetUnrollStatus) are reachable BEFORE the swap - runtime is live. + covering every daemonrpc/darepod method swapwallet composes against: + LeaveVTXOs, SendOnChain, SendOOR, ListVTXOs, ListTransactions, GetInfo, + EstimateFee, GetBalance, NewAddress, NewWalletAddress, ListWalletUnspent, + GenSeed, InitWallet, UnlockWallet, Unroll, GetUnrollStatus, ExitSummary, + GetExitPlan, SweepWallet, JoinNextRound. The admin-shape methods + (GenSeed/InitWallet/UnlockWallet/Unroll/GetUnrollStatus) are reachable + BEFORE the swap runtime is live. - `WalletEntry` (re-exported from walletdkrpc) — Flat row type the entire history/streaming surface returns. Every internal correlator (session_id, round_id, settlement_type, mailbox subtype) is dropped @@ -48,12 +55,15 @@ default builds avoid the swap executor's dependency graph. - **Depends on**: - `rpc/walletdkrpc` (generated gRPC stubs and request/response shapes) - `daemonrpc` (admin RPCs proxied by Create/Unlock/Exit and the - backends consumed for LeaveVTXOs, ListVTXOs, ListTransactions, + backends consumed for LeaveVTXOs, SendOOR, ListVTXOs, ListTransactions, GetBalance, NewAddress) - `rpc/swapclientrpc` (swap-subsystem gRPC shape; ListSwaps, StartPay, StartReceive) - `swapclientserver` (typed `Backend` handle and runtime resume) - - `darepod` (`SwapBackend` interface) + - `credit` (`CreditMsg`/`CreditResp`, `StartCreditPayRequest`, + `ListCreditOpsRequest` — durable credit-backed pay/recv routing and the + credit projector poll) + - `darepod` (`SwapBackend`, `ActivityStore` interfaces) - `ledger` (account name constants for OOR ledger projection) - `btclog/v2` (subsystem logger) - **Depended on by**: @@ -64,14 +74,20 @@ default builds avoid the swap executor's dependency graph. - → daemonrpc (in-process via RPCServer): `InitWalletRequest`, `UnlockWalletRequest`, `GenSeedRequest`, `UnrollRequest`, `GetUnrollStatusRequest`, `LeaveVTXOsRequest`, - `SendOnChainRequest`, `JoinNextRoundRequest`, `ListVTXOsRequest`, - `ListTransactionsRequest`, `NewAddressRequest`, `GetBalanceRequest`, - `GetInfoRequest` + `SendOnChainRequest`, `SendOORRequest`, `JoinNextRoundRequest`, + `ListVTXOsRequest`, `ListTransactionsRequest`, `NewAddressRequest`, + `GetBalanceRequest`, `GetInfoRequest` - → swapclientrpc (in-process via SwapService): `StartPayRequest`, `StartReceiveRequest`, `ListSwapsRequest`, `SubscribeSwapsRequest` - **Receives**: - - ← API: `walletdkrpc.{Create,Unlock,Send,Recv,List,Balance,Deposit, - Status,Exit,ExitStatus,SubscribeWallet}Request` + - ← API: `walletdkrpc.{Create,Unlock,PrepareSend,Send,Recv,List,Balance, + Deposit,Status,GetExitPlan,SweepWallet,Exit,ExitStatus,ExitSummary, + SubscribeWallet,InspectActivity}Request` +- **Messages to/from**: Sends `credit.StartCreditPayRequest` / + `credit.ListCreditOpsRequest` -> `credit` registry actor (via + `Deps.CreditRegistry.Ask`); the credit projector loop polls + `ListCreditOpsResponse` <- `credit` to fan terminal credit-op state onto + `WalletEntry` rows. ## Invariants @@ -79,6 +95,20 @@ default builds avoid the swap executor's dependency graph. admin-shape: they reach daemonrpc via the injected `RPCServer` and DO NOT depend on `Runtime`, router, recv, or history. Create and Unlock must work before the swap subsystem is live. +- Credit-backed routing is nil-safe: `Deps.CreditRegistry == nil` disables + credit-only sends (falls back with `ErrSwapBackendUnavailable`) and the + credit projector loop is a no-op, so builds without the credit subsystem + wired pay nothing extra. +- A pay is **credit-only** (owned solely by the credit projector) when the + server pins it to credit or `creditCoversSat` (overflow-safe) shows applied + credit + planned top-up covers the full principal; otherwise it is + **mixed** and the swap monitor loop stays the single terminal authority for + the shared payment-hash row — the credit projector must never emit for a + mixed pay. +- The credit projector polls on a coarse 5s tick + (`creditProjectInterval`) and only re-emits an operation when its + `credit.State` changed since the last poll, keyed by `OpID` in an + in-process (non-durable) map that starts empty on restart. - Background goroutines (monitor loop, deadline watcher, resume sweep) are anchored to the daemon root context, NEVER to RPC-call contexts. An RPC client disconnect cannot cancel in-flight work. diff --git a/systest/AGENTS.md b/systest/AGENTS.md index 3ceff2bb9..f66f45118 100644 --- a/systest/AGENTS.md +++ b/systest/AGENTS.md @@ -2,10 +2,47 @@ ## Purpose -System-level end-to-end integration tests exercising the full daemon with real -Bitcoin/LND backends via the test harness. +System-level end-to-end tests, gated by the `systest` build tag, that +exercise real components against Docker-backed Bitcoin/LND infrastructure. +Some tests drive only the boarding-wallet actor via `SysTestHarness`; others +(`send_vtxo_test.go`, `leave_strand_test.go`, `refresh_strand_test.go`) stand +up a full in-process `darepod` daemon plus round/serverconn/mailbox pieces +for round and OOR-send scenarios. + +## Key Types + +- `SysTestHarness` — Per-test wrapper around `harness.Harness` (Docker + bitcoind + lnd) plus a per-test `actor.ActorSystem`, in-memory SQLite + `db.BoardingWalletStore`, and subsystem loggers. `NewSysTestHarness` + isolates every test's Docker infra, actor system, and database. +- `BoardingWalletFixture` — Higher-level fixture built on + `SysTestHarness`: wires a chain source actor, `wallet.BoardingBackend`, and + a running `wallet.Ark` actor, and exposes helpers + (`CreateBoardingAddress`, `FundAddress`, `WaitForBalance`, + `RegisterNotifier`, `AssertAddressStored`, `AssertIntentStored`) so + boarding tests skip setup boilerplate. +- `ParallelN(t)` / `TestMain` — Caps concurrent systest execution via a + semaphore sized by the `-test.parallelism` flag (default 4), since each + test's Docker harness is resource-heavy. ## Relationships -- **Depends on**: `harness` (test environment), `darepod` (daemon under test). -- **Depended on by**: nothing (test-only). +- **Depends on**: `harness` (Docker bitcoind/lnd test environment), `wallet` + (boarding wallet actor under test), `chainsource`/`chainbackends`/ + `lndbackend` (chain backend wiring), `darepod` (full in-process daemon for + round/send-VTXO tests), `db` (test-scoped SQLite stores). +- **Depended on by**: nothing (test-only, `systest`-tagged). + +## Invariants + +- All files require the `systest` build tag; nothing here compiles into + default builds. +- Background goroutines and actor systems are per-test, not shared: each + `SysTestHarness`/`BoardingWalletFixture` cleans itself up via `t.Cleanup`, + so tests must not share a harness across `t.Parallel()` subtests. +- Tests that need to run concurrently must call `ParallelN(t)` (not raw + `t.Parallel()`) so the Docker-resource semaphore is respected. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/systest/CLAUDE.md b/systest/CLAUDE.md index 3ceff2bb9..f66f45118 100644 --- a/systest/CLAUDE.md +++ b/systest/CLAUDE.md @@ -2,10 +2,47 @@ ## Purpose -System-level end-to-end integration tests exercising the full daemon with real -Bitcoin/LND backends via the test harness. +System-level end-to-end tests, gated by the `systest` build tag, that +exercise real components against Docker-backed Bitcoin/LND infrastructure. +Some tests drive only the boarding-wallet actor via `SysTestHarness`; others +(`send_vtxo_test.go`, `leave_strand_test.go`, `refresh_strand_test.go`) stand +up a full in-process `darepod` daemon plus round/serverconn/mailbox pieces +for round and OOR-send scenarios. + +## Key Types + +- `SysTestHarness` — Per-test wrapper around `harness.Harness` (Docker + bitcoind + lnd) plus a per-test `actor.ActorSystem`, in-memory SQLite + `db.BoardingWalletStore`, and subsystem loggers. `NewSysTestHarness` + isolates every test's Docker infra, actor system, and database. +- `BoardingWalletFixture` — Higher-level fixture built on + `SysTestHarness`: wires a chain source actor, `wallet.BoardingBackend`, and + a running `wallet.Ark` actor, and exposes helpers + (`CreateBoardingAddress`, `FundAddress`, `WaitForBalance`, + `RegisterNotifier`, `AssertAddressStored`, `AssertIntentStored`) so + boarding tests skip setup boilerplate. +- `ParallelN(t)` / `TestMain` — Caps concurrent systest execution via a + semaphore sized by the `-test.parallelism` flag (default 4), since each + test's Docker harness is resource-heavy. ## Relationships -- **Depends on**: `harness` (test environment), `darepod` (daemon under test). -- **Depended on by**: nothing (test-only). +- **Depends on**: `harness` (Docker bitcoind/lnd test environment), `wallet` + (boarding wallet actor under test), `chainsource`/`chainbackends`/ + `lndbackend` (chain backend wiring), `darepod` (full in-process daemon for + round/send-VTXO tests), `db` (test-scoped SQLite stores). +- **Depended on by**: nothing (test-only, `systest`-tagged). + +## Invariants + +- All files require the `systest` build tag; nothing here compiles into + default builds. +- Background goroutines and actor systems are per-test, not shared: each + `SysTestHarness`/`BoardingWalletFixture` cleans itself up via `t.Cleanup`, + so tests must not share a harness across `t.Parallel()` subtests. +- Tests that need to run concurrently must call `ParallelN(t)` (not raw + `t.Parallel()`) so the Docker-resource semaphore is respected. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. diff --git a/timeout/AGENTS.md b/timeout/AGENTS.md index 6f777ffe0..6ac357bd1 100644 --- a/timeout/AGENTS.md +++ b/timeout/AGENTS.md @@ -2,86 +2,51 @@ ## Purpose -Generic fire-and-forget timeout scheduling actor. Sends `ExpiredMsg` to a -callback when a one-shot timeout fires, and `TickFiredMsg` on each -recurring tick scheduled via `ScheduleRecurringTickRequest`. - -## Architecture - -The actor follows a strict self-tell model: clock callbacks never mutate -actor state directly. Each `Clock.AfterFunc` callback Tells an internal -fire message (`internalTimerFired` / `internalTickFired`) into the -actor's own mailbox via the self-ref attached at `Start`. All state -mutation — adding/removing entries from the `oneshots` and `recurring` -maps, delivering user-facing messages, and re-arming recurring chains — -happens single-threadedly inside `Receive`. There is no internal mutex -and no per-entry forwarder goroutine. - -Recurring ticks are implemented as a chain of one-shot timers that -re-arm from inside `handleTickFired`, which gives "fixed-delay" -semantics (next fire = handler-finish + interval) rather than -`time.Ticker`'s "fixed-rate with drops". Stale fires that race with a -Cancel or reschedule are filtered by a per-entry generation token. - -## Clock Abstraction - -- `Clock` — Interface (`Now() time.Time`, `AfterFunc(d, f) Stoppable`) - that drives all timer creation. Allows deterministic time injection in - tests. -- `Stoppable` — Interface satisfied directly by `*time.Timer`; returned - by `Clock.AfterFunc`. -- `RealClock` — Production implementation backed by the standard library. -- `NewActor()` — Constructor using `RealClock`. -- `NewActorWithClock(clock Clock)` — Test constructor; inject a fake - clock for deterministic timer behavior without wall-clock delays. - -## Transform Helpers - -- `MapTimeoutExpired[Out](targetRef, mapFn)` — Wraps a target ref to - convert `*ExpiredMsg` deliveries into the caller's message type using - `actor.NewMapInputRef`. Eliminates boilerplate adapter types when wiring - one-shot timeouts to domain actors. -- `MapTickFired[Out](targetRef, mapFn)` — Same pattern for `*TickFiredMsg`. - Both helpers are the idiomatic way to wire the timeout actor to a domain - actor. - -## Message Types - -- `ScheduleTimeoutRequest` — Schedule a one-shot timer for `Duration`. - Fires `*ExpiredMsg` to `Target` ref. -- `ScheduleRecurringTickRequest` — Schedule a recurring tick. `Interval` - must be strictly positive (zero/negative is rejected before touching - state). Fires `*TickFiredMsg` on each tick. -- `CancelTimeoutRequest` — Cancel a scheduled timeout or recurring tick by - ID. One-shot and recurring timers share the same ID namespace; either - type can be cancelled with this message. -- `ExpiredMsg` — Delivered to the target ref when a one-shot timer fires. -- `TickFiredMsg` — Delivered on each recurring tick. `FiredAt` carries the - clock-goroutine capture time, not the Receive-processing time; important - for test assertions. - -## Wiring - -Callers must call `Start(ref)` after registering the actor with the -actor system; `ref` is the `actor.ActorRef` (or any -`actor.TellOnlyRef[Msg]`) returned by `RegisterWithSystem` / `Spawn`. -External callers must Tell into the actor through that same ref — -calling `Receive` directly is unsafe under the self-tell model because -clock callbacks expect a serializing mailbox in front of `Receive`. +Generic fire-and-forget timeout scheduling actor. Schedules one-shot +timeouts and recurring ticks, delivering `ExpiredMsg` / +`TickFiredMsg` to a caller-supplied callback ref when they fire. + +## Key Types + +- `Actor` — Holds `oneshots`/`recurring` entry maps; all state mutation + happens single-threadedly inside `Receive`. Clock callbacks never + touch actor state directly — they self-Tell an internal fire message + (`internalTimerFired`/`internalTickFired`) carrying a generation + token, so stale fires racing a Cancel/reschedule are dropped. +- `Clock` — Interface (`Now`, `AfterFunc`) abstracting the time source; + `RealClock` is the production impl, tests inject a fake via + `NewActorWithClock`. +- `ScheduleTimeoutRequest` / `ScheduleRecurringTickRequest` / + `CancelTimeoutRequest` — Msg variants schedule/cancel a timer; + one-shot and recurring share the same `ID` namespace. +- `MapTimeoutExpired` / `MapTickFired` — Wrap a target ref to convert + `ExpiredMsg`/`TickFiredMsg` into a domain actor's own message type. ## Relationships - **Depends on**: `baselib/actor` (actor framework). -- **Depended on by**: `round` (forfeit collection timeouts, registration - timeouts), `oor` (retry timers via `SigningOutboxHandler.TimeoutActor`). +- **Depended on by**: `round` (forfeit/registration timeouts), `oor` + (retry timers via `SigningOutboxHandler`), `credit` (retry + callbacks). +- **Messages to/from**: Receives `ScheduleTimeoutRequest` / + `ScheduleRecurringTickRequest` / `CancelTimeoutRequest` from any + actor; sends `ExpiredMsg` / `TickFiredMsg` back to the `Callback` + ref supplied in the request. ## Invariants -- One-shot and recurring timers share the same ID namespace. Scheduling - either type with an existing ID cancels the prior entry, regardless of - type. +- One-shot and recurring timers share the same ID namespace; + scheduling either type with an existing ID cancels the prior entry + regardless of type. - `ScheduleRecurringTickRequest.Interval` must be strictly positive; - zero or negative is rejected before touching actor state. -- Every `Receive` path returns `fn.Ok[Resp](&AckResponse{Success: true})`; - the only error return is for invalid `Interval` on - `ScheduleRecurringTickRequest`. + zero/negative is rejected before touching state (an immediate + re-arming loop would starve the mailbox). +- Recurring ticks are "fixed-delay" (next fire = handler-finish + + interval), not `time.Ticker`'s fixed-rate-with-drops. +- Callers must call `Start(ref)` with the actor's own registered ref + before any request is delivered; calling `Receive` directly without + a mailbox in front breaks the self-tell model. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/timeout/CLAUDE.md b/timeout/CLAUDE.md index 6f777ffe0..6ac357bd1 100644 --- a/timeout/CLAUDE.md +++ b/timeout/CLAUDE.md @@ -2,86 +2,51 @@ ## Purpose -Generic fire-and-forget timeout scheduling actor. Sends `ExpiredMsg` to a -callback when a one-shot timeout fires, and `TickFiredMsg` on each -recurring tick scheduled via `ScheduleRecurringTickRequest`. - -## Architecture - -The actor follows a strict self-tell model: clock callbacks never mutate -actor state directly. Each `Clock.AfterFunc` callback Tells an internal -fire message (`internalTimerFired` / `internalTickFired`) into the -actor's own mailbox via the self-ref attached at `Start`. All state -mutation — adding/removing entries from the `oneshots` and `recurring` -maps, delivering user-facing messages, and re-arming recurring chains — -happens single-threadedly inside `Receive`. There is no internal mutex -and no per-entry forwarder goroutine. - -Recurring ticks are implemented as a chain of one-shot timers that -re-arm from inside `handleTickFired`, which gives "fixed-delay" -semantics (next fire = handler-finish + interval) rather than -`time.Ticker`'s "fixed-rate with drops". Stale fires that race with a -Cancel or reschedule are filtered by a per-entry generation token. - -## Clock Abstraction - -- `Clock` — Interface (`Now() time.Time`, `AfterFunc(d, f) Stoppable`) - that drives all timer creation. Allows deterministic time injection in - tests. -- `Stoppable` — Interface satisfied directly by `*time.Timer`; returned - by `Clock.AfterFunc`. -- `RealClock` — Production implementation backed by the standard library. -- `NewActor()` — Constructor using `RealClock`. -- `NewActorWithClock(clock Clock)` — Test constructor; inject a fake - clock for deterministic timer behavior without wall-clock delays. - -## Transform Helpers - -- `MapTimeoutExpired[Out](targetRef, mapFn)` — Wraps a target ref to - convert `*ExpiredMsg` deliveries into the caller's message type using - `actor.NewMapInputRef`. Eliminates boilerplate adapter types when wiring - one-shot timeouts to domain actors. -- `MapTickFired[Out](targetRef, mapFn)` — Same pattern for `*TickFiredMsg`. - Both helpers are the idiomatic way to wire the timeout actor to a domain - actor. - -## Message Types - -- `ScheduleTimeoutRequest` — Schedule a one-shot timer for `Duration`. - Fires `*ExpiredMsg` to `Target` ref. -- `ScheduleRecurringTickRequest` — Schedule a recurring tick. `Interval` - must be strictly positive (zero/negative is rejected before touching - state). Fires `*TickFiredMsg` on each tick. -- `CancelTimeoutRequest` — Cancel a scheduled timeout or recurring tick by - ID. One-shot and recurring timers share the same ID namespace; either - type can be cancelled with this message. -- `ExpiredMsg` — Delivered to the target ref when a one-shot timer fires. -- `TickFiredMsg` — Delivered on each recurring tick. `FiredAt` carries the - clock-goroutine capture time, not the Receive-processing time; important - for test assertions. - -## Wiring - -Callers must call `Start(ref)` after registering the actor with the -actor system; `ref` is the `actor.ActorRef` (or any -`actor.TellOnlyRef[Msg]`) returned by `RegisterWithSystem` / `Spawn`. -External callers must Tell into the actor through that same ref — -calling `Receive` directly is unsafe under the self-tell model because -clock callbacks expect a serializing mailbox in front of `Receive`. +Generic fire-and-forget timeout scheduling actor. Schedules one-shot +timeouts and recurring ticks, delivering `ExpiredMsg` / +`TickFiredMsg` to a caller-supplied callback ref when they fire. + +## Key Types + +- `Actor` — Holds `oneshots`/`recurring` entry maps; all state mutation + happens single-threadedly inside `Receive`. Clock callbacks never + touch actor state directly — they self-Tell an internal fire message + (`internalTimerFired`/`internalTickFired`) carrying a generation + token, so stale fires racing a Cancel/reschedule are dropped. +- `Clock` — Interface (`Now`, `AfterFunc`) abstracting the time source; + `RealClock` is the production impl, tests inject a fake via + `NewActorWithClock`. +- `ScheduleTimeoutRequest` / `ScheduleRecurringTickRequest` / + `CancelTimeoutRequest` — Msg variants schedule/cancel a timer; + one-shot and recurring share the same `ID` namespace. +- `MapTimeoutExpired` / `MapTickFired` — Wrap a target ref to convert + `ExpiredMsg`/`TickFiredMsg` into a domain actor's own message type. ## Relationships - **Depends on**: `baselib/actor` (actor framework). -- **Depended on by**: `round` (forfeit collection timeouts, registration - timeouts), `oor` (retry timers via `SigningOutboxHandler.TimeoutActor`). +- **Depended on by**: `round` (forfeit/registration timeouts), `oor` + (retry timers via `SigningOutboxHandler`), `credit` (retry + callbacks). +- **Messages to/from**: Receives `ScheduleTimeoutRequest` / + `ScheduleRecurringTickRequest` / `CancelTimeoutRequest` from any + actor; sends `ExpiredMsg` / `TickFiredMsg` back to the `Callback` + ref supplied in the request. ## Invariants -- One-shot and recurring timers share the same ID namespace. Scheduling - either type with an existing ID cancels the prior entry, regardless of - type. +- One-shot and recurring timers share the same ID namespace; + scheduling either type with an existing ID cancels the prior entry + regardless of type. - `ScheduleRecurringTickRequest.Interval` must be strictly positive; - zero or negative is rejected before touching actor state. -- Every `Receive` path returns `fn.Ok[Resp](&AckResponse{Success: true})`; - the only error return is for invalid `Interval` on - `ScheduleRecurringTickRequest`. + zero/negative is rejected before touching state (an immediate + re-arming loop would starve the mailbox). +- Recurring ticks are "fixed-delay" (next fire = handler-finish + + interval), not `time.Ticker`'s fixed-rate-with-drops. +- Callers must call `Start(ref)` with the actor's own registered ref + before any request is delivered; calling `Receive` directly without + a mailbox in front breaks the self-tell model. + +## Deep Docs + +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/tools/AGENTS.md b/tools/AGENTS.md index 1419ba543..b5c14dcfa 100644 --- a/tools/AGENTS.md +++ b/tools/AGENTS.md @@ -2,16 +2,38 @@ ## Purpose -Development tool dependencies (`tools.go` for protoc plugins, sqlc, linters). +Build-tooling package: pins Go tool dependencies (`tools.go`) and hosts +`linters/`, a custom golangci-lint plugin enforcing an 80-column line +limit that is tab- and log-call-aware. + +## Key Types + +- `linters.LLPlugin` — golangci-lint plugin implementing the `ll` + linter (`register.LinterPlugin`); reports lines exceeding + `LLConfig.LineLength` after expanding leading tabs to + `LLConfig.TabWidth` spaces, skipping `//go:` directives, import + blocks, and lines matching `LLConfig.LogRegex` (structured log + calls, which may wrap args across lines). +- `linters.New(settings)` — Plugin constructor golangci-lint calls via + `.custom-gcl.yml`; fills default line length (80), tab width (8), + and log regex when unset. ## Relationships -- **Depends on**: nothing (Go module tool dependencies). -- **Depended on by**: `make rpc`, `make sqlc`, `make lint`. +- **Depends on**: `golangci-lint`/`plugin-module-register`, + `golang.org/x/tools/go/analysis` (analyzer framework). +- **Depended on by**: `make lint` / `make lint-changed` / + `make install-custom-gcl` (builds `custom-gcl` per + `.custom-gcl.yml`, which registers this module as a plugin). + +## Invariants + +- `tools.go` is guarded by `//go:build tools` and never compiled into + the main binary; it exists only to pin tool versions in `go.mod`. +- The `ll` linter's log-line skip relies on `LogRegex` matching the + start of a structured log call; changing log helper naming + conventions requires updating `defaultLogRegex` here too. -## Local Linting +## Deep Docs -- `make lint-changed-local` — fast no-Docker check against branch changes - (uses native `custom-gcl` built via `make install-custom-gcl`). -- `make lint-local` — full local scope, no Docker. -- `make lint` — canonical Docker-based linter (matches CI). +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/tools/CLAUDE.md b/tools/CLAUDE.md index 1419ba543..b5c14dcfa 100644 --- a/tools/CLAUDE.md +++ b/tools/CLAUDE.md @@ -2,16 +2,38 @@ ## Purpose -Development tool dependencies (`tools.go` for protoc plugins, sqlc, linters). +Build-tooling package: pins Go tool dependencies (`tools.go`) and hosts +`linters/`, a custom golangci-lint plugin enforcing an 80-column line +limit that is tab- and log-call-aware. + +## Key Types + +- `linters.LLPlugin` — golangci-lint plugin implementing the `ll` + linter (`register.LinterPlugin`); reports lines exceeding + `LLConfig.LineLength` after expanding leading tabs to + `LLConfig.TabWidth` spaces, skipping `//go:` directives, import + blocks, and lines matching `LLConfig.LogRegex` (structured log + calls, which may wrap args across lines). +- `linters.New(settings)` — Plugin constructor golangci-lint calls via + `.custom-gcl.yml`; fills default line length (80), tab width (8), + and log regex when unset. ## Relationships -- **Depends on**: nothing (Go module tool dependencies). -- **Depended on by**: `make rpc`, `make sqlc`, `make lint`. +- **Depends on**: `golangci-lint`/`plugin-module-register`, + `golang.org/x/tools/go/analysis` (analyzer framework). +- **Depended on by**: `make lint` / `make lint-changed` / + `make install-custom-gcl` (builds `custom-gcl` per + `.custom-gcl.yml`, which registers this module as a plugin). + +## Invariants + +- `tools.go` is guarded by `//go:build tools` and never compiled into + the main binary; it exists only to pin tool versions in `go.mod`. +- The `ll` linter's log-line skip relies on `LogRegex` matching the + start of a structured log call; changing log helper naming + conventions requires updating `defaultLogRegex` here too. -## Local Linting +## Deep Docs -- `make lint-changed-local` — fast no-Docker check against branch changes - (uses native `custom-gcl` built via `make install-custom-gcl`). -- `make lint-local` — full local scope, no Docker. -- `make lint` — canonical Docker-based linter (matches CI). +- [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map diff --git a/txconfirm/AGENTS.md b/txconfirm/AGENTS.md index 6af44f7e9..f0d79ccb8 100644 --- a/txconfirm/AGENTS.md +++ b/txconfirm/AGENTS.md @@ -43,8 +43,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon and a subscriber. - `CancelInterestReq` / `CancelInterestResp` — drop a subscriber; the last subscriber's cancel tears down tracking. +- `BumpNowReq` / `BumpNowResp` — operator "bump this stuck tx now" Ask: + forces an immediate CPFP bump at a supplied fee rate (clamped to the + broadcaster's max) instead of waiting for the next interval. - `TxConfirmed` / `TxFailed` — terminal `Notification` types delivered to each subscriber. +- `NewServiceKey` / `LookupRef` — actor-system service-key helpers + (`ServiceKeyName = "txconfirm"`) so callers resolve the shared actor + ref via the receptionist instead of holding a direct reference. - `TxState` — `New`, `Broadcasting`, `AwaitingConfirmation`, `FeeBumping`, `Confirmed`, `Failed`. `Broadcasting` covers BOTH the initial attempt and the "reached no mempool, retrying" case; @@ -55,6 +61,11 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon - `Config.BroadcastFailureAlertThreshold` — consecutive no-mempool failures before the operator escalation fires (default 3). Time to first alert ≈ threshold × `FeeBumpIntervalBlocks` blocks. +- Internal fee-input fanout FSM (`fee_bump_fsm_*.go`, driven from + `fee_input_actor.go`) — when a CPFP broadcast fails with + `ErrCPFPFeeInputUnavailable`, the actor fans a wallet self-payment + out to fresh confirmed UTXOs, watches it via chainsource, and + retries every stuck `Broadcasting` parent once it confirms. ## Relationships @@ -62,8 +73,10 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon (confirmation watches, block epochs, broadcast, package submission, fee estimation, preflight), `wallet` (`Utxo`, `OutputLeaser`, `LockID`), `lib/tx/arktx` (`TxVersion` constant, `IsAnchorOutput`). -- **Depended on by**: `unroll`, `btcwbackend` (fee-input selection - helper), `darepod`, `db`. +- **Depended on by**: `unroll` (exit-tx confirmation), `wallet` + (`wallet_sweep_actor.go` / `boarding_sweep_actor.go` confirm their + sweep txs through `EnsureConfirmedReq` + `MapNotification`), + `darepod` (wiring/registration). - **Sends → `chainsource`** (Ask): `BestHeightRequest`, `SubscribeBlocksRequest`, `RegisterConfRequest`, `UnregisterConfRequest`, `BroadcastTxRequest`, @@ -147,5 +160,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon lifecycle, CPFP correctness invariants, PSBT finalization, service-key round trip, and eviction. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/txconfirm/CLAUDE.md b/txconfirm/CLAUDE.md index 6af44f7e9..f0d79ccb8 100644 --- a/txconfirm/CLAUDE.md +++ b/txconfirm/CLAUDE.md @@ -43,8 +43,14 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon and a subscriber. - `CancelInterestReq` / `CancelInterestResp` — drop a subscriber; the last subscriber's cancel tears down tracking. +- `BumpNowReq` / `BumpNowResp` — operator "bump this stuck tx now" Ask: + forces an immediate CPFP bump at a supplied fee rate (clamped to the + broadcaster's max) instead of waiting for the next interval. - `TxConfirmed` / `TxFailed` — terminal `Notification` types delivered to each subscriber. +- `NewServiceKey` / `LookupRef` — actor-system service-key helpers + (`ServiceKeyName = "txconfirm"`) so callers resolve the shared actor + ref via the receptionist instead of holding a direct reference. - `TxState` — `New`, `Broadcasting`, `AwaitingConfirmation`, `FeeBumping`, `Confirmed`, `Failed`. `Broadcasting` covers BOTH the initial attempt and the "reached no mempool, retrying" case; @@ -55,6 +61,11 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon - `Config.BroadcastFailureAlertThreshold` — consecutive no-mempool failures before the operator escalation fires (default 3). Time to first alert ≈ threshold × `FeeBumpIntervalBlocks` blocks. +- Internal fee-input fanout FSM (`fee_bump_fsm_*.go`, driven from + `fee_input_actor.go`) — when a CPFP broadcast fails with + `ErrCPFPFeeInputUnavailable`, the actor fans a wallet self-payment + out to fresh confirmed UTXOs, watches it via chainsource, and + retries every stuck `Broadcasting` parent once it confirms. ## Relationships @@ -62,8 +73,10 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon (confirmation watches, block epochs, broadcast, package submission, fee estimation, preflight), `wallet` (`Utxo`, `OutputLeaser`, `LockID`), `lib/tx/arktx` (`TxVersion` constant, `IsAnchorOutput`). -- **Depended on by**: `unroll`, `btcwbackend` (fee-input selection - helper), `darepod`, `db`. +- **Depended on by**: `unroll` (exit-tx confirmation), `wallet` + (`wallet_sweep_actor.go` / `boarding_sweep_actor.go` confirm their + sweep txs through `EnsureConfirmedReq` + `MapNotification`), + `darepod` (wiring/registration). - **Sends → `chainsource`** (Ask): `BestHeightRequest`, `SubscribeBlocksRequest`, `RegisterConfRequest`, `UnregisterConfRequest`, `BroadcastTxRequest`, @@ -147,5 +160,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/txcon lifecycle, CPFP correctness invariants, PSBT finalization, service-key round trip, and eviction. - [ARCHITECTURE.md](../ARCHITECTURE.md) — System-wide package map. - - diff --git a/unroll/AGENTS.md b/unroll/AGENTS.md index 1ce09d608..3e258004e 100644 --- a/unroll/AGENTS.md +++ b/unroll/AGENTS.md @@ -98,6 +98,22 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol policy by `(ExitPolicyKind, ExitPolicyRef)`. Implemented by `vhtlcrecovery/unrollpolicy.ExitSpendPolicyResolver`. +### Feasibility & Funding + +- `AssessExitFeasibility(ExitFeasibilityInput) ExitFeasibility` — + up-front verdict folding wallet-funded CPFP cost (recovery-tx + ancestry) and VTXO-funded sweep cost into one check, so admission can + refuse an exit that would leave a dust sweep or burn more in fees + than the VTXO is worth (darepo-client#608) instead of stranding it + in an exit state after a min-relay-fee broadcast failure. +- `PlanExitFunding(desc, mat, feeRate, ...) ExitFundingPlan` — + derives the wallet fee-input amount an operator/caller should fund + before starting the exit; `RecommendedExitFeeInputAmount` reads the + verdict for the same number. +- `ExitProgress` (in `GetStatusResp`) — `ConfirmedTxs`/`InFlightTxs`/ + `ReadyTxs`/`BlockedTxs` counts over the proof graph, for status + probes. + ### Support - `LocalProofAssembler` — assembles a `recovery.Proof` from the VTXO @@ -148,7 +164,9 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - **Depended on by**: `darepod` (wires the registry via the lazy chain-resolver seam, PR #264), `vhtlcrecovery/coordinator` (admission via `EnsureUnrollRequest`), `vhtlcrecovery/unrollpolicy` (implements - `ExitSpendPolicyResolver` and `ExitSpendPolicy`). + `ExitSpendPolicyResolver` and `ExitSpendPolicy`), `fraud` (admits a + `TriggerFraudSpend` unroll via `EnsureUnrollRequest` when it detects a + counterparty spend racing our forfeit). - **Sends**: - → `txconfirm` (Ask): `EnsureConfirmedReq` per proof node and for the final sweep; txid dedup makes retries idempotent. @@ -272,5 +290,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - [lib/recovery/CLAUDE.md](../lib/recovery/CLAUDE.md) — immutable proof graph. - [ARCHITECTURE.md](../ARCHITECTURE.md) — system-wide package map. - - diff --git a/unroll/CLAUDE.md b/unroll/CLAUDE.md index 1ce09d608..3e258004e 100644 --- a/unroll/CLAUDE.md +++ b/unroll/CLAUDE.md @@ -98,6 +98,22 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol policy by `(ExitPolicyKind, ExitPolicyRef)`. Implemented by `vhtlcrecovery/unrollpolicy.ExitSpendPolicyResolver`. +### Feasibility & Funding + +- `AssessExitFeasibility(ExitFeasibilityInput) ExitFeasibility` — + up-front verdict folding wallet-funded CPFP cost (recovery-tx + ancestry) and VTXO-funded sweep cost into one check, so admission can + refuse an exit that would leave a dust sweep or burn more in fees + than the VTXO is worth (darepo-client#608) instead of stranding it + in an exit state after a min-relay-fee broadcast failure. +- `PlanExitFunding(desc, mat, feeRate, ...) ExitFundingPlan` — + derives the wallet fee-input amount an operator/caller should fund + before starting the exit; `RecommendedExitFeeInputAmount` reads the + verdict for the same number. +- `ExitProgress` (in `GetStatusResp`) — `ConfirmedTxs`/`InFlightTxs`/ + `ReadyTxs`/`BlockedTxs` counts over the proof graph, for status + probes. + ### Support - `LocalProofAssembler` — assembles a `recovery.Proof` from the VTXO @@ -148,7 +164,9 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - **Depended on by**: `darepod` (wires the registry via the lazy chain-resolver seam, PR #264), `vhtlcrecovery/coordinator` (admission via `EnsureUnrollRequest`), `vhtlcrecovery/unrollpolicy` (implements - `ExitSpendPolicyResolver` and `ExitSpendPolicy`). + `ExitSpendPolicyResolver` and `ExitSpendPolicy`), `fraud` (admits a + `TriggerFraudSpend` unroll via `EnsureUnrollRequest` when it detects a + counterparty spend racing our forfeit). - **Sends**: - → `txconfirm` (Ask): `EnsureConfirmedReq` per proof node and for the final sweep; txid dedup makes retries idempotent. @@ -272,5 +290,3 @@ For field-level detail, use `go doc github.com/lightninglabs/darepo-client/unrol - [lib/recovery/CLAUDE.md](../lib/recovery/CLAUDE.md) — immutable proof graph. - [ARCHITECTURE.md](../ARCHITECTURE.md) — system-wide package map. - - diff --git a/vhtlcrecovery/AGENTS.md b/vhtlcrecovery/AGENTS.md index 0983a69e8..75ca55062 100644 --- a/vhtlcrecovery/AGENTS.md +++ b/vhtlcrecovery/AGENTS.md @@ -33,8 +33,6 @@ state. `vhtlcrecovery/unrollpolicy` (builds concrete exit spend policies from job rows), `darepod` (RPC handlers: `ArmVHTLCRecovery`, `EscalateVHTLCRecovery`, etc.) -- **Sends**: nothing (pure data types, no actors) -- **Receives**: nothing (pure data types, no actors) ## Invariants diff --git a/vhtlcrecovery/CLAUDE.md b/vhtlcrecovery/CLAUDE.md index 0983a69e8..75ca55062 100644 --- a/vhtlcrecovery/CLAUDE.md +++ b/vhtlcrecovery/CLAUDE.md @@ -33,8 +33,6 @@ state. `vhtlcrecovery/unrollpolicy` (builds concrete exit spend policies from job rows), `darepod` (RPC handlers: `ArmVHTLCRecovery`, `EscalateVHTLCRecovery`, etc.) -- **Sends**: nothing (pure data types, no actors) -- **Receives**: nothing (pure data types, no actors) ## Invariants diff --git a/vhtlcrecovery/coordinator/AGENTS.md b/vhtlcrecovery/coordinator/AGENTS.md index 06164d844..c5801a7c4 100644 --- a/vhtlcrecovery/coordinator/AGENTS.md +++ b/vhtlcrecovery/coordinator/AGENTS.md @@ -33,12 +33,11 @@ This package exists as a child of `vhtlcrecovery` to avoid an import cycle: `ActorUnrollRegistry`). - **Depended on by**: `darepod` (instantiates and wires the service; implements `TargetMaterializer` via `vhtlcRecoveryTargetMaterializer`). -- **Sends**: - - → `unroll` registry: `EnsureUnrollRequest`, `GetStatusRequest` -- **Receives**: - - ← API: `ArmRecovery`, `EscalateRecovery`, `CancelRecovery`, - `CompleteRecovery`, `FailRecovery`, `GetStatus`, `ListRecoveries` - (from `darepod.RPCServer` via `Service`) +- **Messages to/from**: Sends `EnsureUnrollRequest` / `GetStatusRequest` -> + `unroll` registry (via `UnrollRegistry`). `Service` methods (`ArmRecovery`, + `EscalateRecovery`, `CancelRecovery`, `GetRecoveryStatus`, + `ListRecoveryStatuses`, `RestoreNonTerminal`) are called directly by + `darepod.RPCServer`, not actor messages. ## Invariants @@ -49,8 +48,10 @@ This package exists as a child of `vhtlcrecovery` to avoid an import cycle: already escalated before shutdown. - Any existing unroll job for the same target must carry the same `exit_policy_kind` and `exit_policy_ref`; mismatches fail closed. -- The raw preimage is not present in this package. Claim policies resolve it - later through the policy adapter. +- `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` + deliberately). ## Deep Docs diff --git a/vhtlcrecovery/coordinator/CLAUDE.md b/vhtlcrecovery/coordinator/CLAUDE.md index 06164d844..c5801a7c4 100644 --- a/vhtlcrecovery/coordinator/CLAUDE.md +++ b/vhtlcrecovery/coordinator/CLAUDE.md @@ -33,12 +33,11 @@ This package exists as a child of `vhtlcrecovery` to avoid an import cycle: `ActorUnrollRegistry`). - **Depended on by**: `darepod` (instantiates and wires the service; implements `TargetMaterializer` via `vhtlcRecoveryTargetMaterializer`). -- **Sends**: - - → `unroll` registry: `EnsureUnrollRequest`, `GetStatusRequest` -- **Receives**: - - ← API: `ArmRecovery`, `EscalateRecovery`, `CancelRecovery`, - `CompleteRecovery`, `FailRecovery`, `GetStatus`, `ListRecoveries` - (from `darepod.RPCServer` via `Service`) +- **Messages to/from**: Sends `EnsureUnrollRequest` / `GetStatusRequest` -> + `unroll` registry (via `UnrollRegistry`). `Service` methods (`ArmRecovery`, + `EscalateRecovery`, `CancelRecovery`, `GetRecoveryStatus`, + `ListRecoveryStatuses`, `RestoreNonTerminal`) are called directly by + `darepod.RPCServer`, not actor messages. ## Invariants @@ -49,8 +48,10 @@ This package exists as a child of `vhtlcrecovery` to avoid an import cycle: already escalated before shutdown. - Any existing unroll job for the same target must carry the same `exit_policy_kind` and `exit_policy_ref`; mismatches fail closed. -- The raw preimage is not present in this package. Claim policies resolve it - later through the policy adapter. +- `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` + deliberately). ## Deep Docs diff --git a/vhtlcrecovery/unrollpolicy/AGENTS.md b/vhtlcrecovery/unrollpolicy/AGENTS.md index 271be8933..3213ca162 100644 --- a/vhtlcrecovery/unrollpolicy/AGENTS.md +++ b/vhtlcrecovery/unrollpolicy/AGENTS.md @@ -36,8 +36,6 @@ parent package. - **Depended on by**: `darepod` (constructs `ExitSpendPolicyResolver`, registers into `unroll.RegistryConfig`, installs `PreimageResolver` via `PreimageResolverRegistry.SetResolver` at swap-runtime startup). -- **Sends**: nothing (policy builder, no actors) -- **Receives**: nothing (policy builder, no actors) ## Invariants diff --git a/vhtlcrecovery/unrollpolicy/CLAUDE.md b/vhtlcrecovery/unrollpolicy/CLAUDE.md index 271be8933..3213ca162 100644 --- a/vhtlcrecovery/unrollpolicy/CLAUDE.md +++ b/vhtlcrecovery/unrollpolicy/CLAUDE.md @@ -36,8 +36,6 @@ parent package. - **Depended on by**: `darepod` (constructs `ExitSpendPolicyResolver`, registers into `unroll.RegistryConfig`, installs `PreimageResolver` via `PreimageResolverRegistry.SetResolver` at swap-runtime startup). -- **Sends**: nothing (policy builder, no actors) -- **Receives**: nothing (policy builder, no actors) ## Invariants diff --git a/vtxo/AGENTS.md b/vtxo/AGENTS.md index 354b1ac8e..4ea382d56 100644 --- a/vtxo/AGENTS.md +++ b/vtxo/AGENTS.md @@ -14,20 +14,46 @@ when the local wallet owns the receive script. ## Key Types - `VTXOState` — Sealed interface for all states (Live, Spending, Spent, PendingForfeit, Forfeiting, Forfeited, UnilateralExit, Failed). -- `Descriptor` — Complete VTXO metadata: `Outpoint`, `Amount`, `PkScript`, `OwnerKey` (keychain.KeyDescriptor), `OperatorKey`, `TapScript`, `TreePath`, `RoundID`, `CommitmentTxID`, `BatchExpiry`, `RelativeExpiry`, `TreeDepth`, `ChainDepth` (OOR hop count), `CreatedHeight`, `Status`. +- `Descriptor` — Complete VTXO metadata: `Outpoint`, `Amount`, `PolicyTemplate` + (the authoritative semantic policy for ownership/spend semantics), + `PkScript`, `ClientKey` (keychain.KeyDescriptor), `OperatorKey`, + `TapScript`, `Ancestry []Ancestry`, `RoundID`, `CommitmentTxID`, + `BatchExpiry`, `RelativeExpiry`, `ChainDepth` (OOR hop count), + `CreatedHeight`, `Status`, `ConstructionVersion` (zero-indexed build-rule + version stamped at creation; validated only at the ingress edge, trusted + verbatim once persisted). Use `EffectivePkScript`/`EffectivePolicyTemplate` + to derive the current script/policy rather than reading `PkScript` + directly, since custom policies decode through `PolicyTemplate`. - `Manager` — Actor managing per-VTXO FSM instances, lifecycle, and admission gating. Configured via `ManagerConfig`. - `ManagerConfig` — Configuration holding Store, Wallet, ChainSource, ActorSystem, ChainParams, ExpiryConfig, RoundActor ref, ChainResolver ref, optional `Log`, optional `LedgerSink fn.Option[ledger.Sink]`, - `ForfeitVTXOActorAskTimeout`, `RefreshFeeQuoter`, `ExitOutcomeResolver`, and - `ReservationStore`. Confirmed exit-cost accounting is emitted by unroll + `ForfeitVTXOActorAskTimeout`, `RefreshFeeQuoter`, `FetchOperatorKey`, + `ForfeitParticipantSigner`, `TerminalVTXOObserver`, `ExitOutcomeResolver`, + and `ReservationStore`. Confirmed exit-cost accounting is emitted by unroll after final sweep confirmation. `ForfeitVTXOActorAskTimeout` (default 5 s) bounds forfeit and refresh child asks so a blocked child actor cannot monopolize the manager until the outer RPC deadline. Zero uses the default; negative disables the timeout. Spend-path asks keep the caller's - context. `ExitOutcomeResolver` is called at startup to reconcile VTXOs still - persisted in `VTXOStatusUnilateralExit` with their terminal job outcome. - `ReservationStore` is used at startup to sweep orphaned Spending VTXOs. + context. `FetchOperatorKey` fetches the operator's current long-term key at + refresh-join time so the new VTXO output's policy binds it (the old VTXO's + stored key is never reused). `ForfeitParticipantSigner` collects non-local + participant signatures for custom VTXO policies after connector assignment. + `TerminalVTXOObserver` fires with the outpoint whenever a VTXO leaves the + active set. `ExitOutcomeResolver` is called at startup to reconcile VTXOs + still persisted in `VTXOStatusUnilateralExit` with their terminal job + outcome. `ReservationStore` is used at startup to sweep orphaned Spending + VTXOs. +- `CustomForfeitInput` (`lib/actormsg`) — Describes a caller-supplied VTXO + outside the wallet's live coin set that still needs a local actor to sign + the exact round forfeit tx. `ActivateCustomForfeitInputsRequest` (sent by + `wallet` before round-intent registration) spins up temporary + `PendingForfeit` actors for these inputs; `DropCustomForfeitInputsRequest` + (sent by `wallet` and `round` on rejection/rollback) tears them down. +- `ForfeitParticipantSignRequest` / `ForfeitParticipantSigner` — Request + describing the exact forfeit tx (connector prevout already assigned) that a + non-local participant must sign, and the hook that supplies those + signatures for custom VTXO policies. - `ExitOutcomeResolution` — Terminal result for an exiting VTXO: `Outcome` (`ExitOutcomeRecoverable` or `ExitOutcomeConfirmed`) and `Reason`. - `ExitOutcomeResolver` — Function type @@ -56,7 +82,7 @@ when the local wallet owns the receive script. ## Relationships -- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor system), `lib/tree` (tree paths), `lib/arkscript` (taproot construction and policy helpers in `IncomingVTXOHandler`), `lib/actormsg` (admission message types), `arkrpc` (`IncomingVTXOEvent`), `chainsource` (block epochs), `ledger` (`Sink` type for compatibility with manager wiring), `unroll` (via `ExitOutcomeResolver` callback wired by `darepod`). +- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor system), `lib/types` (`Ancestry`), `lib/arkscript` (taproot construction and policy helpers in `IncomingVTXOHandler`), `lib/actormsg` (admission and custom-forfeit message types), `arkrpc` (`IncomingVTXOEvent`), `chainsource` (block epochs), `coinselect` (largest-first VTXO selection), `metrics` (optional `OORTransferReceivedMsg` sink), `ledger` (`Sink` type for compatibility with manager wiring), `unroll` (via `ExitOutcomeResolver` callback wired by `darepod`). - **Depended on by**: `round` (triggers forfeit requests), `oor` (incoming VTXOs), `wallet` (admission gating), `db` (persistence), `darepod` (wiring, owned-script adapters, incoming event route). - **Sends**: - → `round` (via manager relay): `RelayToRoundMsg` wrapping `ForfeitSignatureSubmission` @@ -66,7 +92,8 @@ when the local wallet owns the receive script. `ExitCostMsg` after sweep confirmation - **Receives**: - ← `round`: `ForfeitRequestEvent`, `ForfeitConfirmedEvent`, `ForfeitSignedEvent`, `ForfeitReleasedEvent`, `BlockEpochEvent`, `PendingForfeitEvent`, `SpendReserveEvent`, `SpendReleasedEvent`, `SpendCompletedEvent`, `ResumeVTXOEvent` - - ← `wallet` (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest` + - ← `wallet` (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest`, `ActivateCustomForfeitInputsRequest`, `DropCustomForfeitInputsRequest` + - ← `round` (via `lib/actormsg`): `DropCustomForfeitInputsRequest` (rollback on rejected round intent) - ← `chainsource` (via Manager): `BlockEpochEvent` - ← `serverconn` (via `EventRouter` route `MethodIncomingVTXO`): `IncomingVTXOMsg` (wrapping `arkrpc.IncomingVTXOEvent`), routed to `IncomingVTXOHandler` - ← `unroll` (via `RegistryConfig.VTXOExitObserver`, forwarded by `darepod`): `ExitOutcomeNotification` — terminal exit job result forwarded to reconcile VTXO lifecycle after an unroll completes or fails cleanly @@ -83,7 +110,7 @@ when the local wallet owns the receive script. ancestry slice for callers that need worst-case unilateral-exit timing (e.g. `expiry.go`). - The DB persistence layer stores ancestry rows in the - `vtxo_ancestry_paths` side table (migration 000009) keyed by VTXO + `vtxo_ancestry_paths` side table (migration 000004) keyed by VTXO outpoint; routine queries (`ListUnspentVTXOs`, `GetVTXO`) skip the ancestry join and only load it when the unroller resolves an exit. @@ -103,6 +130,7 @@ when the local wallet owns the receive script. past the FSM transition without affecting the transition outcome. - **Startup reconcile of unilateral-exit VTXOs.** When `ManagerConfig.ExitOutcomeResolver` is set, `Start` calls `reconcileUnilateralExits` after recovering actors. For each VTXO in `VTXOStatusUnilateralExit`, it resolves the terminal outcome: `ExitOutcomeRecoverable` (no on-chain footprint) rolls the VTXO back to `LiveState` and spawns a fresh actor; `ExitOutcomeConfirmed` retires it to `SpentState`. `None` (job still running) is left untouched. - **Startup sweep of orphaned Spending VTXOs.** When `ManagerConfig.ReservationStore` is set, `Start` calls `sweepOrphanedReservations` after all actors are recovered. A Spending VTXO with no reservation row in the durable index is provably orphaned (its spend session died before checkpointing) and is released back to `LiveState` via `SpendReleasedEvent`. The sweep aborts entirely if `ListReservedOutpoints` fails to avoid releasing VTXOs an in-flight spend still owns. +- **Startup sweep of orphaned PendingForfeit VTXOs.** `Start` unconditionally calls `releaseOrphanedForfeits` after actor recovery. Any VTXO still in `VTXOStatusPendingForfeit` at startup is provably orphaned — forfeit signatures are submitted only on the PendingForfeit -> Forfeiting transition, so it has leaked no signature and is safe to release to `LiveState`. VTXOs already in `Forfeiting`/`Forfeited` are past the point of no return and are left untouched for chain-confirmation reconciliation. - **Atomic reservation cleanup.** `VTXOStore.UpdateVTXOStatusReleasingReservation` deletes the spending-reservation row in the same transaction as the VTXO status change when a VTXO leaves `SpendingState` (via `SpendReleasedEvent`, `SpendCompletedEvent`, or escalation to `UnilateralExitState`). This prevents the durable index from retaining stale rows that would mask a future orphan on the same outpoint. - `ForceUnrollEvent` is accepted in `LiveState`, `PendingForfeitState`, `SpendingState`, and `ForfeitingState`: each transitions to `UnilateralExitState` and emits `ExpiringNotification` + `VTXOStatusUpdate{UnilateralExit}`. It does **not** emit `VTXOTerminatedNotification` on intent — `UnilateralExitState` is **non-terminal** (darepo-client#602), 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 re-unroll of a VTXO already in `UnilateralExitState` self-loops with no outbox; the `Unroll` RPC short-circuits it earlier via the persisted `VTXOStatusUnilateralExit` status. - `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). 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). diff --git a/vtxo/CLAUDE.md b/vtxo/CLAUDE.md index 354b1ac8e..4ea382d56 100644 --- a/vtxo/CLAUDE.md +++ b/vtxo/CLAUDE.md @@ -14,20 +14,46 @@ when the local wallet owns the receive script. ## Key Types - `VTXOState` — Sealed interface for all states (Live, Spending, Spent, PendingForfeit, Forfeiting, Forfeited, UnilateralExit, Failed). -- `Descriptor` — Complete VTXO metadata: `Outpoint`, `Amount`, `PkScript`, `OwnerKey` (keychain.KeyDescriptor), `OperatorKey`, `TapScript`, `TreePath`, `RoundID`, `CommitmentTxID`, `BatchExpiry`, `RelativeExpiry`, `TreeDepth`, `ChainDepth` (OOR hop count), `CreatedHeight`, `Status`. +- `Descriptor` — Complete VTXO metadata: `Outpoint`, `Amount`, `PolicyTemplate` + (the authoritative semantic policy for ownership/spend semantics), + `PkScript`, `ClientKey` (keychain.KeyDescriptor), `OperatorKey`, + `TapScript`, `Ancestry []Ancestry`, `RoundID`, `CommitmentTxID`, + `BatchExpiry`, `RelativeExpiry`, `ChainDepth` (OOR hop count), + `CreatedHeight`, `Status`, `ConstructionVersion` (zero-indexed build-rule + version stamped at creation; validated only at the ingress edge, trusted + verbatim once persisted). Use `EffectivePkScript`/`EffectivePolicyTemplate` + to derive the current script/policy rather than reading `PkScript` + directly, since custom policies decode through `PolicyTemplate`. - `Manager` — Actor managing per-VTXO FSM instances, lifecycle, and admission gating. Configured via `ManagerConfig`. - `ManagerConfig` — Configuration holding Store, Wallet, ChainSource, ActorSystem, ChainParams, ExpiryConfig, RoundActor ref, ChainResolver ref, optional `Log`, optional `LedgerSink fn.Option[ledger.Sink]`, - `ForfeitVTXOActorAskTimeout`, `RefreshFeeQuoter`, `ExitOutcomeResolver`, and - `ReservationStore`. Confirmed exit-cost accounting is emitted by unroll + `ForfeitVTXOActorAskTimeout`, `RefreshFeeQuoter`, `FetchOperatorKey`, + `ForfeitParticipantSigner`, `TerminalVTXOObserver`, `ExitOutcomeResolver`, + and `ReservationStore`. Confirmed exit-cost accounting is emitted by unroll after final sweep confirmation. `ForfeitVTXOActorAskTimeout` (default 5 s) bounds forfeit and refresh child asks so a blocked child actor cannot monopolize the manager until the outer RPC deadline. Zero uses the default; negative disables the timeout. Spend-path asks keep the caller's - context. `ExitOutcomeResolver` is called at startup to reconcile VTXOs still - persisted in `VTXOStatusUnilateralExit` with their terminal job outcome. - `ReservationStore` is used at startup to sweep orphaned Spending VTXOs. + context. `FetchOperatorKey` fetches the operator's current long-term key at + refresh-join time so the new VTXO output's policy binds it (the old VTXO's + stored key is never reused). `ForfeitParticipantSigner` collects non-local + participant signatures for custom VTXO policies after connector assignment. + `TerminalVTXOObserver` fires with the outpoint whenever a VTXO leaves the + active set. `ExitOutcomeResolver` is called at startup to reconcile VTXOs + still persisted in `VTXOStatusUnilateralExit` with their terminal job + outcome. `ReservationStore` is used at startup to sweep orphaned Spending + VTXOs. +- `CustomForfeitInput` (`lib/actormsg`) — Describes a caller-supplied VTXO + outside the wallet's live coin set that still needs a local actor to sign + the exact round forfeit tx. `ActivateCustomForfeitInputsRequest` (sent by + `wallet` before round-intent registration) spins up temporary + `PendingForfeit` actors for these inputs; `DropCustomForfeitInputsRequest` + (sent by `wallet` and `round` on rejection/rollback) tears them down. +- `ForfeitParticipantSignRequest` / `ForfeitParticipantSigner` — Request + describing the exact forfeit tx (connector prevout already assigned) that a + non-local participant must sign, and the hook that supplies those + signatures for custom VTXO policies. - `ExitOutcomeResolution` — Terminal result for an exiting VTXO: `Outcome` (`ExitOutcomeRecoverable` or `ExitOutcomeConfirmed`) and `Reason`. - `ExitOutcomeResolver` — Function type @@ -56,7 +82,7 @@ when the local wallet owns the receive script. ## Relationships -- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor system), `lib/tree` (tree paths), `lib/arkscript` (taproot construction and policy helpers in `IncomingVTXOHandler`), `lib/actormsg` (admission message types), `arkrpc` (`IncomingVTXOEvent`), `chainsource` (block epochs), `ledger` (`Sink` type for compatibility with manager wiring), `unroll` (via `ExitOutcomeResolver` callback wired by `darepod`). +- **Depends on**: `baselib/protofsm` (FSM engine), `baselib/actor` (actor system), `lib/types` (`Ancestry`), `lib/arkscript` (taproot construction and policy helpers in `IncomingVTXOHandler`), `lib/actormsg` (admission and custom-forfeit message types), `arkrpc` (`IncomingVTXOEvent`), `chainsource` (block epochs), `coinselect` (largest-first VTXO selection), `metrics` (optional `OORTransferReceivedMsg` sink), `ledger` (`Sink` type for compatibility with manager wiring), `unroll` (via `ExitOutcomeResolver` callback wired by `darepod`). - **Depended on by**: `round` (triggers forfeit requests), `oor` (incoming VTXOs), `wallet` (admission gating), `db` (persistence), `darepod` (wiring, owned-script adapters, incoming event route). - **Sends**: - → `round` (via manager relay): `RelayToRoundMsg` wrapping `ForfeitSignatureSubmission` @@ -66,7 +92,8 @@ when the local wallet owns the receive script. `ExitCostMsg` after sweep confirmation - **Receives**: - ← `round`: `ForfeitRequestEvent`, `ForfeitConfirmedEvent`, `ForfeitSignedEvent`, `ForfeitReleasedEvent`, `BlockEpochEvent`, `PendingForfeitEvent`, `SpendReserveEvent`, `SpendReleasedEvent`, `SpendCompletedEvent`, `ResumeVTXOEvent` - - ← `wallet` (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest` + - ← `wallet` (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest`, `ActivateCustomForfeitInputsRequest`, `DropCustomForfeitInputsRequest` + - ← `round` (via `lib/actormsg`): `DropCustomForfeitInputsRequest` (rollback on rejected round intent) - ← `chainsource` (via Manager): `BlockEpochEvent` - ← `serverconn` (via `EventRouter` route `MethodIncomingVTXO`): `IncomingVTXOMsg` (wrapping `arkrpc.IncomingVTXOEvent`), routed to `IncomingVTXOHandler` - ← `unroll` (via `RegistryConfig.VTXOExitObserver`, forwarded by `darepod`): `ExitOutcomeNotification` — terminal exit job result forwarded to reconcile VTXO lifecycle after an unroll completes or fails cleanly @@ -83,7 +110,7 @@ when the local wallet owns the receive script. ancestry slice for callers that need worst-case unilateral-exit timing (e.g. `expiry.go`). - The DB persistence layer stores ancestry rows in the - `vtxo_ancestry_paths` side table (migration 000009) keyed by VTXO + `vtxo_ancestry_paths` side table (migration 000004) keyed by VTXO outpoint; routine queries (`ListUnspentVTXOs`, `GetVTXO`) skip the ancestry join and only load it when the unroller resolves an exit. @@ -103,6 +130,7 @@ when the local wallet owns the receive script. past the FSM transition without affecting the transition outcome. - **Startup reconcile of unilateral-exit VTXOs.** When `ManagerConfig.ExitOutcomeResolver` is set, `Start` calls `reconcileUnilateralExits` after recovering actors. For each VTXO in `VTXOStatusUnilateralExit`, it resolves the terminal outcome: `ExitOutcomeRecoverable` (no on-chain footprint) rolls the VTXO back to `LiveState` and spawns a fresh actor; `ExitOutcomeConfirmed` retires it to `SpentState`. `None` (job still running) is left untouched. - **Startup sweep of orphaned Spending VTXOs.** When `ManagerConfig.ReservationStore` is set, `Start` calls `sweepOrphanedReservations` after all actors are recovered. A Spending VTXO with no reservation row in the durable index is provably orphaned (its spend session died before checkpointing) and is released back to `LiveState` via `SpendReleasedEvent`. The sweep aborts entirely if `ListReservedOutpoints` fails to avoid releasing VTXOs an in-flight spend still owns. +- **Startup sweep of orphaned PendingForfeit VTXOs.** `Start` unconditionally calls `releaseOrphanedForfeits` after actor recovery. Any VTXO still in `VTXOStatusPendingForfeit` at startup is provably orphaned — forfeit signatures are submitted only on the PendingForfeit -> Forfeiting transition, so it has leaked no signature and is safe to release to `LiveState`. VTXOs already in `Forfeiting`/`Forfeited` are past the point of no return and are left untouched for chain-confirmation reconciliation. - **Atomic reservation cleanup.** `VTXOStore.UpdateVTXOStatusReleasingReservation` deletes the spending-reservation row in the same transaction as the VTXO status change when a VTXO leaves `SpendingState` (via `SpendReleasedEvent`, `SpendCompletedEvent`, or escalation to `UnilateralExitState`). This prevents the durable index from retaining stale rows that would mask a future orphan on the same outpoint. - `ForceUnrollEvent` is accepted in `LiveState`, `PendingForfeitState`, `SpendingState`, and `ForfeitingState`: each transitions to `UnilateralExitState` and emits `ExpiringNotification` + `VTXOStatusUpdate{UnilateralExit}`. It does **not** emit `VTXOTerminatedNotification` on intent — `UnilateralExitState` is **non-terminal** (darepo-client#602), 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 re-unroll of a VTXO already in `UnilateralExitState` self-loops with no outbox; the `Unroll` RPC short-circuits it earlier via the persisted `VTXOStatusUnilateralExit` status. - `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). 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). diff --git a/wallet/AGENTS.md b/wallet/AGENTS.md index 62c6ab19c..a97f34d69 100644 --- a/wallet/AGENTS.md +++ b/wallet/AGENTS.md @@ -13,8 +13,8 @@ refresh, leave, OOR spend, and directed send flows. - `Ark` — Main actor managing boarding addresses, UTXO enumeration, confirmation polling, boarding sweeps, admission forwarding, and VTXO selection/locking. Holds a `ledgerSink` field (`fn.Option[ledger.Sink]`) used by wallet UTXO and boarding-sweep paths to Tell accounting messages to the ledger actor. - `NewArk` — Constructor; takes the `ledgerSink` as a **required** argument (`fn.Option[ledger.Sink]`) rather than a setter, so every call site is forced to make an explicit emission choice. Production passes `fn.Some(ledger.NewSink(actorSystem))`; harnesses and unit tests that do not register a ledger actor pass `fn.None[ledger.Sink]()`. - `emitUTXOCreated(ctx, utxo, blockHeight, classification)` — Internal helper that null-safely builds a `ledger.UTXOCreatedMsg` from a wallet `Utxo` and Tells it to `ledgerSink`. Negative block heights clamp to `0` rather than wrapping under a direct `uint32` cast; nil `utxo` and `fn.None` sink are silent no-ops. -- `LockID` — `[32]byte` caller-scoped output lease identifier used to associate leased UTXOs with a specific subsystem (`txconfirmLockID` in `txconfirm`, etc.). -- `OutputLeaser` — Interface for UTXO output leasing: `LeaseOutput(ctx, outpoint, lockID, expiry)` and `ReleaseOutput(ctx, outpoint, lockID)`. Implemented by all three `BoardingBackend` implementations (`btcwbackend`, `lndbackend`, `lwwallet`) to coordinate cross-subsystem UTXO reservation. +- `LockID` — Type alias for `walletcore.LockID`, a `[32]byte` caller-scoped output lease identifier used to associate leased UTXOs with a specific subsystem (`txconfirmLockID` in `txconfirm`, etc.). +- `OutputLeaser` — Type alias for `walletcore.OutputLeaser`: `LeaseOutput(ctx, lockID, outpoint, expiry)` and `ReleaseOutput(ctx, lockID, outpoint)`. Implemented by all three `BoardingBackend` implementations (`btcwbackend`, `lndbackend`, `lwwallet`) to coordinate cross-subsystem UTXO reservation. - `BoardingBackend` — Interface for wallet integration (key derivation, taproot import, ListUnspent). `GetTransaction` returns `*TxInfo` (containing tx, block hash, and block height). - `TxInfo` — Struct wrapping a confirmed transaction with its block hash and block height. Returned by `BoardingBackend.GetTransaction`. - `BoardingStore` — Interface for persisting boarding addresses and intents. @@ -26,7 +26,21 @@ refresh, leave, OOR spend, and directed send flows. - `BoardingUtxoConfirmedEvent` — Tell-message sent when a VTXO confirms. - `BoardRequest` / `BoardResponse` — Ask-request from RPC to trigger boarding flow. - `GetBoardingBalanceResponse` — Balance breakdown with fields: `Balance` (confirmed), `UtxoCount`, `UnconfirmedBalance` (zero-conf), `UnconfirmedUtxoCount`, `AdoptedBalance` (accepted into round, VTXOs not yet live), `PendingSweepBalance`, `SweepPendingCount`. -- `RefreshVTXOsRequest` — Ask-request to select VTXOs for refresh and compose intent package. Carries `OperatorFees map[wire.OutPoint]btcutil.Amount`; when non-empty, the handler validates each fee is non-negative and below the VTXO amount, then subtracts it from the new VTXO output before registering with the round actor. Empty map is pre-#269 zero-fee behavior (tests, legacy paths). +- `RefreshVTXOsRequest` — Ask-request to select wallet-owned VTXOs for refresh + (`TargetOutpoints`, `ForceRefresh`) and compose the intent package; the + server is the fee authority at seal time (#270), so no per-input fee is + pre-quoted on this path. +- `CustomRefreshInput` / `CustomRefreshOutput` / `RefreshCustomVTXOsRequest` / + `DropCustomRefreshVTXOsRequest` — Custom-policy refresh path for + caller-supplied VTXOs outside the wallet's live coin set (e.g. vHTLC + contract outputs). The wallet does not select these from live balance; it + validates the input/output pairing and activates temporary `PendingForfeit` + signer actors on the VTXO manager (`ActivateCustomForfeitInputsRequest`) so + the later connector-bound forfeit can be locally signed. + `CustomRefreshOutput.FixedAmount` pins the replacement value exactly so a + contract output (e.g. a vHTLC) can't have its amount shrunk to pay round + fees. `DropCustomRefreshVTXOsRequest` releases the temporary signer actors + when round registration never starts. - `SelectAndLockVTXOsRequest` — Ask-request to select and lock VTXOs for OOR spend. `MinChangeAmount`, when positive, asks selection to avoid a non-zero residual below that amount (exact spends still valid). - `LeaveVTXOsRequest` — Ask-request to select VTXOs for cooperative leave. Carries a singular `DestOutput *wire.TxOut` plus a per-outpoint `DestOutputs map[wire.OutPoint]*wire.TxOut` override map; the handler picks `DestOutputs[op]` when set and falls back to `DestOutput`. Per-input operator fees are no longer pre-quoted on the client — under the #270 seal-time fee handshake the server stamps the residual onto the IsChange=true leave output at seal time, so the wallet ships the full forfeited amount on each leave output. - `CompleteSpendVTXOsRequest` — Tell-message to finalize spend and release locks. @@ -34,29 +48,49 @@ refresh, leave, OOR spend, and directed send flows. - `SendRecipient` — Describes a single directed send destination (pkscript, amount, recipient client key). - `SendVTXOsRequest` / `SendVTXOsResponse` — Ask-request for in-round directed sends. Validates each recipient amount is within `(0, MaxSatoshi]` and that the running total never overflows `int64`, atomically selects and reserves VTXOs via `SelectAndReserveForfeitRequest`, builds forfeit + recipient VTXO intents, and registers with the round actor. Supports dry-run mode for previewing coin selection without committing. Reserved VTXOs are released via a deferred cleanup that uses `context.WithoutCancel` so cleanup survives caller disconnect; on success, a `committed` flag is set to skip the release. - `SendOnChainRequest` — Ask-request to plan and submit an atomic on-chain payment from VTXOs. Supports two modes: bounded send (`TargetAmountSat` > 0, empty `SweepOutpoints`) and sweep-all (`SweepOutpoints` non-empty). Bounded mode selects VTXOs with headroom for `OperatorFee + DustLimit` and creates a change VTXO. Sweep-all drains the exact outpoints to the destination with no change. Supports `DryRun` mode. -- `SendOnChainResponse` — Response to `SendOnChainRequest` carrying the selected outpoints, total amount, operator fee, and leave output details. -- `SendOnChainStatus` — Terminal outcome enum: `SendOnChainStatusSubmitted` (intent queued for next round), `SendOnChainStatusDryRun` (dry-run preview, no commitment). +- `SendOnChainResponse` — Response to `SendOnChainRequest`: `Status` + (Submitted/Preview), `IntentID` (`PendingIntentID`, a deterministic hash of + the consumed outpoints; zero on dry-run), `ActualAmountSat`, + `SelectedOutpoints`, `TotalSelected`, `ChangeAmount`. +- `boardingClamp` / `clampBoardingAmount` — Applies the operator's advertised + `MaxVTXOAmount`/`MaxUserBalance` terms to a confirmed boarding balance + before boarding: clips the boarded amount to remaining cap headroom, splits + it into `[floor, maxVTXO]`-sized VTXO outputs, and routes the clipped + remainder to a change leave output (or, when no even split is possible, + a sub-dust remainder to miner fee via `DustToFee`). Errors: + `ErrBoardingCapReached`, `ErrBoardAmountBelowFloor`, + `ErrTooManyBoardOutputs` (per-VTXO maximum would require more than + `maxBoardOutputs` pieces), `ErrMaxVTXOBelowFloor`. +- `WithFetchOperatorTerms` — `ArkOption` wiring the closure `handleBoard` uses + to fetch operator terms and clamp boarding via `applyBoardingLimits`; nil + (default) preserves unbounded boarding. +- `WithMetricsSink` — `ArkOption` wiring an optional `metrics.Sink` so the + boarding-sweep watcher reports terminal sweep failures via + `darepod_background_task_errors_total`; a no-op when omitted. +- `SendOnChainStatus` — Terminal outcome enum: `SendOnChainStatusSubmitted` (intent queued for next round), `SendOnChainStatusPreview` (dry-run preview, no commitment). - `GetConfirmedBoardingIntentsRequest` / `GetConfirmedBoardingIntentsResponse` — Ask-request to retrieve currently confirmed boarding intents (used by the RPC/CLI layer to report boarding balance with policy metadata). - `VTXODescriptor.EffectivePolicyTemplate` — Decodes the serialized `PolicyTemplate` field on the wallet-level VTXO descriptor using `lib/arkscript`. ## Relationships -- **Depends on**: `baselib/actor` (actor system), `chainsource` (block epoch notifications), `lib/actormsg` (VTXO manager admission types), `ledger` (`Sink` alias for emission + `UTXOCreatedMsg` / `ClassificationDeposit` constants). +- **Depends on**: `baselib/actor` (actor system), `chainsource` (block epoch notifications), `lib/actormsg` (VTXO manager / round admission types, incl. custom-forfeit activation), `lib/arkscript` (custom-refresh spend paths), `lib/types` (`Ancestry`, `LeaveRequest`, `OperatorTerms`), `lib/tx/arktx` (tx version constant), `walletcore` (`LockID`/`OutputLeaser` aliases, `Utxo`), `txconfirm` (boarding-sweep confirmation tracking), `ledger` (`Sink` alias for emission + `UTXOCreatedMsg` / `ClassificationDeposit` constants), `metrics` (optional background-task-error sink). - **Depended on by**: `round` (boarding intents, types: `BoardingAddress`, `SelectedVTXO`), `db` (persistence), `darepod` (wiring). - **Sends**: - → `round` (via registered notifier): `BoardingUtxoConfirmedEvent` - → `round` (via `lib/actormsg`): `TriggerBoardMsg` (VTXO amounts for - boarding), `RegisterIntentMsg` (pre-composed cooperative intents with + boarding, the named `Outpoints` this trigger covers, and an optional + `Change` leave when operator limits clip the confirmed balance), + `RegisterIntentMsg` (pre-composed cooperative intents with forfeits + VTXOs/leaves); `TriggerRegistration=true` for directed sends so the round FSM advances from `PendingRoundAssembly` immediately, `false` for refresh/leave batching - - → `vtxo` manager (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest` + - → `vtxo` manager (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest`, `ActivateCustomForfeitInputsRequest`, `DropCustomForfeitInputsRequest` (custom refresh signer activation/teardown) - → `ledger` actor (via `ledger.Sink` Tell, when `fn.Some`): `UTXOCreatedMsg` on every processed confirmed wallet UTXO, tagged `ClassificationDeposit`. `handleUTXOCreated` expands this into both a `wallet_utxo_log` audit row AND a double-entry deposit leg (debit `wallet_balance`, credit `opening_balance`). Confirmed boarding sweeps emit a single `BoardingSweepConfirmedMsg` (txid, chain cost, per-input list, destination) that the ledger expands into the fee, per-input, and destination clearing legs inside one Commit, so `wallet_clearing` is updated atomically. - → `round` (via `lib/types.VTXORequest.Origin`): wallet intent composition tags each locally-owned VTXO output with a `VTXOOrigin` classifier so the round actor's downstream ledger emission dispatches to the correct `Source`. Refresh outputs and directed-send self-change get `VTXOOriginRoundRefresh`; boarding-path tagging lives in `round.handleTriggerBoard` (`VTXOOriginRoundBoarding`). - **Receives**: - ← `chainsource`: `BlockEpochNotification` (triggers UTXO polling) - ← `round`: `RegisterConfirmationNotifierRequest`, `UnregisterConfirmationNotifierRequest` - - ← API: `CreateBoardingAddressRequest`, `GetActiveBoardingAddressesRequest`, `GetBoardingBalanceRequest`, `GetConfirmedBoardingIntentsRequest`, `RefreshVTXOsRequest`, `SelectAndLockVTXOsRequest`, `LeaveVTXOsRequest`, `BoardRequest`, `CompleteSpendVTXOsRequest`, `UnlockVTXOsRequest`, `SendVTXOsRequest`, `SendOnChainRequest` + - ← API: `CreateBoardingAddressRequest`, `GetActiveBoardingAddressesRequest`, `GetBoardingBalanceRequest`, `GetConfirmedBoardingIntentsRequest`, `RefreshVTXOsRequest`, `RefreshCustomVTXOsRequest`, `DropCustomRefreshVTXOsRequest`, `SelectAndLockVTXOsRequest`, `LeaveVTXOsRequest`, `BoardRequest`, `CompleteSpendVTXOsRequest`, `UnlockVTXOsRequest`, `SendVTXOsRequest`, `SendOnChainRequest` ## Invariants @@ -71,6 +105,24 @@ refresh, leave, OOR spend, and directed send flows. - `handleSendVTXOs` uses a `defer`-based release rather than a `releaseAndFail` helper: any error path (including dry-run) falls through to the deferred release, and the `committed` flag is set only after the round actor accepts the intent. Context is preserved via `context.WithoutCancel` so cleanup is not dropped when the caller disconnects. - `handleSendVTXOs` rejects pre-flight any directed send with multiple recipients and exactly-zero change residual under the #270 seal-time fee handshake. The server is the amount authority and absorbs the operator fee out of the designated `IsChange=true` slot; if there is no residual to absorb the fee against, the server has no slack to deduct fees without silently shifting them onto a recipient leg. The wallet refuses the request rather than letting the server pick the loser. - `VTXOReader` / `VTXODescriptor` / `SelectedVTXO` break the vtxo → round → wallet import cycle by providing wallet-level types that don't reference `vtxo.Descriptor` directly. +- The wallet tracks, in memory, boarding outpoints already handed to the round + actor via `TriggerBoardMsg` that have not yet left the confirmed set, and + excludes them from later triggers. This keeps a second per-block trigger + (fired when a new deposit confirms before an earlier boarding round adopts + its input) from re-registering an already-in-flight outpoint under a fresh + owner key, which previously produced a quote pkScript-echo mismatch and + failed the round. +- `applyBoardingLimits`/`clampBoardingAmount` clip a confirmed boarding + balance to the operator's `MaxVTXOAmount`/`MaxUserBalance` terms and, when + clipped, mint a change leave output back to a fresh boarding script so the + remainder re-confirms as a new boardable intent. A sub-floor leftover that + cannot form an even `[floor, maxVTXO]` split is burned to miner fee + (`DustToFee`) rather than minted as a dust VTXO. +- The round FSM's `IntentRequested` transition sums VTXO-request and + leave-output values together and hard-fails only if that combined total + is zero; an individual leave output (e.g. an `IsChange=true` slot the + server re-stamps at seal time under the #270 fee handshake) may itself + be zero-value as long as the intent's combined total is not. - Per-subsystem logging via `build.LoggerFromContext` (no global mutable loggers). ## Deep Docs diff --git a/wallet/CLAUDE.md b/wallet/CLAUDE.md index 62c6ab19c..a97f34d69 100644 --- a/wallet/CLAUDE.md +++ b/wallet/CLAUDE.md @@ -13,8 +13,8 @@ refresh, leave, OOR spend, and directed send flows. - `Ark` — Main actor managing boarding addresses, UTXO enumeration, confirmation polling, boarding sweeps, admission forwarding, and VTXO selection/locking. Holds a `ledgerSink` field (`fn.Option[ledger.Sink]`) used by wallet UTXO and boarding-sweep paths to Tell accounting messages to the ledger actor. - `NewArk` — Constructor; takes the `ledgerSink` as a **required** argument (`fn.Option[ledger.Sink]`) rather than a setter, so every call site is forced to make an explicit emission choice. Production passes `fn.Some(ledger.NewSink(actorSystem))`; harnesses and unit tests that do not register a ledger actor pass `fn.None[ledger.Sink]()`. - `emitUTXOCreated(ctx, utxo, blockHeight, classification)` — Internal helper that null-safely builds a `ledger.UTXOCreatedMsg` from a wallet `Utxo` and Tells it to `ledgerSink`. Negative block heights clamp to `0` rather than wrapping under a direct `uint32` cast; nil `utxo` and `fn.None` sink are silent no-ops. -- `LockID` — `[32]byte` caller-scoped output lease identifier used to associate leased UTXOs with a specific subsystem (`txconfirmLockID` in `txconfirm`, etc.). -- `OutputLeaser` — Interface for UTXO output leasing: `LeaseOutput(ctx, outpoint, lockID, expiry)` and `ReleaseOutput(ctx, outpoint, lockID)`. Implemented by all three `BoardingBackend` implementations (`btcwbackend`, `lndbackend`, `lwwallet`) to coordinate cross-subsystem UTXO reservation. +- `LockID` — Type alias for `walletcore.LockID`, a `[32]byte` caller-scoped output lease identifier used to associate leased UTXOs with a specific subsystem (`txconfirmLockID` in `txconfirm`, etc.). +- `OutputLeaser` — Type alias for `walletcore.OutputLeaser`: `LeaseOutput(ctx, lockID, outpoint, expiry)` and `ReleaseOutput(ctx, lockID, outpoint)`. Implemented by all three `BoardingBackend` implementations (`btcwbackend`, `lndbackend`, `lwwallet`) to coordinate cross-subsystem UTXO reservation. - `BoardingBackend` — Interface for wallet integration (key derivation, taproot import, ListUnspent). `GetTransaction` returns `*TxInfo` (containing tx, block hash, and block height). - `TxInfo` — Struct wrapping a confirmed transaction with its block hash and block height. Returned by `BoardingBackend.GetTransaction`. - `BoardingStore` — Interface for persisting boarding addresses and intents. @@ -26,7 +26,21 @@ refresh, leave, OOR spend, and directed send flows. - `BoardingUtxoConfirmedEvent` — Tell-message sent when a VTXO confirms. - `BoardRequest` / `BoardResponse` — Ask-request from RPC to trigger boarding flow. - `GetBoardingBalanceResponse` — Balance breakdown with fields: `Balance` (confirmed), `UtxoCount`, `UnconfirmedBalance` (zero-conf), `UnconfirmedUtxoCount`, `AdoptedBalance` (accepted into round, VTXOs not yet live), `PendingSweepBalance`, `SweepPendingCount`. -- `RefreshVTXOsRequest` — Ask-request to select VTXOs for refresh and compose intent package. Carries `OperatorFees map[wire.OutPoint]btcutil.Amount`; when non-empty, the handler validates each fee is non-negative and below the VTXO amount, then subtracts it from the new VTXO output before registering with the round actor. Empty map is pre-#269 zero-fee behavior (tests, legacy paths). +- `RefreshVTXOsRequest` — Ask-request to select wallet-owned VTXOs for refresh + (`TargetOutpoints`, `ForceRefresh`) and compose the intent package; the + server is the fee authority at seal time (#270), so no per-input fee is + pre-quoted on this path. +- `CustomRefreshInput` / `CustomRefreshOutput` / `RefreshCustomVTXOsRequest` / + `DropCustomRefreshVTXOsRequest` — Custom-policy refresh path for + caller-supplied VTXOs outside the wallet's live coin set (e.g. vHTLC + contract outputs). The wallet does not select these from live balance; it + validates the input/output pairing and activates temporary `PendingForfeit` + signer actors on the VTXO manager (`ActivateCustomForfeitInputsRequest`) so + the later connector-bound forfeit can be locally signed. + `CustomRefreshOutput.FixedAmount` pins the replacement value exactly so a + contract output (e.g. a vHTLC) can't have its amount shrunk to pay round + fees. `DropCustomRefreshVTXOsRequest` releases the temporary signer actors + when round registration never starts. - `SelectAndLockVTXOsRequest` — Ask-request to select and lock VTXOs for OOR spend. `MinChangeAmount`, when positive, asks selection to avoid a non-zero residual below that amount (exact spends still valid). - `LeaveVTXOsRequest` — Ask-request to select VTXOs for cooperative leave. Carries a singular `DestOutput *wire.TxOut` plus a per-outpoint `DestOutputs map[wire.OutPoint]*wire.TxOut` override map; the handler picks `DestOutputs[op]` when set and falls back to `DestOutput`. Per-input operator fees are no longer pre-quoted on the client — under the #270 seal-time fee handshake the server stamps the residual onto the IsChange=true leave output at seal time, so the wallet ships the full forfeited amount on each leave output. - `CompleteSpendVTXOsRequest` — Tell-message to finalize spend and release locks. @@ -34,29 +48,49 @@ refresh, leave, OOR spend, and directed send flows. - `SendRecipient` — Describes a single directed send destination (pkscript, amount, recipient client key). - `SendVTXOsRequest` / `SendVTXOsResponse` — Ask-request for in-round directed sends. Validates each recipient amount is within `(0, MaxSatoshi]` and that the running total never overflows `int64`, atomically selects and reserves VTXOs via `SelectAndReserveForfeitRequest`, builds forfeit + recipient VTXO intents, and registers with the round actor. Supports dry-run mode for previewing coin selection without committing. Reserved VTXOs are released via a deferred cleanup that uses `context.WithoutCancel` so cleanup survives caller disconnect; on success, a `committed` flag is set to skip the release. - `SendOnChainRequest` — Ask-request to plan and submit an atomic on-chain payment from VTXOs. Supports two modes: bounded send (`TargetAmountSat` > 0, empty `SweepOutpoints`) and sweep-all (`SweepOutpoints` non-empty). Bounded mode selects VTXOs with headroom for `OperatorFee + DustLimit` and creates a change VTXO. Sweep-all drains the exact outpoints to the destination with no change. Supports `DryRun` mode. -- `SendOnChainResponse` — Response to `SendOnChainRequest` carrying the selected outpoints, total amount, operator fee, and leave output details. -- `SendOnChainStatus` — Terminal outcome enum: `SendOnChainStatusSubmitted` (intent queued for next round), `SendOnChainStatusDryRun` (dry-run preview, no commitment). +- `SendOnChainResponse` — Response to `SendOnChainRequest`: `Status` + (Submitted/Preview), `IntentID` (`PendingIntentID`, a deterministic hash of + the consumed outpoints; zero on dry-run), `ActualAmountSat`, + `SelectedOutpoints`, `TotalSelected`, `ChangeAmount`. +- `boardingClamp` / `clampBoardingAmount` — Applies the operator's advertised + `MaxVTXOAmount`/`MaxUserBalance` terms to a confirmed boarding balance + before boarding: clips the boarded amount to remaining cap headroom, splits + it into `[floor, maxVTXO]`-sized VTXO outputs, and routes the clipped + remainder to a change leave output (or, when no even split is possible, + a sub-dust remainder to miner fee via `DustToFee`). Errors: + `ErrBoardingCapReached`, `ErrBoardAmountBelowFloor`, + `ErrTooManyBoardOutputs` (per-VTXO maximum would require more than + `maxBoardOutputs` pieces), `ErrMaxVTXOBelowFloor`. +- `WithFetchOperatorTerms` — `ArkOption` wiring the closure `handleBoard` uses + to fetch operator terms and clamp boarding via `applyBoardingLimits`; nil + (default) preserves unbounded boarding. +- `WithMetricsSink` — `ArkOption` wiring an optional `metrics.Sink` so the + boarding-sweep watcher reports terminal sweep failures via + `darepod_background_task_errors_total`; a no-op when omitted. +- `SendOnChainStatus` — Terminal outcome enum: `SendOnChainStatusSubmitted` (intent queued for next round), `SendOnChainStatusPreview` (dry-run preview, no commitment). - `GetConfirmedBoardingIntentsRequest` / `GetConfirmedBoardingIntentsResponse` — Ask-request to retrieve currently confirmed boarding intents (used by the RPC/CLI layer to report boarding balance with policy metadata). - `VTXODescriptor.EffectivePolicyTemplate` — Decodes the serialized `PolicyTemplate` field on the wallet-level VTXO descriptor using `lib/arkscript`. ## Relationships -- **Depends on**: `baselib/actor` (actor system), `chainsource` (block epoch notifications), `lib/actormsg` (VTXO manager admission types), `ledger` (`Sink` alias for emission + `UTXOCreatedMsg` / `ClassificationDeposit` constants). +- **Depends on**: `baselib/actor` (actor system), `chainsource` (block epoch notifications), `lib/actormsg` (VTXO manager / round admission types, incl. custom-forfeit activation), `lib/arkscript` (custom-refresh spend paths), `lib/types` (`Ancestry`, `LeaveRequest`, `OperatorTerms`), `lib/tx/arktx` (tx version constant), `walletcore` (`LockID`/`OutputLeaser` aliases, `Utxo`), `txconfirm` (boarding-sweep confirmation tracking), `ledger` (`Sink` alias for emission + `UTXOCreatedMsg` / `ClassificationDeposit` constants), `metrics` (optional background-task-error sink). - **Depended on by**: `round` (boarding intents, types: `BoardingAddress`, `SelectedVTXO`), `db` (persistence), `darepod` (wiring). - **Sends**: - → `round` (via registered notifier): `BoardingUtxoConfirmedEvent` - → `round` (via `lib/actormsg`): `TriggerBoardMsg` (VTXO amounts for - boarding), `RegisterIntentMsg` (pre-composed cooperative intents with + boarding, the named `Outpoints` this trigger covers, and an optional + `Change` leave when operator limits clip the confirmed balance), + `RegisterIntentMsg` (pre-composed cooperative intents with forfeits + VTXOs/leaves); `TriggerRegistration=true` for directed sends so the round FSM advances from `PendingRoundAssembly` immediately, `false` for refresh/leave batching - - → `vtxo` manager (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest` + - → `vtxo` manager (via `lib/actormsg`): `SelectAndReserveSpendRequest`, `ReleaseSpendRequest`, `CompleteSpendRequest`, `ReserveForfeitRequest`, `ReleaseForfeitRequest`, `SelectAndReserveForfeitRequest`, `ActivateCustomForfeitInputsRequest`, `DropCustomForfeitInputsRequest` (custom refresh signer activation/teardown) - → `ledger` actor (via `ledger.Sink` Tell, when `fn.Some`): `UTXOCreatedMsg` on every processed confirmed wallet UTXO, tagged `ClassificationDeposit`. `handleUTXOCreated` expands this into both a `wallet_utxo_log` audit row AND a double-entry deposit leg (debit `wallet_balance`, credit `opening_balance`). Confirmed boarding sweeps emit a single `BoardingSweepConfirmedMsg` (txid, chain cost, per-input list, destination) that the ledger expands into the fee, per-input, and destination clearing legs inside one Commit, so `wallet_clearing` is updated atomically. - → `round` (via `lib/types.VTXORequest.Origin`): wallet intent composition tags each locally-owned VTXO output with a `VTXOOrigin` classifier so the round actor's downstream ledger emission dispatches to the correct `Source`. Refresh outputs and directed-send self-change get `VTXOOriginRoundRefresh`; boarding-path tagging lives in `round.handleTriggerBoard` (`VTXOOriginRoundBoarding`). - **Receives**: - ← `chainsource`: `BlockEpochNotification` (triggers UTXO polling) - ← `round`: `RegisterConfirmationNotifierRequest`, `UnregisterConfirmationNotifierRequest` - - ← API: `CreateBoardingAddressRequest`, `GetActiveBoardingAddressesRequest`, `GetBoardingBalanceRequest`, `GetConfirmedBoardingIntentsRequest`, `RefreshVTXOsRequest`, `SelectAndLockVTXOsRequest`, `LeaveVTXOsRequest`, `BoardRequest`, `CompleteSpendVTXOsRequest`, `UnlockVTXOsRequest`, `SendVTXOsRequest`, `SendOnChainRequest` + - ← API: `CreateBoardingAddressRequest`, `GetActiveBoardingAddressesRequest`, `GetBoardingBalanceRequest`, `GetConfirmedBoardingIntentsRequest`, `RefreshVTXOsRequest`, `RefreshCustomVTXOsRequest`, `DropCustomRefreshVTXOsRequest`, `SelectAndLockVTXOsRequest`, `LeaveVTXOsRequest`, `BoardRequest`, `CompleteSpendVTXOsRequest`, `UnlockVTXOsRequest`, `SendVTXOsRequest`, `SendOnChainRequest` ## Invariants @@ -71,6 +105,24 @@ refresh, leave, OOR spend, and directed send flows. - `handleSendVTXOs` uses a `defer`-based release rather than a `releaseAndFail` helper: any error path (including dry-run) falls through to the deferred release, and the `committed` flag is set only after the round actor accepts the intent. Context is preserved via `context.WithoutCancel` so cleanup is not dropped when the caller disconnects. - `handleSendVTXOs` rejects pre-flight any directed send with multiple recipients and exactly-zero change residual under the #270 seal-time fee handshake. The server is the amount authority and absorbs the operator fee out of the designated `IsChange=true` slot; if there is no residual to absorb the fee against, the server has no slack to deduct fees without silently shifting them onto a recipient leg. The wallet refuses the request rather than letting the server pick the loser. - `VTXOReader` / `VTXODescriptor` / `SelectedVTXO` break the vtxo → round → wallet import cycle by providing wallet-level types that don't reference `vtxo.Descriptor` directly. +- The wallet tracks, in memory, boarding outpoints already handed to the round + actor via `TriggerBoardMsg` that have not yet left the confirmed set, and + excludes them from later triggers. This keeps a second per-block trigger + (fired when a new deposit confirms before an earlier boarding round adopts + its input) from re-registering an already-in-flight outpoint under a fresh + owner key, which previously produced a quote pkScript-echo mismatch and + failed the round. +- `applyBoardingLimits`/`clampBoardingAmount` clip a confirmed boarding + balance to the operator's `MaxVTXOAmount`/`MaxUserBalance` terms and, when + clipped, mint a change leave output back to a fresh boarding script so the + remainder re-confirms as a new boardable intent. A sub-floor leftover that + cannot form an even `[floor, maxVTXO]` split is burned to miner fee + (`DustToFee`) rather than minted as a dust VTXO. +- The round FSM's `IntentRequested` transition sums VTXO-request and + leave-output values together and hard-fails only if that combined total + is zero; an individual leave output (e.g. an `IsChange=true` slot the + server re-stamps at seal time under the #270 fee handshake) may itself + be zero-value as long as the intent's combined total is not. - Per-subsystem logging via `build.LoggerFromContext` (no global mutable loggers). ## Deep Docs diff --git a/walletcore/AGENTS.md b/walletcore/AGENTS.md index 9a71ea916..f1eaa78a0 100644 --- a/walletcore/AGENTS.md +++ b/walletcore/AGENTS.md @@ -11,7 +11,10 @@ btcwallet.BtcWallet regardless of the underlying chain source. - `Wallet` — Core wallet struct embedding `input.Signer`. Provides key derivation, P2TR address generation, balance queries, and UTXO listing. Also implements `proofkeys.Backend` (via `ProofSigner` method). Chain-specific implementations embed this to satisfy `round.ClientWallet`. - `BoardingBackendBase` — Shared boarding functionality: taproot script import under BIP86 scope, imported address tracking for UTXO filtering, and HD key derivation. Chain-specific adapters embed this and add `ListUnspent`, `GetTransaction`, `GetBlock`. -- `Config` — Base configuration (seed, chain params, recovery window, DB dir, logger) shared by all btcwallet-backed wallet backends. +- `Config` — Base configuration (seed, wallet password, seed birthday, chain + params (mainnet/testnet/testnet4/regtest), recovery window, DB dir, logger) + shared by all btcwallet-backed wallet backends. `Birthday`, when set, + bounds recovery rescans instead of starting from genesis. - `OutputLeaser` — Interface for UTXO reservation during coin selection: `LeaseOutput(ctx, id, outpoint, expiry)` and `ReleaseOutput(ctx, id, outpoint)`. Implemented by `lwwallet.BoardingBackendAdapter` @@ -25,7 +28,7 @@ btcwallet.BtcWallet regardless of the underlying chain source. ## Relationships - **Depends on**: `build` (context logger extraction), `proofkeys` (implements Backend), `indexer` (SchnorrSigner interface). -- **Depended on by**: `lwwallet` (embeds Wallet + BoardingBackendBase), `btcwbackend` (embeds Wallet + BoardingBackendBase), `darepod` (proof key backend). +- **Depended on by**: `lwwallet` (embeds Wallet + BoardingBackendBase), `btcwbackend` (embeds Wallet + BoardingBackendBase), `darepod` (proof key backend), `wallet` (`LockID`/`OutputLeaser` aliases, `Utxo`), `txconfirm` (`Utxo`/`LockID`/`OutputLeaser` for fee-bump input selection and leasing). ## Invariants @@ -36,7 +39,11 @@ btcwallet.BtcWallet regardless of the underlying chain source. `addressForTaprootScript`, repopulating the filter without a second import attempt — covers the case where btcwallet already persisted the script but the in-memory filter started empty. -- `WalletPassphrase` is shared across all wallet backends for both `PrivatePass` and `PublicPass`. +- The user-supplied `Config.WalletPassword` is btcwallet's `PrivatePass`; the + static `PublicWalletPassphrase` constant covers only public (watch-only) + data. A nil `Config.Seed` opens an existing wallet database; a non-nil seed + creates a new one, and backends must refuse a seed when a database already + exists (btcwallet would silently ignore it). ## Deep Docs diff --git a/walletcore/CLAUDE.md b/walletcore/CLAUDE.md index 97405efad..f1eaa78a0 100644 --- a/walletcore/CLAUDE.md +++ b/walletcore/CLAUDE.md @@ -11,7 +11,10 @@ btcwallet.BtcWallet regardless of the underlying chain source. - `Wallet` — Core wallet struct embedding `input.Signer`. Provides key derivation, P2TR address generation, balance queries, and UTXO listing. Also implements `proofkeys.Backend` (via `ProofSigner` method). Chain-specific implementations embed this to satisfy `round.ClientWallet`. - `BoardingBackendBase` — Shared boarding functionality: taproot script import under BIP86 scope, imported address tracking for UTXO filtering, and HD key derivation. Chain-specific adapters embed this and add `ListUnspent`, `GetTransaction`, `GetBlock`. -- `Config` — Base configuration (seed, chain params, recovery window, DB dir, logger) shared by all btcwallet-backed wallet backends. +- `Config` — Base configuration (seed, wallet password, seed birthday, chain + params (mainnet/testnet/testnet4/regtest), recovery window, DB dir, logger) + shared by all btcwallet-backed wallet backends. `Birthday`, when set, + bounds recovery rescans instead of starting from genesis. - `OutputLeaser` — Interface for UTXO reservation during coin selection: `LeaseOutput(ctx, id, outpoint, expiry)` and `ReleaseOutput(ctx, id, outpoint)`. Implemented by `lwwallet.BoardingBackendAdapter` @@ -25,7 +28,7 @@ btcwallet.BtcWallet regardless of the underlying chain source. ## Relationships - **Depends on**: `build` (context logger extraction), `proofkeys` (implements Backend), `indexer` (SchnorrSigner interface). -- **Depended on by**: `lwwallet` (embeds Wallet + BoardingBackendBase), `btcwbackend` (embeds Wallet + BoardingBackendBase), `darepod` (proof key backend). +- **Depended on by**: `lwwallet` (embeds Wallet + BoardingBackendBase), `btcwbackend` (embeds Wallet + BoardingBackendBase), `darepod` (proof key backend), `wallet` (`LockID`/`OutputLeaser` aliases, `Utxo`), `txconfirm` (`Utxo`/`LockID`/`OutputLeaser` for fee-bump input selection and leasing). ## Invariants From 738989da159bf67cae1b59beed3dc2022edbe40b Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 9 Jul 2026 15:38:41 -0700 Subject: [PATCH 4/4] docs: verify and tighten the docs/ knowledge base Verify concrete claims across the docs/ knowledge base against current source and fix the drift; add the two orphaned design docs to the index and neutralize cross-repo relative links that cannot resolve. --- docs/RPC_MAILBOX_CONTRACT.md | 11 ++-- docs/arkscript_spec.md | 56 +++++++++------- docs/ast-grep-guide.md | 1 + docs/canonical_activity_log_design.md | 4 +- docs/credit_durable_actor_design.md | 41 +++++++----- docs/credit_system.md | 9 +-- docs/daemon_cli_guide.md | 95 ++++++++++++++++----------- docs/dev_rpc_cli_builder.md | 17 +++-- docs/development_guidelines.md | 23 ++----- docs/durable_actor_architecture.md | 52 +++++++-------- docs/durable_actor_quickstart.md | 55 ++++++++-------- docs/fee-change-model.md | 16 +++-- docs/fee_ledger.md | 16 ++--- docs/index.md | 2 + docs/mailbox_architecture.md | 24 +++++-- docs/mailbox_durable_actor_layer.md | 10 ++- docs/policy_arkscript_review_guide.md | 40 ++++++----- docs/sdk_layered_architecture.md | 14 ++-- docs/structured-logging.md | 6 +- docs/swap_background_execution.md | 38 +++++------ docs/swap_system.md | 77 +++++++++++----------- docs/testing-guide.md | 6 +- docs/walletdk_mobile.md | 12 ++-- docs/walletdkrpc_build.md | 12 ++-- 24 files changed, 356 insertions(+), 281 deletions(-) diff --git a/docs/RPC_MAILBOX_CONTRACT.md b/docs/RPC_MAILBOX_CONTRACT.md index dc9660c21..b1f4776ca 100644 --- a/docs/RPC_MAILBOX_CONTRACT.md +++ b/docs/RPC_MAILBOX_CONTRACT.md @@ -67,7 +67,11 @@ As a result: The contract assumes each request envelope contains an idempotency key (a stable identifier suitable for receiver-side deduplication). In the current -envelope model, the request's correlation id serves as this idempotency key: +envelope model this is the envelope's dedicated `idempotency_key` field, +distinct from the `rpc.correlation_id` field used for response demuxing; the +default client implementation derives the correlation id from the +idempotency key when the caller does not override it, so the two typically +match but MUST NOT be assumed to be the same field: - The sender MUST set an idempotency key for any request that it may retry. - The receiver SHOULD use the idempotency key to deduplicate request handling. @@ -75,9 +79,8 @@ envelope model, the request's correlation id serves as this idempotency key: ## Correlation -The request/response pairing is performed using a correlation identifier. -For request envelopes, this correlation id also acts as the idempotency key -used for request deduplication: +The request/response pairing is performed using a correlation identifier +carried in `rpc.correlation_id`: - Each request has a correlation id. - The response MUST carry the correlation id of the request it answers. diff --git a/docs/arkscript_spec.md b/docs/arkscript_spec.md index 5ea040c8c..a11eb0b15 100644 --- a/docs/arkscript_spec.md +++ b/docs/arkscript_spec.md @@ -127,7 +127,7 @@ locks, absolute locktimes, payment-hash preimages). Helper builders live in that also enforces the 32-byte preimage-size rule. The predicate bytes are opaque to the AST walker for the purposes of -`ContainsKey` and key extraction (`lib/arkscript/validate.go:90-129`). This +`ContainsKey` and key extraction (`lib/arkscript/validate.go:208-218`). This is intentional: the AST reasons about *who can sign*, not about *what hashlock values are in play*. @@ -156,7 +156,7 @@ type PolicyTemplate struct { } ``` -Defined in: `lib/arkscript/policy_template.go:116-200`. +Defined in: `lib/arkscript/policy_template.go:127-222`. `PolicyTemplate` preserves the author's leaf order. The canonical taproot tree ordering (by leaf version, then lexicographic script bytes) is applied @@ -181,7 +181,7 @@ Where: - each leaf blob is a length-prefixed `LeafTemplate` encoding (below). Implemented by `PolicyTemplate.Encode` / `DecodePolicyTemplate` -(`lib/arkscript/policy_template.go:174-287`). +(`lib/arkscript/policy_template.go:276-411`). ### 3.3 Binary encoding — LeafTemplate @@ -205,7 +205,7 @@ Each node is `kind(1) || payload`. Payload layout by kind: | `Condition` | `3` | `varbytes(predicate) || varbytes(child-node-encoding)` | Implemented by `EncodeNode` / `decodeNodePayload` / `decodeLockedNode` -(`lib/arkscript/policy_template.go:289-595`). +(`lib/arkscript/policy_template.go:412-645`). > **Note on key encoding:** Multisig currently encodes keys as 32-byte > x-only (via `schnorr.SerializePubKey`). This round-trips lossy for @@ -269,7 +269,7 @@ Extensions that REQUIRE a version bump: ### 4.1 Compilation -`PolicyTemplate.Compile` (`lib/arkscript/policy_template.go:125-149`) walks +`PolicyTemplate.Compile` (`lib/arkscript/policy_template.go:225-249`) walks each leaf's AST, calls `Node.Script()`, wraps the bytes in a `txscript.NewBaseTapLeaf`, and hands the result to `BuildTree`. @@ -287,11 +287,11 @@ Defined in `lib/arkscript/tree.go:33-53`. ### 4.2 Canonical leaf ordering -`sortLeaves` (`lib/arkscript/tree.go:187-198`) sorts by +`sortLeaves` (`lib/arkscript/tree.go:176-198`) sorts by `(LeafVersion, Script)` — leaf version first, then lexicographic by script bytes. This gives a deterministic tap-tree shape independent of how the caller constructed the `PolicyTemplate`. BIP-341 further sorts child hashes -at each merkle branch via `tapBranchHash` (`lib/arkscript/tree.go:310-320`), +at each merkle branch via `tapBranchHash` (`lib/arkscript/tree.go:296-305`), so the tree root is stable under reordering at both layers. ### 4.3 Internal key: ARK NUMS point @@ -305,7 +305,7 @@ if !internalKey.IsEqual(&ARKNUMSKey) { } ``` -(`lib/arkscript/tree.go:224-229`) +(`lib/arkscript/tree.go:210-215`) Because the NUMS key is provably unspendable via key-path, every Ark output is script-path-only. This is the foundational invariant that the "no @@ -313,7 +313,7 @@ operator-unilateral spend" admission check (§7) can build on. ### 4.4 Control block derivation -`CompiledPolicy.SpendInfo(leafIndex)` (`lib/arkscript/tree.go:60-79`) +`CompiledPolicy.SpendInfo(leafIndex)` (`lib/arkscript/tree.go:61-86`) returns a `*SpendInfo` with the witness script and a freshly built BIP-341 control block: @@ -341,11 +341,11 @@ Leaves: [ ] ``` -Defined in `lib/arkscript/standard_vtxo.go:36-67`. +Defined in `lib/arkscript/standard_vtxo.go:36-83`. -Compiled output key computed by `VTXOTapKey` (same file); canonical P2TR -pkScript by `EncodeStandardVTXOArtifacts` -(`lib/arkscript/standard_vtxo.go:81-116`) — the helper used by the wallet +Compiled output key computed by `VTXOTapKey` (`lib/arkscript/spend_helpers.go`); +canonical P2TR pkScript by `EncodeStandardVTXOArtifacts` +(`lib/arkscript/standard_vtxo.go:96-122`) — the helper used by the wallet when constructing recipient descriptors without touching the tree-layer signing key. @@ -416,7 +416,7 @@ Defined in `lib/arkscript/spend_path.go:22-38`. ### 6.2 Witness stack order -`SpendPath.Witness(sigItems...)` (`lib/arkscript/spend_path.go:208-224`) +`SpendPath.Witness(sigItems...)` (`lib/arkscript/spend_path.go:258-274`) assembles: ``` @@ -433,7 +433,7 @@ land in reverse key order. ### 6.3 Tx-context requirements - `RequiredSequence` comes from `DeriveSequence(node)` - (`lib/arkscript/tree.go:160` via `SpendPathForNode`). For CSV-gated paths + (`lib/arkscript/tree.go:146` via `SpendPathForNode`). For CSV-gated paths it returns the `CSV.Lock` value; for non-CSV paths it returns `0xffffffff` (opt-out of BIP-68). - `RequiredLockTime` comes from `ExtractAbsoluteLockTime(node)`. Set only @@ -458,7 +458,7 @@ CSV/CLTV op-code. Where `ver = spendPathVersion = 1` (`lib/arkscript/spend_path.go:17`). The decoder (`DecodeSpendPath`) caps `conditionCount` at 64 (`maxConditions` -at `lib/arkscript/spend_path.go:131`). +at `lib/arkscript/spend_path.go:190`). ### 6.5 Condition witness (durable persistence) @@ -470,7 +470,7 @@ The OOR actor durably persists a `TransferInputSnapshot` that includes a varint(count) || N × varbytes(item) ``` -Caps (`oor/actor_durable_message.go:815-832`): +Caps (`oor/actor_durable_message.go:1365-1381`): | Constant | Value | Bounded thing | |-------------------------------|-------|-------------------------------------------| @@ -486,7 +486,7 @@ from what the persisted form can represent. ### 7.1 `ValidatePolicy` — structural -Signature and invariants (`lib/arkscript/validate.go:30-120`): +Signature and invariants (`lib/arkscript/validate.go:25-107`): ```go func ValidatePolicy(nodes []Node, opts PolicyValidationOpts) error @@ -515,10 +515,14 @@ func ValidateStandardVTXOPolicy(nodes []Node, ``` Requires `minExitDelay > 0` fail-closed, then delegates to -`ValidatePolicy`. Use this on any admission surface that consumes a -**standard Ark VTXO recipient** (e.g. `darepod/rpc_server.go` output -resolver). Custom shapes (vHTLC claims from daemon RPC) continue to use -the structural `ValidatePolicy`. +`ValidatePolicy`. Intended as the admission check for any surface that +consumes a **standard Ark VTXO recipient**. As of this writing, +`darepod/rpc_server.go`'s recipient-output path (`resolveRecipientOutput`, +`validateOutputPolicyTemplate`) does not call this helper directly — +it pre-filters the shape via `DecodeStandardVTXOParams` and falls back to +structural `ValidatePolicy` for both standard and custom shapes. Custom +shapes (vHTLC claims from daemon RPC) also use the structural +`ValidatePolicy`. ### 7.3 Invariants that are NOT enforced here @@ -569,7 +573,7 @@ meaningful allocation. Signers that accept a caller-supplied witness script + control block MUST verify the control block commits to a tap tree whose output key is the declared pkScript. `SpendPath.VerifyBindsToPkScript` -(`lib/arkscript/spend_path.go:60-107`) does this: +(`lib/arkscript/spend_path.go:60-116`) does this: 1. Parse the control block. 2. Assert internal key is `ARKNUMSKey`. @@ -614,8 +618,10 @@ switch to lossless 33-byte compressed in the policy template itself. invariants. The admission surfaces in `darepod/rpc_server.go` and `darepod/wallet_ops.go` layer additional policy-specific checks on top: -- Recipient outputs use `ValidateStandardVTXOPolicy` (with operator-derived - `MinExitDelay`). +- Recipient outputs are pre-filtered to the standard VTXO shape via + `DecodeStandardVTXOParams` (`resolveRecipientOutput`), then checked with + structural `ValidatePolicy` plus a fail-closed non-zero + operator-exit-delay gate (`validateOutputPolicyTemplate`). - Custom OOR inputs use structural `ValidatePolicy` but also `MatchesPkScript` and `SpendPath.VerifyBindsToPkScript`. diff --git a/docs/ast-grep-guide.md b/docs/ast-grep-guide.md index 7eb1e8e29..403ef2edf 100644 --- a/docs/ast-grep-guide.md +++ b/docs/ast-grep-guide.md @@ -47,6 +47,7 @@ sg scan --interactive # Review fixes one by one | `log-error-expanded-form` | Log/error calls should use compact form, not expanded | | `switch-case-needs-spacing` | Switch cases should be separated by blank lines | | `select-case-needs-spacing` | Select cases should be separated by blank lines | +| `no-inline-comments` | Comments should be on their own line, not trailing code | ### Function Definitions (`rules/go-func-def.yml`) diff --git a/docs/canonical_activity_log_design.md b/docs/canonical_activity_log_design.md index 424f6ec4c..4389dbba8 100644 --- a/docs/canonical_activity_log_design.md +++ b/docs/canonical_activity_log_design.md @@ -112,7 +112,7 @@ single mutable-row table would not fix consequence (3). The mailbox avoids the trap by appending one immutable row per event; C1 does the same. Both tables ship as one additive sqlc migration -(`db/sqlc/migrations/0000NN_activity_log.{up,down}.sql`, regenerated via +(`db/sqlc/migrations/000010_activity_log.{up,down}.sql`, regenerated via `make sqlc`), following the existing enum-lookup-table convention. The `kind` and `status` enums mirror the wire enums one-to-one — @@ -222,7 +222,7 @@ kinds have a stable one; the other three are the work. | Kind | canonical_id source | Status today | |------|---------------------|--------------| | SEND (invoice) / RECV | Lightning `payment_hash` | **Stable already.** `normalize.go` keys the swap row by `payment_hash`; the projection is a no-op. | -| OOR send / receive | OOR `session_id` | **Stable already.** Durably persisted (`oor_session_registry`, migration 000019) and RPC-exposed; adopting it is essentially a rename of the txid the row already uses. | +| OOR send / receive | OOR `session_id` | **Stable already.** Durably persisted (`oor_session_registry`, migration 000005) and RPC-exposed; adopting it is essentially a rename of the txid the row already uses. | | SEND (on-chain) / EXIT | a durable leave-job id | **Does not exist yet — must be built.** | | DEPOSIT | a durable deposit-address record id | **Does not exist yet — must be built.** | diff --git a/docs/credit_durable_actor_design.md b/docs/credit_durable_actor_design.md index da3bd90cc..c2fc9a505 100644 --- a/docs/credit_durable_actor_design.md +++ b/docs/credit_durable_actor_design.md @@ -51,7 +51,7 @@ Everything reduces to three durable operations with one stable key each. | Op | Stable key | Authoritative completion signal | |---|---|---| | `pay` (with optional top-up) | `pay:` | server ledger shows credits available, then `StartPay` idempotent by payment hash | -| `recv` (credit receive) | `recv:` | server ledger shows the receive op `CREDITED` | +| `recv` (credit receive) | `recv:` | server ledger shows the receive op `CREDITED` | | `redeem` (credits to vTXO) | `redeem:` | redeemed vTXO lands locally (`FindLiveVTXOByPkScript`) | The **same key** goes to the server `CreateCredit` or `RedeemCredit` *and* to the @@ -148,7 +148,7 @@ row. walletdk Send/Recv │ Ask StartCredit{Pay,Receive}Request (plain actor Ask; returns pending entry) ▼ -CreditRegistry plain in-memory mailbox "credit-client" (RestoreNonTerminal on boot) +Registry plain in-memory mailbox "credit-client" (RestoreNonTerminal on boot) ├─ dedup by opKey (durable table + partial UNIQUE index) ├─ write credit_operations row (ordinary txn) ◄── the one durable table ├─ lazy-spawn + route Resume to child @@ -157,7 +157,7 @@ CreditRegistry plain in-memory mailbox "credit-client" (RestoreNon └─ run the auto-redeem boot reconcile (one-shot) │ Tell Resume (the only durable message into the child) ▼ -CreditOpActor (per op) durable mailbox "credit-op-" (Read/Stage/Commit; protofsm FSM) +OpActor (per op) durable mailbox "credit-op-" (Read/Stage/Commit; protofsm FSM) ├─ gRPC → swap server CreateCredit / ListCredits / RedeemCredit (key = opKey) ├─ DurableTell → OOR registry StartTransfer{IdempotencyKey: opKey, ...} ├─ gRPC → swapclientserver StartPay(invoice, maxCreditSat) (terminal pay) @@ -180,7 +180,7 @@ re-enters the durable child mailbox needs a codec entry. | Message | Kind | Path | |---|---|---| | `StartCreditPayRequest{invoice, maxFeeSat, quote, paymentHash}` | plain `CreditMsg` | walletdk to registry (Ask) | -| `StartCreditReceiveRequest{amountSat, memo, paymentHash}` | plain `CreditMsg` | walletdk to registry (Ask) | +| `StartCreditReceiveRequest{opKey, amountSat, memo}` | plain `CreditMsg` | walletdk to registry (Ask) | | `RedeemRequest{opKey, amountSat}` | plain `CreditMsg` | registry-internal (admitted by `considerRedeem`) | | `ConsiderRedeemRequest{availableSat}` | plain `CreditMsg` | child or boot reconcile to registry (Tell) | | `CreditTerminalNotification{opKey, terminal}` | plain `CreditMsg` | child to registry (reap) | @@ -413,9 +413,11 @@ bridge disappears entirely. ``` credit_operations( + op_id TEXT NOT NULL PRIMARY KEY, -- durable mailbox id source op_key TEXT NOT NULL, -- stable idempotency key - kind TEXT NOT NULL, -- pay | recv | redeem + kind INTEGER NOT NULL, -- 1=pay | 2=recv | 3=redeem state TEXT NOT NULL, + status INTEGER NOT NULL, -- 0=pending | 1=completed | 2=failed server_op_id TEXT, destination_pubkey BLOB, oor_session_id TEXT, @@ -424,27 +426,32 @@ credit_operations( amount_sat BIGINT NOT NULL DEFAULT 0, topup_sat BIGINT NOT NULL DEFAULT 0, max_credit_sat BIGINT NOT NULL DEFAULT 0, - snapshot BLOB, -- opaque resume blob (TLV) + max_fee_sat BIGINT NOT NULL DEFAULT 0, + last_error TEXT, + snapshot_data BLOB, -- opaque resume blob (TLV) + snapshot_version INTEGER NOT NULL DEFAULT 0, created_at, updated_at ) --- partial UNIQUE index on op_key for live-or-completed rows +-- partial UNIQUE index on op_key WHERE status != 2 (live-or-completed rows) ``` -It lives in the daemon-owned swap DB, via `db/queries` and sqlc. Terminal rows -are retained for status and diagnostics. The `state` column holds the persisted -state string; on resume, `decodeCreditState` maps it back to the typed protofsm -state, and an unrecognized string drives the row to a terminal failure rather than -wedging it. +It lives in the daemon-owned swap DB, via `db/sqlc/queries` and sqlc. Terminal +rows are retained for status and diagnostics. The `state` column holds the +persisted state string; on resume, `decodeCreditState` maps it back to the +typed protofsm state, and an unrecognized string drives the row to a terminal +failure rather than wedging it. ### 7.2 Boot and resume -`credit.Register(...)` (called by `swapruntime`-tagged `darepod`, next to -`swapclientserver.Register` and the OOR registry) opens the store, wires the -registry actor, and calls `RestoreNonTerminal` **synchronously** before serving. -Each non-terminal row respawns its child and is told `ResumeCreditOpRequest`, so +`darepod`'s `initCreditRegistry` (called right after the OOR actor registers, +guarded by a nil-check on the swap-runtime-populated `cfg.Swap.Credit*` +bridges) builds the store, constructs the registry via `credit.NewRegistry`, +and calls `RestoreNonTerminal` **synchronously** before serving. Each +non-terminal row respawns its child and is told `ResumeCreditOpRequest`, so it re-drives from persisted state. Retry timers are in-memory and do not survive restart; they are re-armed on resume. After the restore, the daemon starts the -one-shot auto-redeem boot reconcile on the root context. +one-shot auto-redeem boot reconcile (`registry.StartAutoRedeem`) on the root +context. ### 7.3 Crash-recovery walkthrough diff --git a/docs/credit_system.md b/docs/credit_system.md index 4c09ea09c..f526b55fc 100644 --- a/docs/credit_system.md +++ b/docs/credit_system.md @@ -49,7 +49,7 @@ with different parameters is rejected. ## Client Layers -Wallet-facing callers use `walletdk`. Credit details are folded into `Receive`, +Wallet-facing callers use `walletdk`. Credit details are folded into `Recv`, `PrepareSend`, `Send`, and `Balance`. Raw callers can use `sdk/swaps` and `swapclientrpc` directly: @@ -58,8 +58,9 @@ Raw callers can use `sdk/swaps` and `swapclientrpc` directly: intent. - `ListCredits`: read balances, operations, and ledger entries. - `RedeemCredit`: low-level escape hatch for materializing credits into an Ark - output. `walletdk` does not expose or automate redemption for normal wallet - flows. + output. `walletdk` does not expose it directly; the daemon auto-redeems + credits into a vTXO once the balance clears a watermark, without exposing + that decision to the caller. ```mermaid flowchart TB @@ -100,7 +101,7 @@ returns the full plan: ```mermaid flowchart TD - A["Receive(requested_amount_sat)"] --> B{"requested >= dust?"} + A["Recv(requested_amount_sat)"] --> B{"requested >= dust?"} B -->|"yes"| N["normal receive"] B -->|"no"| C{"requested + available_credit >= dust?"} C -->|"no"| R["credit receive invoice"] diff --git a/docs/daemon_cli_guide.md b/docs/daemon_cli_guide.md index 7049bdbe7..18888a8cd 100644 --- a/docs/daemon_cli_guide.md +++ b/docs/daemon_cli_guide.md @@ -7,7 +7,7 @@ daemon (`darepod`) and its CLI (`darepocli`). ### From Source -Requires Go 1.22+. +Requires Go 1.26+ (see `go.mod`). ```bash git clone https://github.com/lightninglabs/darepo-client.git @@ -67,8 +67,6 @@ darepod \ --wallet.esploraurl=http://localhost:3000 \ --server.host=localhost:10010 \ --server.insecure \ - --server.localmailboxid=client1 \ - --server.remotemailboxid=server \ --rpc.listenaddr=localhost:10029 ``` @@ -86,8 +84,6 @@ darepod \ --lnd.macaroonpath=~/.lnd/data/chain/bitcoin/regtest/admin.macaroon \ --server.host=localhost:10010 \ --server.insecure \ - --server.localmailboxid=client1 \ - --server.remotemailboxid=server \ --rpc.listenaddr=localhost:10029 ``` @@ -105,7 +101,7 @@ darepod \ | `--wallet.feeurl` | | Fee-estimate JSON endpoint URL (btcwallet only) | | `--wallet.btcwallet_blockheaderssource` | | Block header import source for btcwallet fast sync | | `--wallet.btcwallet_filterheaderssource` | | Filter header import source for btcwallet fast sync | -| `--wallet.pollinterval` | `5s` | Esplora poll interval (lwwallet only) | +| `--wallet.pollinterval` | `30s` | Esplora poll interval (lwwallet only) | | `--wallet.recoverywindow` | `100` | Address look-ahead window (lwwallet only) | | `--wallet.password_file` | | Auto-unlock password file path (lwwallet/btcwallet) | | `--lnd.host` | `localhost:10009` | lnd gRPC address | @@ -116,8 +112,6 @@ darepod \ | `--server.transport` | `grpc` | Ark operator transport: `grpc` or `rest` | | `--server.insecure` | `false` | Disable TLS for server connection | | `--server.tlscertpath` | | Operator TLS certificate path | -| `--server.localmailboxid` | | This client's mailbox ID | -| `--server.remotemailboxid` | | Server's mailbox ID | | `--rpc.listenaddr` | `localhost:10029` | Daemon gRPC listen address | | `--rpc.tlscertpath` | | Custom TLS cert for daemon RPC | | `--rpc.tlskeypath` | | Custom TLS key for daemon RPC | @@ -128,6 +122,9 @@ Empty Ark and swap addresses resolve from the selected network and transport. See [signet.md](signet.md) for the testnet3, testnet4, and signet endpoints and override examples. +There is no mailbox-ID flag: the client and compound server mailbox IDs are +derived automatically from the client's identity key at connect time. + ### Environment Variables All flags can be set via environment variables with the `DAREPOD_` @@ -251,7 +248,8 @@ darepocli ├── recv — boarding address / Lightning invoice (walletdkrpc) ├── send — Lightning invoice / onchain leave (walletdkrpc) ├── activity — unified wallet activity feed (walletdkrpc) -├── exit [status] — unilateral exit a VTXO +├── exit {status|summary|plan} — cooperative leave by default, forced unroll (walletdkrpc) +├── wallet-sweep — sweep backing wallet to a destination (walletdkrpc) ├── mcp serve — MCP server for AI agents (walletdkrpc) ├── schema — JSON dump of CLI methods ├── ark — power-user parent (no walletdkrpc) @@ -259,10 +257,12 @@ darepocli │ ├── vtxos {list|refresh|leave} │ ├── oor {receive|get|list} │ ├── send {oor|inround} -│ ├── rounds {get|list|watch} +│ ├── rounds {get|list|join|watch} │ ├── sweep [list] │ ├── fees {estimate|history} │ └── listtransactions +├── recovery {list|status|escalate|cancel} — daemon-owned vHTLC recovery rows +├── swap {list|show|receive|pay|resume|watch} — Lightning swap ops (swapruntime build) └── dev — generated low-level RPC (no walletdkrpc) └── daemon — call any daemonrpc.DaemonService method ``` @@ -401,7 +401,8 @@ Send via in-round refresh (waits for next round to commit). | Flag | Type | Description | |------|------|-------------| | `--to` | string[] | Recipient address(es) (bech32m) | -| `--amount` | int64[] | Amount(s) in sats (one per --to) | +| `--pubkey` | string[] | Recipient x-only pubkey hex(es); paired after `--to` entries | +| `--amount` | int64[] | Amount(s) in sats (one per recipient, `--to` then `--pubkey` order) | | `--dry_run` | bool | Validate without submitting | ```bash @@ -410,8 +411,7 @@ darepocli ark send inround --to bcrt1p... --amount 50000 # Multiple recipients darepocli ark send inround \ --to bcrt1p...addr1 --amount 50000 \ - --to bcrt1p...addr2 --amount 30000 \ - + --to bcrt1p...addr2 --amount 30000 # Via JSON input darepocli ark send inround --json '{ @@ -443,40 +443,62 @@ darepocli ark send oor --pubkey --amount 25000 \ ### `send ` (walletdkrpc) Unified send for Lightning invoice (`--offchain`, default) or onchain -leave (`--onchain`). Onchain v1 has whole-VTXO sweep semantics — -selected VTXOs are swept in full, so the actual outflow (echoed in -`actual_amount_sat`) may exceed `--amt`. +send (`--onchain`). Onchain sends are atomic: the destination receives +exactly `--amt` sats and any residual lands back in the wallet as a +change VTXO. Use `--sweep-all` with `--amt=0` to drain every live VTXO +to the destination instead. + +By default `send` blocks until the payment reaches a terminal state +(printing the Lightning preimage on success); pass `--no-wait` to +return as soon as the send is dispatched. Non-interactive callers must +pass `--force` or `--yes` to skip the confirmation prompt. | Flag | Type | Description | |------|------|-------------| | `--offchain` | bool | BOLT-11 dispatch via swap subsystem (default) | -| `--onchain` | bool | Cooperative leave via `LeaveVTXOs` | +| `--onchain` | bool | Atomic onchain send via `SendOnChain` | | `--amt` | uint | Amount in sats (required for onchain unless `--sweep-all`) | -| `--max_fee` | uint | Max fee in sats | +| `--max_fee` | uint | Max swap fee in sats (invoice sends only) | | `--note` | string | Caller-supplied label | | `--sweep-all` | bool | Onchain only: drain wallet; `--amt` must be 0 | +| `--force` / `--yes` | bool | Skip the interactive confirmation prompt | +| `--no-wait` | bool | Return once dispatched instead of blocking to a terminal state | +| `--dry-run` | bool | Prepare and print the preview without dispatching | ```bash -darepocli send lnbcrt... --offchain -darepocli send bcrt1... --onchain --amt 1000 -darepocli send bcrt1... --onchain --sweep-all +darepocli send lnbcrt... --offchain --force +darepocli send bcrt1... --onchain --amt 1000 --force +darepocli send bcrt1... --onchain --sweep-all --force ``` -### `exit` (unilateral exit, formerly `unroll`) +### `exit` (cooperative by default, unilateral with `--force-unroll-ack`) -Start the on-chain recovery process for a VTXO. +Cooperatively exit a VTXO by default; queues the outpoint for +cooperative leave and joins the next round. Unilateral (on-chain) +unroll only starts when `--force-unroll-ack` is set to the exact +string `I_KNOW_WHAT_I_AM_DOING`. `exit` replaces the legacy `unroll` +verb at the user surface. | Flag | Type | Description | |------|------|-------------| | `--outpoint` | string | VTXO outpoint to exit (txid:vout) | +| `--onchain-address` | string | Cooperative leave destination; omitted generates a fresh wallet address | +| `--force-unroll-ack` | string | Must be exactly `I_KNOW_WHAT_I_AM_DOING` to force unilateral unroll | +| `--dry-run` | bool | Validate locally and print the preview without dispatching | ```bash darepocli exit --outpoint +darepocli exit --outpoint --force-unroll-ack I_KNOW_WHAT_I_AM_DOING darepocli exit status --outpoint +darepocli exit summary +darepocli exit plan --outpoint ``` -The job survives daemon restarts; the command only submits the request. -Status enum (`UNROLL_JOB_STATUS_*`) still uses the old "unroll" naming. +`exit status` reports progress for a forced unilateral unroll job (it +survives daemon restarts); `exit summary` aggregates all in-progress +exits; `exit plan` previews backing-wallet funding readiness before +forcing an exit. Status enum (`UNROLL_JOB_STATUS_*`) still uses the old +"unroll" naming. ### `ark listtransactions` @@ -497,8 +519,7 @@ darepocli ark listtransactions --limit 25 darepocli ark listtransactions \ --type oor \ --from 2026-05-01T00:00:00Z \ - --to 2026-05-08T23:59:59Z \ - + --to 2026-05-08T23:59:59Z ``` ### `activity` (walletdkrpc) @@ -510,13 +531,14 @@ The merged wallet activity feed: send / recv / deposit / exit history. | `--pending` | | Only entries still in flight | | `--kind` | | Filter by kind (`send,recv,deposit,exit`); repeatable | | `--limit` | daemon default | Page size | -| `--offset` | `0` | Pagination offset | +| `--cursor` | | Page token from a prior page's `next_cursor` | | `--format` | `table` | Output format (`table`, `expanded`/`x`, `json`) | ```bash darepocli activity darepocli activity --pending --kind send,recv darepocli activity --format json +darepocli activity --cursor darepocli activity inspect ``` @@ -535,7 +557,8 @@ Introspect available CLI commands and their parameters. ```bash darepocli schema -darepocli schema --method ark.vtxos.list +darepocli schema ark.vtxos.list +darepocli schema --all ``` ### `mcp serve` (walletdkrpc) @@ -550,7 +573,7 @@ darepocli mcp serve **Note:** Wallet management tools (`create`, `unlock`, genseed) are intentionally excluded from MCP to prevent sensitive material from transiting the protocol. Use the CLI directly for wallet operations. -`receive_script` is exposed because it only allocates a fresh +`ark.oor.receive` is exposed because it only allocates a fresh wallet-derived receive target and does not reveal seed material. ## Regtest Quickstart @@ -572,8 +595,6 @@ darepod \ --wallet.password_file=/path/to/password_file \ --server.host=localhost:10010 \ --server.insecure \ - --server.localmailboxid=client1 \ - --server.remotemailboxid=server \ --rpc.listenaddr=localhost:10029 # 2b. Alias the CLI for this regtest daemon: plaintext transport (no TLS, @@ -602,16 +623,16 @@ da ark send inround --to bcrt1p... --amount 5000 ``` For per-client manual testing under the `arktest` harness, see -[`MANUAL_TESTING.md`](../../MANUAL_TESTING.md) at the server repo root. +`MANUAL_TESTING.md` at the server (darepo) repo root. ## Password Handling Wallet passwords are never accepted as CLI arguments. The priority order for password resolution: -1. **stdin pipe** -- `echo -n 'pass' | darepocli unlock` -2. **Environment variable** -- `DAREPOD_WALLET_PASSWORD=pass` -3. **Password file** -- `--wallet_password_file=/path/to/file` +1. **Environment variable** -- `DAREPOD_WALLET_PASSWORD=pass` +2. **Password file** -- `--wallet_password_file=/path/to/file` +3. **stdin pipe** -- `echo -n 'pass' | darepocli unlock` 4. **Interactive prompt** -- prompted on TTY if none of the above For production deployments, use the password file approach with @@ -625,7 +646,7 @@ restrictive file permissions (`chmod 600`). | `connection refused` | Daemon not running or wrong `--rpcserver` address | | `wallet not ready` | Run `darepocli unlock` (requires walletdkrpc) or restart the daemon with `--wallet.password_file` | | `wallet already exists` | Wallet was already created; use `unlock` instead | -| `GenSeed: lwwallet mode only` | Switch daemon to `--wallet.type=lwwallet` | +| `GenSeed is only available in lwwallet/btcwallet mode` | Switch daemon to `--wallet.type=lwwallet` or `btcwallet` | | `read macaroon: ... no such file` | The CLI is looking under the wrong data dir/network. Pass `--datadir` / `--network` to match the daemon (see [Authentication](#authentication)), or `--macaroonpath` directly | | `credentials require transport level security` | A macaroon can't ride a plaintext connection. Use TLS (drop `--no-tls`), or add `--no-macaroons` alongside `--no-tls` | | TLS certificate errors | Point `--datadir` / `--network` at the daemon's cert, set `--tlscertpath`, or use `--no-tls --no-macaroons` for regtest | diff --git a/docs/dev_rpc_cli_builder.md b/docs/dev_rpc_cli_builder.md index 44ab37008..2f5b37f27 100644 --- a/docs/dev_rpc_cli_builder.md +++ b/docs/dev_rpc_cli_builder.md @@ -15,8 +15,13 @@ go run ./cmd/darepocli/internal/gen-devrpc The generator reads the linked Go descriptors for: -- `daemonrpc.File_daemon_proto` -- `swapclientrpc.File_swap_client_proto` +- `daemonrpc.File_daemon_proto` (`daemonrpc.DaemonService`, alias `daemon`) +- `swapclientrpc.File_swap_client_proto` (`swapclientrpc.SwapClientService`, + alias `swapclient`) +- `walletdkrpc.File_wallet_proto` (`walletdkrpc.WalletService` and + `WalletInspectionService`, aliases `wallet` and `wallet-inspection`) +- `btcwalletrpc.File_api_proto` (`walletrpc.VersionService` and + `walletrpc.WalletService`, aliases `btcwallet-version` and `btcwallet`) It writes `cmd/darepocli/darepoclicommands/devrpc/registry_generated.go`. That generated file contains only service and method metadata. The runtime @@ -65,14 +70,14 @@ Field handling rules: Flattening is deliberately bounded to singular messages. For example: ```shell -darepocli dev daemon send-oor \ +darepocli dev daemon prepare-oor \ --recipient.address bcrt1... \ - --recipient.amount_sat 1000 \ - --dry_run + --recipient.amount_sat 1000 darepocli dev daemon refresh-vtxos \ --outpoints.outpoints txid:0 \ - --outpoints.outpoints txid:1 + --outpoints.outpoints txid:1 \ + --dry_run ``` Repeated message fields are not flattened because indexed flags would need a diff --git a/docs/development_guidelines.md b/docs/development_guidelines.md index f24813c69..6b3286751 100644 --- a/docs/development_guidelines.md +++ b/docs/development_guidelines.md @@ -14,7 +14,7 @@ eliminate many discussions, the resulting code can still look and feel very differently among different developers. We aim to enforce a few additional rules to unify the look and feel of all code -in `lnd` to help improve the overall readability. +in this codebase to help improve the overall readability. ## Code Documentation and Commenting @@ -86,17 +86,7 @@ comment can make. ## Code Spacing and formatting -Code in general (and Open Source code specifically) is _read_ by developers many -more times during its lifecycle than it is modified. With this fact in mind, the -Golang language was designed for readability (among other goals). -While the enforced formatting of `go fmt` and some best practices already -eliminate many discussions, the resulting code can still look and feel very -differently among different developers. - -We aim to enforce a few additional rules to unify the look and feel of all code -in `lnd` to help improve the overall readability. - -Blocks of code within `lnd` should be segmented into logical stanzas of +Blocks of code within this codebase should be segmented into logical stanzas of operation. Such spacing makes the code easier to follow at a skim, and reduces unnecessary line noise. Coupled with the commenting scheme specified in the [contribution guide](#code-documentation-and-commenting), @@ -519,12 +509,11 @@ your editor with at least the following two settings: 1. Set your tabulator width (also called "tab size") to **8 spaces**. 2. Set a ruler or visual guide at 80 character. -Note that the two above settings are automatically applied in editors that -support the `EditorConfig` scheme (for example GoLand, GitHub, GitLab, -VisualStudio). In addition, specific settings for Visual Studio Code are checked -into the code base as well. +Editors that support the `EditorConfig` scheme (for example GoLand, GitHub, +GitLab, VisualStudio) can pick up these settings automatically from an +`.editorconfig` file if one is added to the project. Other editors (for example Atom, Notepad++, Vim, Emacs and so on) might install -a plugin to understand the rules in the `.editorconfig` file. +a plugin to understand the rules in an `.editorconfig` file. In Vim, you might want to use `set colorcolumn=80`. diff --git a/docs/durable_actor_architecture.md b/docs/durable_actor_architecture.md index 8925e5d22..811aa4e26 100644 --- a/docs/durable_actor_architecture.md +++ b/docs/durable_actor_architecture.md @@ -99,7 +99,7 @@ sequenceDiagram A->>TX: Commit Note over A,TX: Transaction Complete - loop Poll Interval (100ms) + loop Poll Interval (1s) P->>OB: ClaimOutboxBatch() OB-->>P: Pending messages end @@ -132,12 +132,13 @@ If the transaction commits, the message is guaranteed to be delivered ```go type OutboxPublisherConfig struct { - Store DeliveryStore // Persistence layer - Codec *MessageCodec // Message serialization - System SystemContext // Actor discovery via ServiceKey - PollInterval time.Duration // Default: 100ms - BatchSize int // Default: 100 - MaxDeliveryAttempts int // Default: 10 + Store DeliveryStore // Persistence layer + Codec *MessageCodec // Message serialization + System SystemContext // Actor discovery via ServiceKey + PollInterval time.Duration // Default: 1s (fallback; commits wake immediately) + BatchSize int // Default: 100 + MaxDeliveryAttempts int // Default: 10 + ClaimDuration time.Duration // Default: 30s } ``` @@ -284,9 +285,10 @@ delegates message handling to your `ActorBehavior` implementation. handles serialization via `MessageCodec` and yields `Delivery` objects that wrap messages with lease operations. -**Persistence Layer**: `DeliveryStore` is the interface; `ActorDeliveryStore` is -the SQLite implementation. For transactional FSM updates, use `TxAwareActorDeliveryStore` -which wraps message processing in a database transaction. +**Persistence Layer**: `DeliveryStore` is the interface; `actordelivery.Store` +is the SQLite implementation. For transactional FSM updates, use +`TxAwareActorDeliveryStore` which wraps message processing in a database +transaction. **CDC Layer**: `OutboxPublisher` runs as a background service, polling the outbox table and delivering messages to target actors via the Discovery Layer. @@ -318,7 +320,7 @@ flowchart TB subgraph "Persistence Layer" DS[DeliveryStore] - ADS[ActorDeliveryStore] + ADS[actordelivery.Store] TADS[TxAwareActorDeliveryStore] DS -.->|interface| ADS @@ -371,7 +373,7 @@ flowchart TB | `DurableMailbox` | Message queue interface, lease-based iteration, priority ordering | | `Delivery` | Message wrapper with lease operations (Ack/Nack/Extend) | | `DeliveryStore` | Persistence interface for all mailbox operations | -| `ActorDeliveryStore` | SQLite implementation of DeliveryStore | +| `actordelivery.Store` | SQLite implementation of DeliveryStore | | `TxAwareActorDeliveryStore` | Adds transaction support for atomic FSM updates | | `OutboxPublisher` | Background service draining outbox, delivering to targets | | `MessageCodec` | TLV serialization/deserialization with type dispatch | @@ -596,25 +598,23 @@ flowchart TD Messages that were leased but not acknowledged before crash are automatically redelivered. The timing depends on when the lease expires: -1. **Lease Expiry**: A background job (or the actor itself on startup) runs - `ExpireLeases()` to clear stale leases. This sets `lease_token = NULL` and - `lease_until = NULL` for all messages where `lease_until < now`. +1. **Lease Expiry**: `LeaseNextMessage()` treats a row as eligible once + `lease_until < now`, so an expired lease is reclaimed atomically by the + same query that claims the next message - no separate clearing step is + required for redelivery to proceed. `ExpireLeases()` is available as a + standalone maintenance operation (used in tests/tooling) that explicitly + clears `lease_token`/`lease_until` for stale rows. -2. **Message Available**: Once the lease is cleared, the message's `available_at` - determines when it can be picked up. Messages typically become immediately - available since `available_at` was set at original enqueue time. +2. **Redelivery**: The restarted actor's `LeaseNextMessage()` poll picks up the + message once its `lease_until` has passed. The `attempts` counter is + preserved, so the message won't be retried forever if it keeps failing. -3. **Redelivery**: The restarted actor's `LeaseNextMessage()` poll picks up the - message. The `attempts` counter is preserved, so the message won't be retried - forever if it keeps failing. - -4. **Deduplication**: Before executing `Receive()`, the actor checks +3. **Deduplication**: Before executing `Receive()`, the actor checks `IsProcessed(message_id)`. If the message was processed before crash (but ack was lost), it's skipped and immediately acked. **Default Lease Duration**: 30 seconds. If an actor crashes, its leased messages -become available for redelivery after at most 30 seconds (plus `ExpireLeases()` -poll interval). +become available for redelivery after at most 30 seconds. ```mermaid flowchart LR @@ -624,7 +624,7 @@ flowchart LR end subgraph "Recovery" - D[ExpireLeases runs] --> E[Message available again] + D[lease_until passes] --> E[Message available again] E --> F[Actor restarts] F --> G[Lease same message] G --> H{IsProcessed?} diff --git a/docs/durable_actor_quickstart.md b/docs/durable_actor_quickstart.md index d8342126f..b5913d3d5 100644 --- a/docs/durable_actor_quickstart.md +++ b/docs/durable_actor_quickstart.md @@ -215,8 +215,9 @@ codec.MustRegister(actor.AskResponseMsgType, func() actor.TLVMessage { ### 4. Create and Start the Actor -**What**: Create a `DurableActorConfig` struct with your actor's ID, behavior, -store, and codec, then instantiate and start the actor. +**What**: Build a `DurableActorConfig` with your actor's ID, behavior, store, +and codec via `DefaultDurableActorConfig`, then instantiate and start the +actor. **Why**: The config wires together all the pieces: YOUR behavior logic, the persistence layer (store), and the serialization layer (codec). The actor ID @@ -224,12 +225,13 @@ is used as the mailbox identifier in the database, so it must be unique and stable across restarts. **How**: -1. Create a `DurableActorConfig` with: - - `ID`: Unique identifier for this actor (used as `mailbox_id` in the database) - - `Behavior`: Your `ActorBehavior` implementation - - `Store`: An `ActorDeliveryStore` instance (usually from the `db` package) - - `Codec`: Your `MessageCodec` with all message types registered -2. Call `NewDurableActor(cfg)` to create the actor +1. Call `DefaultDurableActorConfig(id, behavior, store, codec)` with: + - `id`: Unique identifier for this actor (used as `mailbox_id` in the database) + - `behavior`: Your `ActorBehavior` implementation + - `store`: A `DeliveryStore` instance (e.g. from the `db/actordelivery` package) + - `codec`: Your `MessageCodec` with all message types registered +2. Call `NewDurableActor(cfg)` to create the actor - it returns a + `fn.Result[*DurableActor[M, R]]`, so unwrap it with `.Unpack()` 3. Call `Start()` to begin processing messages The actor ID is particularly important: it's how the system knows where to @@ -237,14 +239,14 @@ deliver messages and where to find your checkpoints after a restart. Use a descriptive, stable ID like `"round-actor"` or `"wallet-actor-{wallet_id}"`. ```go -cfg := actor.DurableActorConfig[MyMessage, MyResult]{ - ID: "my-actor-1", - Behavior: &MyBehavior{}, - Store: store, // ActorDeliveryStore from db package - Codec: codec, -} +cfg := actor.DefaultDurableActorConfig( + "my-actor-1", &MyBehavior{}, store, codec, +) -myActor := actor.NewDurableActor(cfg) +myActor, err := actor.NewDurableActor(cfg).Unpack() +if err != nil { + return err +} myActor.Start() ``` @@ -433,12 +435,9 @@ flowchart TD 4. **Wire up Store and Codec** in config: ```go - cfg := actor.DurableActorConfig[M, R]{ - ID: "actor-id", - Behavior: behavior, - Store: store, // ActorDeliveryStore - Codec: codec, - } + cfg := actor.DefaultDurableActorConfig( + "actor-id", behavior, store, codec, // store is a DeliveryStore + ) ``` 5. **Test** crash recovery and redelivery scenarios @@ -717,14 +716,14 @@ func (a *MyActor) handleAskResponse( codec := actor.NewMessageCodec() codec.MustRegister(typeID, func() actor.TLVMessage { return &MyMsg{} }) -cfg := actor.DurableActorConfig[MyMsg, MyResult]{ - ID: "actor-id", - Behavior: &MyBehavior{}, - Store: store, - Codec: codec, -} +cfg := actor.DefaultDurableActorConfig( + "actor-id", &MyBehavior{}, store, codec, +) -myActor := actor.NewDurableActor(cfg) +myActor, err := actor.NewDurableActor(cfg).Unpack() +if err != nil { + return err +} myActor.Start() ``` diff --git a/docs/fee-change-model.md b/docs/fee-change-model.md index a19ec040c..7dc62e6e0 100644 --- a/docs/fee-change-model.md +++ b/docs/fee-change-model.md @@ -8,7 +8,7 @@ It is the source-of-truth narrative for the implementation in: - [`client/round/transitions.go`](../round/transitions.go) — `designateChangeMarker`, `validateQuoteEchoes`, `evaluateQuote`. -- [`rounds/seal_time_fee_builder.go`](../../rounds/seal_time_fee_builder.go) — +- `rounds/seal_time_fee_builder.go` (server/darepo repo) — `resolveChangeDesignation`, `computeSealTimeQuotes`, `quoteForClient`. - [`client/rpc/roundpb/round.proto`](../rpc/roundpb/round.proto) — @@ -35,7 +35,7 @@ is wrong. intent's `target_amount_sat` for every other output. 4. If the client forgets to designate a change output, the FSM normalizes the intent at the - `PendingRoundAssembly → IntentSent` boundary via + `PendingRoundAssembly → IntentSentState` boundary via `designateChangeMarker`. The wire intent is therefore *always* well-formed by the time it leaves the client. @@ -63,6 +63,7 @@ overwrites it at seal time with the real residual. message VTXORequest { int64 target_amount_sat = 1; // hint for non-change outputs bool is_change = 4; // designates this as the residual slot + bool fixed_amount = 5; // disables the single-output implicit-change exception // ... policy template + signing key omitted ... } @@ -91,8 +92,8 @@ message LeaveQuote { message JoinRoundQuote { string round_id = 1; - bytes quote_id = 2; // hash(round_id || seal_pass || client_id) - uint32 seal_pass = 3; + bytes quote_id = 2; // hash(round_id || seal_pass_number || client_id) + uint32 seal_pass_number = 3; repeated VTXOQuote vtxo_quotes = 4; repeated LeaveQuote leave_quotes = 5; int64 operator_fee_sat = 6; @@ -171,7 +172,10 @@ quote: server fills it. - The implicit-change case (`totalOutputs == 1`) skips the amount-equality check entirely; the lone output is server-stamped - by definition. + by definition — unless that lone `VTXORequest` sets + `fixed_amount=true`, which disables the exception and re-enables + the amount check (a fixed-amount single output must carry its own + change leg to pay fees). A mismatch fails the FSM with a `QuoteRejected` event and emits a `JoinRoundReject` outbox echoing the `quote_id`. @@ -402,7 +406,7 @@ and only describe how the mapping is intended to land. VTXOs are submitted; auto-stamped first VTXO absorbs the fee. - `darepocli ark vtxos refresh --outpoint ` — **scenario 5**. Implicit change on the single-output intent. -- `darepocli ark vtxos leave --address --amount ` — +- `darepocli ark vtxos leave --outpoint --address ` — **scenario 8** when a single VTXO covers the leave; **scenario 9** with a `--keep ` flag *(future)* that adds a self-VTXO leg. - `darepocli ark board` — **scenario 1** for a single receive script; diff --git a/docs/fee_ledger.md b/docs/fee_ledger.md index 0484a1ac6..826e9f54c 100644 --- a/docs/fee_ledger.md +++ b/docs/fee_ledger.md @@ -130,9 +130,9 @@ Event 1 (wallet_utxo_created): so crediting increases it). The accompanying `wallet_utxo_log` audit row lives in a -separate table (migration `000007_utxo_audit_log`); it tracks -the per-UTXO on-chain state machine and is out of scope for -double-entry accounting. +separate table (also seeded by migration `000006_accounting`); +it tracks the per-UTXO on-chain state machine and is out of +scope for double-entry accounting. ### Boarding round @@ -237,10 +237,10 @@ Structurally parallel to the in-round case, but keyed by `SessionID` instead of `RoundID`. ``` -emitter (sender side): oor.oorDurableBehavior.emitVTXOSent - (oor/actor.go, on FinalizeAcceptedEvent) -emitter (recipient side): oor.oorDurableBehavior.emitVTXOsReceived - (in notifyMaterializedVTXOs) +emitter (sender side): oor.sessionBehavior.queueVTXOSent + (oor/session_actor_handlers.go, on FinalizeAcceptedEvent) +emitter (recipient side): oor.sessionBehavior.queueVTXOsReceived + (in notifyMaterialized) Sender event (vtxo_sent): debit transfers_out += amount @@ -295,7 +295,7 @@ item. | wallet | `wallet/boarding_sweep_actor.go` | `emitSweepConfirmedLedger` | one `BoardingSweepConfirmedMsg` per confirmed boarding sweep | | round | `round/actor.go` | `emitVTXOsReceived` → `emitOwnedVTXOLedgerEntry` | `VTXOReceivedMsg` (all sources), `VTXOSentMsg` (refresh pair) | | round | `round/actor.go` | `emitRoundFee` | `FeePaidMsg` (`boarding` or `refresh`) | -| oor | `oor/actor.go` | `emitVTXOSent` / `emitVTXOsReceived` | `VTXOSentMsg` (session-keyed) / `VTXOReceivedMsg{Source=SourceOOR}` | +| oor | `oor/session_actor_handlers.go` | `queueVTXOSent` / `queueVTXOsReceived` | `VTXOSentMsg` (session-keyed) / `VTXOReceivedMsg{Source=SourceOOR}` | | unroll | `unroll/actor.go` | `emitExitCostIfCompleted` | `ExitCostMsg` after final sweep confirmation | The round actor's emission path carries the most complexity diff --git a/docs/index.md b/docs/index.md index 877dfe7eb..09428db05 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,6 +27,8 @@ into specific topics below. | [mailbox_durable_actor_layer.md](mailbox_durable_actor_layer.md) | The durable mailbox and durable actor: leases, dedup, transactional outbox, DurableAsk, and the classic vs. Read/Commit (`TxBehavior`/`Exec[S]`) execution paths | | [mailbox_transport_serverconn_clientconn.md](mailbox_transport_serverconn_clientconn.md) | The RPC-over-mailbox transport relating this client's `serverconn` to the operator's `clientconn`: envelope, edge API, shared `mailbox/conn` primitives, ack watermark, identity/auth/liveness, and the wire contract | | [RPC_MAILBOX_CONTRACT.md](RPC_MAILBOX_CONTRACT.md) | Envelope semantics, at-least-once delivery, ack watermarks | +| [credit_durable_actor_design.md](credit_durable_actor_design.md) | Credit subsystem durable-actor design: supervisor + per-operation actors driving fault-tolerant sub-dust pay, credit-receive, and redeem flows against the authoritative server ledger | +| [walletdk_mobile.md](walletdk_mobile.md) | gomobile-safe `sdk/walletdk/mobile` facade: drives an embedded in-process `darepod` wallet from Android/iOS over the private bufconn transport (bytes-out API, no daemon binary) | ## Development diff --git a/docs/mailbox_architecture.md b/docs/mailbox_architecture.md index b71e6e7cf..270396ed7 100644 --- a/docs/mailbox_architecture.md +++ b/docs/mailbox_architecture.md @@ -516,13 +516,17 @@ flowchart TB UF2 -.->|"RegisterWaiter / AwaitRPC"| RR ``` -The actor implements `ActorBehavior[ServerConnMsg, ServerConnResp]`. The +The actor implements `TxBehavior[ServerConnMsg, ServerConnResp, egressTx]`, +the Read/Commit path described in +[`mailbox_durable_actor_layer.md`](mailbox_durable_actor_layer.md). The `Receive` method dispatches by message type: - `SendClientEventRequest` → `handleSendClientEvent` (converts to proto, builds envelope, calls `Edge.Send`). - `SendRPCRequest` → `handleSendRPCRequest` (sends pre-built envelope via `Edge.Send`). +- `SendUnaryRequest` / `DurableUnaryQuery` → `handleSendUnaryRequest` (durable, + correlated unary requests such as proof-gated indexer queries). Source: `serverconn/actor.go` @@ -614,8 +618,11 @@ Each cycle: envelopes with `next_cursor`. 3. **Dispatch phase**: Iterate envelopes. Route each by `RpcMeta.Kind`: - - `KIND_RESPONSE`: Deliver to `ResponseRegistry` for unary RPC waiters. - Not durable — the response is consumed immediately. + - `KIND_RESPONSE`: Deliver to `ResponseRegistry` when a live unary waiter is + registered for the correlation ID — consumed immediately, no durability + needed. When no waiter is registered (e.g. the caller's context expired), + fall back to the `EnvelopeDispatcher` dispatch table like an ordinary + event, so actor-driven unary flows still observe the response durably. - `KIND_REQUEST` / `KIND_EVENT`: Look up the `EnvelopeDispatcher` by `(Service, Method)` in the dispatch table. The dispatcher unmarshals the body, adapts it to an actor message, and calls `Tell` on the target @@ -624,6 +631,13 @@ Each cycle: 4. **Advance and checkpoint**: `AdvanceDispatch(nextCursor)` updates the watermark. `saveCheckpoint` persists the state. The loop restarts. +When the delivery store implements `TxAwareDeliveryStore`, `runFoldedDispatch` +folds the dispatch phase and the checkpoint save into one database +transaction, and a pending ack advance rides along with the next dispatch +checkpoint (or an idle-loop flush) rather than committing on its own. This is +the path a production store takes; a non-transactional store falls back to +the phase-by-phase sequence above. + **Partial failure**: If dispatch fails mid-batch (e.g., the target actor's store is down), the loop advances state only past the last successfully dispatched envelope. The failed envelope will be re-pulled on the next iteration. @@ -733,8 +747,8 @@ type Runtime struct { **`NewRuntime(cfg)`**: Validates required fields (Edge, Store, mailbox IDs). Creates `ServerConnectionActor`, wraps it in a `DurableActor` using -`DefaultDurableActorConfig`, and creates `UnaryFacade`. The durable actor ID -is `"serverconn-" + localMailboxID`. +`DefaultDurableTxActorConfig` (the Read/Commit path), and creates +`UnaryFacade`. The durable actor ID is `"serverconn-" + localMailboxID`. **`Start(ctx)`**: Starts the `DurableActor` (begins processing egress). Starts ingress via `connector.StartIngress(ctx)` (loads ack checkpoint, launches diff --git a/docs/mailbox_durable_actor_layer.md b/docs/mailbox_durable_actor_layer.md index e55634f22..97cbe4156 100644 --- a/docs/mailbox_durable_actor_layer.md +++ b/docs/mailbox_durable_actor_layer.md @@ -348,6 +348,12 @@ type Exec[S any] interface { // Read runs fn inside a short read-only snapshot. No writer lock. Read(ctx context.Context, fn func(ctx context.Context, store S) error) error + // Stage runs fn inside one short, lease-fenced writer transaction with + // no dedup mark and no ack. Use it to durably advance state BEFORE the + // IO when a persist-before-effect invariant matters; a Stage write is + // its own atomic unit, not atomic with the eventual Commit. + Stage(ctx context.Context, fn func(ctx context.Context, store S) error) error + // Commit runs fn inside one short writer transaction, then folds in // the lease-fenced ack and the dedup mark. Commit(ctx context.Context, fn func(ctx context.Context, store S) error) error @@ -355,7 +361,9 @@ type Exec[S any] interface { ``` A handler reads its inputs, drops the transaction, does the IO, and then commits -the result. +the result. A behavior that must durably record state before the IO can insert +a lease-fenced `Stage` write in between; see `credit/op_actor.go` for a worked +example. ```mermaid sequenceDiagram diff --git a/docs/policy_arkscript_review_guide.md b/docs/policy_arkscript_review_guide.md index 76c43054d..d6b0b4eda 100644 --- a/docs/policy_arkscript_review_guide.md +++ b/docs/policy_arkscript_review_guide.md @@ -299,8 +299,8 @@ policy: ```go // Server-side: OOR actor materializes the vHTLC output recipient := RecipientOutput{ - Amount: 100_000, - PolicyTemplate: vhtlcPolicy.Encode(), // semantic, not raw pkScript + Value: 100_000, + VTXOPolicyTemplate: vhtlcPolicy.Template.Encode(), // semantic, not raw pkScript } ``` @@ -325,9 +325,11 @@ claimPath, _ := vhtlc.ClaimPath(preimage) // claimPath.Conditions = [][]byte{preimage} input := TransferInput{ - Outpoint: vhtlcOutpoint, - Amount: 100_000, - VTXOPolicyTemplate: vhtlcPolicy.Encode(), + VTXO: &vtxo.Descriptor{ + Outpoint: vhtlcOutpoint, + Amount: 100_000, + }, + VTXOPolicyTemplate: vhtlcPolicy.Template.Encode(), CustomSpend: claimPath, } ``` @@ -335,7 +337,7 @@ input := TransferInput{ **Step 2: Server validates the spend path against the policy.** ```go -// Server-side: oor/policy_helpers.go +// Server-side: oor/transfer_inputs.go (customSpendKeys) template, _ := arkscript.DecodePolicyTemplate(input.VTXOPolicyTemplate) compiled, _ := template.Compile() @@ -374,7 +376,8 @@ witness, _ := claimPath.Witness( **Step 4: Server validates the finalized witness.** ```go -// Server-side: finalize_signature_validation.go +// Server-side: lib/tx/oor/submit_signature_validate.go +// (ValidateFinalizePackageSigned) // For custom spends, the server: // 1. Extracts the witness from the finalized PSBT // 2. Verifies the operator signature is present and unchanged @@ -470,7 +473,7 @@ forward-compatible evolution. |------|-----------------| | `lib/arkscript/tree.go` | Balanced binary tree, merkle proofs, control blocks | | `lib/arkscript/compose.go` | External root composition (Taproot Assets) | -| `lib/arkscript/vtxo.go` | VTXOPolicy convenience wrapper, SpendInfoWithContext | +| `lib/arkscript/vtxo.go` | VTXOPolicy convenience wrapper, CollabSpendInfo/ExitSpendInfo | ### Tests @@ -484,15 +487,18 @@ forward-compatible evolution. ## 8. Migration Boundaries -This PR introduces the `lib/arkscript` foundation package only. It does **not** -delete `lib/scripts/` or change DB/wire formats — those changes live in the -follow-up integration PRs. - -The golden test vectors in `golden_test.go` verify that the new `arkscript` -code produces **byte-identical** output keys, scripts, and control blocks to -the existing `lib/scripts` implementation. This confirms that the new package -is a drop-in replacement — existing on-chain outputs remain spendable once the -integration layer is wired up. +This PR introduced the `lib/arkscript` foundation package alongside the +legacy `lib/scripts` package, without changing DB/wire formats up front — +those changes shipped in the follow-up integration PRs. `lib/scripts` has +since been removed entirely: checkpoint, forfeit, and OOR transaction +construction all build their taproot artifacts through `lib/arkscript` now. + +The golden test vectors in `golden_test.go` were originally generated by +comparing against the `lib/scripts` implementation, proving **byte-identical** +output keys, scripts, and control blocks and confirming that the new package +was a drop-in replacement. Now that `lib/scripts` is gone, `golden_test.go` +pins those vectors as a frozen regression suite — they must not change unless +the VTXO output format is intentionally changing (a breaking change). The key improvement is that the checkpoint PSBT can now carry the tap-tree metadata directly (via the Ark-specific tap tree encoding), making resume diff --git a/docs/sdk_layered_architecture.md b/docs/sdk_layered_architecture.md index a7e5835ab..acf1427ec 100644 --- a/docs/sdk_layered_architecture.md +++ b/docs/sdk_layered_architecture.md @@ -199,13 +199,13 @@ walks through several checkpoints — listener open, gRPC accepting RPCs, wallet unlocked or initialized, round actor initialized, mailbox transport connected, operator terms cached — and callers need a way to observe them. Today `GetInfo` is the caller-facing -readiness surface: `WalletReady == true` means wallet-dependent -RPCs are expected to work, `ServerConnected == true` is the current -best-effort signal that mailbox ingress is running, and -`ServerInfo != nil` means operator terms have been fetched and -cached. Round-oriented -callers should wait for all three before issuing round-sensitive -operations. `ServerInfo` is intentionally nullable because operator +readiness surface: `Info.WalletReady()` returning `true` means +wallet-dependent RPCs are expected to work, `ServerConnected == +true` is the current best-effort signal that mailbox ingress is +running, and `ServerInfo != nil` means operator terms have been +fetched and cached. Round-oriented callers should wait for all +three before issuing round-sensitive operations. `ServerInfo` is +intentionally nullable because operator bootstrap happens after the daemon process starts accepting RPCs, and the current daemon refreshes that snapshot only during bootstrap, so it remains the latest known terms for the current session until diff --git a/docs/structured-logging.md b/docs/structured-logging.md index ad9de0522..cc8e4d6c9 100644 --- a/docs/structured-logging.md +++ b/docs/structured-logging.md @@ -8,10 +8,12 @@ messages. **Method signature:** 1. First parameter: `context.Context` 2. Second parameter: static string (no `fmt.Sprintf`) -3. Remaining parameters: key-value pairs +3. For `WarnS`/`ErrorS`/`CriticalS`: third parameter is the `error` being + logged (may be `nil`); `InfoS`/`DebugS`/`TraceS` have no `error` param. +4. Remaining parameters: key-value pairs **Key-value helpers:** `slog.Int()`, `slog.String()`, `btclog.Fmt()`, -`btclog.Hex()`, `btclog.Err()`, etc. +`btclog.Hex()`, etc. ## Example diff --git a/docs/swap_background_execution.md b/docs/swap_background_execution.md index 5c9a05f08..57cb2fbaa 100644 --- a/docs/swap_background_execution.md +++ b/docs/swap_background_execution.md @@ -79,7 +79,7 @@ A stub command keeps swap support discoverable in non-swapruntime builds without linking a second CLI-side swap runtime into ordinary binaries. The tagged CLI command should depend on the generated swap subserver client, -not `sdk/swaps`. It should be structurally similar to `cmd_unroll.go`: parse +not `sdk/swaps`. It should be structurally similar to `cmd_getinfo.go`: parse flags, build a protobuf request, call the daemon-hosted service, and render the protobuf response. Any richer Go models can live in `sdk/ark` or a future WalletDK-facing package, but the CLI should not become a second swap runtime. @@ -239,8 +239,9 @@ make install-swapruntime make unit-swapruntime ``` -These targets are thin wrappers around `tags="swapruntime"` so local developer -and integration-test environments do not need to remember the exact build tag. +These targets are thin wrappers around `tags="swapruntime"` (`unit-swapruntime` +also adds `walletdkrpc`) so local developer and integration-test environments +do not need to remember the exact build tag. Implementation registration should look like the existing `DaemonService` registration path in `darepod/server.go`, but through a programmatic registrar @@ -326,19 +327,17 @@ The CLI should not open the swap DB, construct `sdk/swaps.SwapClient`, dial Recommended commands: -- `swap pay --invoice ... [--max-fee ...] [--wait]` -- `swap receive --amount ... [--wait]` -- `swap list [--pending] [--verbose]` -- `swap show ` -- `swap watch [payment_hash]` +- `swap pay --invoice ... [--maxfee ...]` +- `swap receive --amount ...` +- `swap list [--pending]` +- `swap show [payment_hash]` +- `swap watch [--pending] [--include-existing]` -`--wait` should subscribe or poll until terminal state, but it is optional. -Exiting the CLI must not stop the swap. +Exiting the CLI must not stop the swap; `pay` and `receive` return as soon as +the daemon has durably persisted the session. -If `SubscribeSwaps` lands in the first slice, `--wait` and `swap watch` should -use the stream. If it is deferred, they should poll `GetSwap` with a small -interval and stop once a terminal state appears. The polling fallback should -live in the CLI, not in the daemon executor. +`SubscribeSwaps` landed in the first slice, so `swap watch` streams live +updates directly instead of falling back to polling `GetSwap`. Swap storage and swap-server settings belong in `darepod` config. That keeps daemon startup as the single place where background worker dependencies are @@ -348,11 +347,12 @@ configured, and keeps the CLI focused on the daemon RPC contract. The tagged daemon config should include: -- `swap.enable` or implicit enable when the tag is built and config is set; -- `swap.server_addr`; -- `swap.server_tls_cert`; -- `swap.server_insecure` for regtest/dev; -- `swap.db_path`; +- implicit enable when the tag is built and config is set, rather than a + separate `swap.enable` field; +- `swap.serveraddress`; +- `swap.servertlscertpath`; +- `swap.serverinsecure` for regtest/dev; +- `swap.databasefilename`; - receive auth key policy. Receive auth material is the one real design question. The current CLI invoice diff --git a/docs/swap_system.md b/docs/swap_system.md index 2349164d9..241f2af66 100644 --- a/docs/swap_system.md +++ b/docs/swap_system.md @@ -101,8 +101,8 @@ Funding and claiming a vHTLC happen through **OOR** (out-of-round) transfers: Ark's mechanism for moving a virtual output between owners without waiting for the next round. When *we* fund a vHTLC we send an OOR transfer to its pkScript; when we claim one we send an OOR transfer that spends its Claim leaf with the -preimage. The daemon's `SendOORWithPolicy` and `SendOORWithCustomInputs` do this -work. +preimage. The daemon's `SendOORWithPolicyDetails` and `SendOORWithCustomInputs` +do this work. --- @@ -147,7 +147,7 @@ wants to be paid over Lightning and end up with the value as an Ark output. ### 3.1 What the client sets up The receive session begins in -[`prepareInvoice`](../sdk/swaps/out_swap.go) (out_swap.go:591). It does four +[`prepareInvoice`](../sdk/swaps/out_swap.go) (out_swap.go:623). It does four things that matter: 1. Fetches the client's own identity key (`IdentityPubKey`) and the operator's @@ -155,9 +155,9 @@ things that matter: 2. Generates a fresh preimage and its hash. The hash is the invoice's payment hash and the vHTLC's hashlock. 3. Allocates the **claim destination** — an ordinary OOR receive script - (`AllocateReceiveScript`, out_swap.go:620) — and registers it with the + (`AllocateReceiveScript`, out_swap.go:650) — and registers it with the indexer. This is where the value lands after the vHTLC is claimed. -4. Calls the swap server's **`RequestChannelId`** RPC (out_swap.go:641), +4. Calls the swap server's **`RequestChannelId`** RPC (out_swap.go:671), handing over `(client_vhtlc_pubkey, payment_hash, amount_msat)`. The server allocates a virtual short-channel-id derived from the client key and payment hash, and returns a **route hint**. The client embeds that hint in the @@ -187,7 +187,7 @@ sits at a NUMS-keyed address that belongs to no wallet. ### 3.3 What the client does with the event -[`acceptOutSwapHtlcEvent`](../sdk/swaps/out_swap.go) (out_swap.go:907) validates +[`acceptOutSwapHtlcEvent`](../sdk/swaps/out_swap.go) (out_swap.go:996) validates the event (payment hash matches, amount matches, onion decrypts), then builds the policy and derives the script: @@ -202,7 +202,7 @@ policy, _ := arkscript.NewVHTLCPolicy(arkscript.VHTLCOpts{ UnilateralRefundDelay: event.VHTLCConfig.UnilateralRefundDelay, UnilateralRefundWithoutReceiverDelay: event.VHTLCConfig.UnilateralRefundWithoutReceiverDelay, }) -pkScript, _ := policy.PkScript() // out_swap.go:963 +pkScript, _ := policy.PkScript() // out_swap.go:1078 // ... persisted into s.vhtlcPkScript, FSM -> ReceiveStateHTLCEventAccepted ``` @@ -217,7 +217,7 @@ Having derived the pkScript, the client must observe that the server actually funded it. It cannot look in its own wallet, because the output is not there (§3.2). Its only authoritative witness is the operator's **indexer**. -[`waitForVHTLC`](../sdk/swaps/out_swap.go) (out_swap.go:1659) polls in a loop: +[`waitForVHTLC`](../sdk/swaps/out_swap.go) (out_swap.go:1915) polls in a loop: ``` waitForVHTLC @@ -280,7 +280,7 @@ Lightning invoice paid. ### 4.1 The negotiation -The client calls the swap server's **`CreateInSwap`** RPC (swap.proto:33) with +The client calls the swap server's **`CreateInSwap`** RPC (swap.proto:74) with the invoice, a fee ceiling, and its vHTLC pubkey. The server replies with a `CreateInSwapResponse`: the payment hash, the amount and fee, the server's pubkey, the `VHTLCConfig`, a deadline, and — decisively — a `settlement_type` @@ -289,11 +289,12 @@ pubkey, the `VHTLCConfig`, a deadline, and — decisively — a `settlement_type ### 4.2 The client funds the vHTLC Here is the mirror image of the receive flow. In -[`fundOrAdoptVHTLC`](../sdk/swaps/in_swap.go) (in_swap.go:659), **the client -funds the vHTLC itself**, via `SendOORWithPolicy`, with itself as `Sender`, the -swap server as `Receiver`, and the operator as `Server`. Because the client's -own daemon performs the OOR transfer, the funded output **lands in the client's -local VTXO store**. The client can see its own funding without asking anyone. +[`fundOrAdoptVHTLC`](../sdk/swaps/in_swap.go) (in_swap.go:718), **the client +funds the vHTLC itself**, via `SendOORWithPolicyDetails`, with itself as +`Sender`, the swap server as `Receiver`, and the operator as `Server`. Because +the client's own daemon performs the OOR transfer, the funded output **lands in +the client's local VTXO store**. The client can see its own funding without +asking anyone. The swap server, watching the vHTLC appear, pays the Lightning invoice, learns the preimage from the Lightning settlement, and claims the vHTLC's Claim leaf — @@ -337,7 +338,7 @@ If the Lightning payment fails, the client does not have to go on-chain to get its money back. It walks a **refund ladder** whose first rungs all settle *off-chain* via OOR: an immediate operator-co-signed cooperative refund (the **Refund** leaf, leaf 2) if the server authorises one through -**`AuthorizeInSwapRefund`** (swap.proto:37); failing that, a CLTV-gated +**`AuthorizeInSwapRefund`** (swap.proto:95); failing that, a CLTV-gated cooperative refund the client can take with the operator *without the swap server's cooperation* once the refund locktime elapses (the **RefundWithoutReceiver** leaf, leaf 3); and only if cooperation breaks down @@ -384,9 +385,9 @@ The client does not wait until a swap is in trouble to think about recovery. The instant a vHTLC is funded — *before* it commits to the risky half of the flow — it stores a dormant **recovery row** in the daemon that already knows how to unilaterally exit this exact output. A receive session arms a *claim* recovery -([`ensureReceiveClaimRecoveryArmed`](../sdk/swaps/recovery.go), recovery.go:392); +([`ensureReceiveClaimRecoveryArmed`](../sdk/swaps/recovery.go), recovery.go:394); a pay session arms a *refund-without-receiver* recovery -([`ensurePayRefundRecoveryArmed`](../sdk/swaps/recovery.go), recovery.go:281). +([`ensurePayRefundRecoveryArmed`](../sdk/swaps/recovery.go), recovery.go:283). Each row captures everything the daemon would need to sweep the output alone: the outpoint and amount, all three participant keys, the CLTV refund locktime and the three CSV delays, the destination script, and a fee-rate cap @@ -408,14 +409,14 @@ own: once the **refund locktime** passes, the swap server may take leaf 3 and reclaim its own funding, leaving the client with nothing. So the receive flow treats the locktime as a hard deadline. Before it starts or continues waiting it checks that the locktime is not imminent -([`out_swap.go`](../sdk/swaps/out_swap.go), out_swap.go:1203), keeping a -one-block buffer (`defaultRefundLocktimeBuffer`); if the deadline has arrived it -gives up with `errSwapExpired` rather than chase a vHTLC the server is about to -pull back. +([`ensureReceiveFundingStillPossible`](../sdk/swaps/out_swap.go), +out_swap.go:1387), keeping a one-block buffer (`defaultRefundLocktimeBuffer`); +if the deadline has arrived it gives up with `errSwapExpired` rather than chase +a vHTLC the server is about to pull back. If the cooperative claim keeps failing while the deadline approaches, the client escalates ([`maybeEscalateReceiveClaimRecovery`](../sdk/swaps/recovery.go), -recovery.go:667): the daemon takes over the armed row and spends leaf 4 +recovery.go:669): the daemon takes over the armed row and spends leaf 4 (UnilateralClaim) on-chain, sweeping the output with the preimage after its CSV matures. Because a real deadline looms here, the policy lets **deadline pressure override the ordinary grace period** — there is no point waiting out a one-hour @@ -427,7 +428,7 @@ On a pay, the client funded the vHTLC and wants either a preimage (proof it paid or its money back. §4.4 named the ladder; here is each rung: 1. **Immediate cooperative refund (leaf 2).** - [`tryCooperativeRefund`](../sdk/swaps/in_swap.go) (in_swap.go:1187) asks the + [`tryCooperativeRefund`](../sdk/swaps/in_swap.go) (in_swap.go:1250) asks the swap server to co-sign the three-party Refund leaf through `AuthorizeInSwapRefund`. The server signs only after it has *safely* failed the Lightning payment and holds no preimage, so an "unavailable" answer is not @@ -436,14 +437,14 @@ or its money back. §4.4 named the ladder; here is each rung: before any locktime. 2. **Timeout cooperative refund (leaf 3).** If the server never authorises the immediate refund, the client waits for the CLTV refund locktime to mature - (in_swap.go:1078) and then spends RefundWithoutReceiver as an OOR transfer - (in_swap.go:1098) — *still off-chain*, and now without needing the swap server + (in_swap.go:1141) and then spends RefundWithoutReceiver as an OOR transfer + (in_swap.go:1171) — *still off-chain*, and now without needing the swap server to cooperate at all, only the operator. This is the rung most often misread: the locktime gates an off-chain spend, it does not force a broadcast. 3. **Unilateral exit (leaf 6).** Only if even that OOR cannot get through does the client escalate the armed row ([`maybeEscalatePayRefundRecovery`](../sdk/swaps/recovery.go), - recovery.go:581) and let the daemon sweep leaf 6 on-chain after its CSV. A pay + recovery.go:583) and let the daemon sweep leaf 6 on-chain after its CSV. A pay refund has no absolute deadline the way a receive claim does, so here the grace period is the only automatic trigger. @@ -451,7 +452,7 @@ or its money back. §4.4 named the ladder; here is each rung: Escalation from the off-chain rungs to the on-chain one is deliberately reluctant, because an on-chain exit costs a transaction and a timelock. The -[`RecoveryPolicy`](../sdk/swaps/recovery.go) (recovery.go:118) decides, and its +[`RecoveryPolicy`](../sdk/swaps/recovery.go) (recovery.go:120) decides, and its production default is conservative: - **`AutoEscalate` is off by default.** Out of the box the SDK never unilaterally @@ -459,7 +460,7 @@ production default is conservative: `swapd recovery` CLI (`EscalateVHTLCRecovery`). The armed row waits patiently until told. - With auto-escalation enabled, [`decideRecoveryEscalation`](../sdk/swaps/recovery.go) - (recovery.go:200) escalates only when waiting longer is unsafe or pointless: + (recovery.go:202) escalates only when waiting longer is unsafe or pointless: when the height plus a safety margin (`MinRecoveryMarginBlocks`, 12) reaches a refund-locktime deadline (`deadline_margin`), or when a grace period (`CooperativeFailureGracePeriod`, one hour) has elapsed since cooperation first @@ -474,14 +475,14 @@ production default is conservative: A recovery row moves through a small state machine: it starts **ARMED** (dormant), advances to **UNROLL_STARTED** when the daemon begins the on-chain exit, and ends **COMPLETED**, **FAILED**, or **CANCELLED**. The pivotal predicate -is [`recoveryIsActive`](../sdk/swaps/recovery.go) (recovery.go:741): the moment a +is [`recoveryIsActive`](../sdk/swaps/recovery.go) (recovery.go:743): the moment a row leaves ARMED for unroll, the SDK stops attempting cooperative OOR spends for that vHTLC and simply reconciles against the daemon's progress — once the daemon owns the exit, two parties racing to spend the same output would only collide. The far more common ending is the happy one, where cooperation wins and the lifeboat is never launched. When the off-chain path succeeds, the session calls -[`cancelVHTLCRecovery`](../sdk/swaps/recovery.go) (recovery.go:495) with a reason +[`cancelVHTLCRecovery`](../sdk/swaps/recovery.go) (recovery.go:497) with a reason naming what won — *server claim observed*, *cooperative refund accepted*, *cooperative claim indexed*, and so on — and the armed row retires unused. @@ -526,11 +527,11 @@ recovery row's outcome into that terminal state: - **Pay, money returned.** Whether the client refunded off-chain (leaf 2 or 3) or the daemon completed an on-chain exit (leaf 6), the session lands in - **`Refunded`**. `reconcilePayRefundRecovery` (recovery.go:781) drives + **`Refunded`**. `reconcilePayRefundRecovery` (recovery.go:783) drives `payEventRefunded` when the recovery row reports COMPLETED. - **Receive, money collected.** A cooperative claim *or* a completed unilateral claim recovery both end in **`Completed`** - (`reconcileReceiveClaimRecovery` → `receiveEventCompleted`, recovery.go:847): + (`reconcileReceiveClaimRecovery` → `receiveEventCompleted`, recovery.go:832): from the user's seat, an on-chain sweep that lands the funds is still a successful receive. - **Recovery hit a wall.** A recovery row that reports FAILED parks a pay session @@ -538,7 +539,7 @@ recovery row's outcome into that terminal state: (**`Failed`**), each carrying the daemon's last error. - **The vHTLC was funded with the wrong amount.** This short-circuits the cooperative path entirely: a pay session goes straight to `RefundInitiated` - ([in_swap.go:1484](../sdk/swaps/in_swap.go)), a receive session to `Failed` — + ([in_swap.go:1688](../sdk/swaps/in_swap.go)), a receive session to `Failed` — never `NeedsIntervention`, because a wrong amount is unambiguous, not anomalous. - **`NeedsIntervention` is the "stop and call a human" state**, reserved for genuinely anomalous server behaviour — most notably a vHTLC spent *without* a @@ -562,7 +563,7 @@ turns while they are stopped. The swap server never pushes a "your payment failed" event: `SwapService` (§9) has no status or subscription RPC, only the three request-response calls. The client *discovers* that the server has safely failed a pay by polling `AuthorizeInSwapRefund` -([`tryCooperativeRefund`](../sdk/swaps/in_swap.go), in_swap.go:1187) — a returned +([`tryCooperativeRefund`](../sdk/swaps/in_swap.go), in_swap.go:1250) — a returned co-signature **is** the notification, and the "unavailable" answer from §5.4 simply means "not yet, keep polling." A receive likewise advances only because the client keeps re-querying the indexer and re-attempting the OOR claim. Stop @@ -614,11 +615,11 @@ mailbox event** it receives. The swap mailbox carries a `SwapMailboxEvent` whose - `InArkHtlcEvent` — a same-Ark payment; carries the sender's pubkey directly (no onion), and *may already include the funded `vhtlc_outpoint` and amount*. -[`acceptIncomingVHTLCNotification`](../sdk/swaps/out_swap.go) (out_swap.go:873) +[`acceptIncomingVHTLCNotification`](../sdk/swaps/out_swap.go) (out_swap.go:962) branches on this: `acceptInArkHtlcEvent` for the same-Ark case, the onion path for Lightning. Because the in-Ark sender funds the vHTLC with an OOR transfer that the receiver's own daemon may materialise locally, the receive flow keeps a -fallback — `localLiveVTXOByPkScript` (out_swap.go:1761) — that consults the local +fallback — `localLiveVTXOByPkScript` (out_swap.go:2017) — that consults the local live VTXO set in addition to the remote indexer. Cancellation needs no special case here. An in-Ark swap rides the same six-leaf @@ -719,11 +720,11 @@ flowchart TD ``` - **Receive** keys off the **mailbox event type**: `out_swap_htlc` versus - `in_ark_htlc` (swap.proto:94-130). The Lightning event is onion-shaped and + `in_ark_htlc` (swap.proto:244-259). The Lightning event is onion-shaped and server-funded; the in-Ark event carries the sender's pubkey directly and may already include the funded outpoint. - **Pay** keys off **`settlement_type`** in the `CreateInSwapResponse` - (swap.proto:206-208), where `SETTLEMENT_TYPE_UNSPECIFIED` is treated as + (swap.proto:380), where `SETTLEMENT_TYPE_UNSPECIFIED` is treated as Lightning for backward compatibility. --- diff --git a/docs/testing-guide.md b/docs/testing-guide.md index 81059cbf2..76714e5b5 100644 --- a/docs/testing-guide.md +++ b/docs/testing-guide.md @@ -21,8 +21,8 @@ make unit pkg= case= timeout=5m # Debug with logs make unit log="stdlog trace" pkg= case= -# Integration test -make itest icase= +# System-level end-to-end test +make systest ``` ## Pre-Commit Checklist @@ -36,7 +36,7 @@ Before every commit, run: - Verify structured logging format is correct. - Ensure no log spam. - **No `[ERR]` lines should appear** unless testing error paths. -4. `make itest icase=$icase` — run affected integration tests. +4. `make systest` — run affected system-level end-to-end tests. ## Test Naming diff --git a/docs/walletdk_mobile.md b/docs/walletdk_mobile.md index 16ab07eb6..245f13c95 100644 --- a/docs/walletdk_mobile.md +++ b/docs/walletdk_mobile.md @@ -28,7 +28,7 @@ The package is gated behind three build tags — `mobile`, `walletdkrpc`, and | Shape | Convention | Verbs | |-------|-----------|-------| -| RPC verbs | **JSON `[]byte` in / out** (throwing) | `GetInfo`, `CreateWallet`, `UnlockWallet`, `Balance`, `Deposit`, `Receive`, `PrepareSend`, `SendPrepared`, `List`, `Exit`, `ExitStatus`, `GetExitPlan`, `SweepWallet`, `Status` | +| RPC verbs | **JSON `[]byte` in / out** (throwing) | `GetInfo`, `CreateWallet`, `UnlockWallet`, `OpenWalletFromPasskey`, `Balance`, `Deposit`, `Receive`, `PrepareSend`, `SendPrepared`, `List`, `Exit`, `ExitStatus`, `ExitSummary`, `GetExitPlan`, `SweepWallet`, `Status` | | Streaming | **pull handle** `Subscription{ next() []byte; close() }` | `Subscribe` | | Hot-path scalars | **plain `int64`/`bool`** | `ConfirmedBalanceSat`, `PendingInboundSat`, `WalletReady`, `IsRunning` | @@ -43,10 +43,12 @@ Mind the field names. The verb DTOs in `sdk/walletdk/types.go` carry **no** `json:"…"` tags, so `encoding/json` uses the Go field names verbatim: the wire keys are PascalCase (`Version`, `ConfirmedSat`, `AmountSat`, `IdentityPubKey`), not snake_case. Host models must map those exact names (e.g. `@SerialName` -/ `CodingKeys`), or fields silently decode as zero. The one exception is the -`Start` config, which is a dedicated tagged struct (`config.go`) and is -snake_case (`data_dir`, `wallet_esplora_url`, …). Requests decode into the -matching `walletdk.*Request` DTO, so they follow the same PascalCase rule. +/ `CodingKeys`), or fields silently decode as zero. Two exceptions: the +`Start` config, a dedicated tagged struct (`config.go`) that is snake_case +(`data_dir`, `wallet_esplora_url`, …), and `OpenWalletFromPasskey`, whose +request is a small camelCase-tagged struct (`prfOutput`). Every other request +decodes into the matching `walletdk.*Request` DTO, so it follows the +PascalCase rule. ## Lifecycle diff --git a/docs/walletdkrpc_build.md b/docs/walletdkrpc_build.md index da3231fb3..1cbcb88cc 100644 --- a/docs/walletdkrpc_build.md +++ b/docs/walletdkrpc_build.md @@ -7,7 +7,8 @@ the daemon-managed signer, the cooperative-leave RPC (`LeaveVTXOs`), and the unified ledger surface into one small RPC service. `WalletService` exposes the seven core user verbs (`Create`, `Unlock`, `Send`, `Recv`, `List`, `Balance`, and `Exit`) plus supporting -methods (`Deposit`, `Status`, `ExitStatus`, and `SubscribeWallet`). +methods (`PrepareSend`, `Deposit`, `Status`, `GetExitPlan`, `SweepWallet`, +`ExitStatus`, `ExitSummary`, `SubscribeWallet`, and `InspectActivity`). This document covers how to build and install the daemon and CLI with the wallet RPC subserver enabled, and what surfaces become available once the @@ -18,8 +19,10 @@ binaries are tagged. The wallet RPC subserver lives behind paired build tags: - `walletdkrpc` — registers the wallet RPC gRPC service in the daemon and - enables the top-level `darepocli` wallet verbs (`balance`, `recv`, - `send`, `activity`, `create`, `unlock`, `mcp`). + gives the top-level `darepocli` wallet verbs (`balance`, `recv`, + `send`, `activity`, `create`, `unlock`, `exit`, `wallet-sweep`) and the + `mcp` server's wallet tools live backing instead of an `Unimplemented` + stub. - `swapruntime` — the underlying swap subsystem the wallet RPC layer composes against. Required transitively: building with `walletdkrpc` but without `swapruntime` is a deliberate compile error. @@ -68,7 +71,8 @@ When the daemon is started from a `walletdkrpc`-tagged build: entries to FAILED, and runs a monitor loop that fans normalized updates to `SubscribeWallet` subscribers. - The CLI exposes top-level wallet verbs: `send`, `recv`, `activity`, - `balance`, `create`, `unlock`, and `mcp serve`. Raw transaction / onchain + `balance`, `create`, `unlock`, `exit`, `wallet-sweep`, and `mcp serve`. + Raw transaction / onchain history is available via `ark listtransactions`, the live VTXO set via `ark vtxos list`, and boarding-timeout sweep records via `ark sweep list`. Subscriptions are available from the