Compare commits

...

104 commits

Author SHA1 Message Date
Viktor Torstensson
fba30046eb
Merge pull request #1372 from lightninglabs/frontend-dep-audit
Some checks are pending
CI / frontend tests on macOS-latest (push) Waiting to run
CI / frontend tests on ubuntu-latest (push) Waiting to run
CI / frontend tests on windows-latest (push) Waiting to run
CI / backend build on macOS-latest (push) Waiting to run
CI / backend build on ubuntu-latest (push) Waiting to run
CI / backend build on windows-latest (push) Waiting to run
CI / cross compilation (push) Waiting to run
CI / cross compilation-1 (push) Waiting to run
CI / cross compilation-2 (push) Waiting to run
CI / RPC proto compilation check (push) Waiting to run
CI / check commits (push) Waiting to run
CI / Sqlc check (push) Waiting to run
CI / lint (push) Waiting to run
CI / run unit tests (push) Waiting to run
CI / run unit tests-1 (push) Waiting to run
CI / run unit tests-2 (push) Waiting to run
CI / run unit tests-3 (push) Waiting to run
CI / build itest binaries (push) Waiting to run
CI / integration test (push) Blocked by required conditions
CI / integration test-1 (push) Blocked by required conditions
CI / integration test-2 (push) Blocked by required conditions
CI / check release notes updated (push) Waiting to run
app: update outdated frontend dependencies
2026-08-13 11:51:33 +02:00
jamaljsr
a04ecb750d
ci: run node 24
The frontend job was pinned to node 16, which is old enough that some of
the packages patched in this branch refuse to install there. tar and
esbuild want node 18 or newer, serialize-javascript wants 20.

Node was also only pinned in the frontend job. The backend,
proto-compile-check and itest jobs all reference matrix.node_version
without declaring it in their matrix, so the expression resolved to an
empty string and setup-node fell back to whatever the runner happened to
ship. Two of those run `yarn install --frozen-lockfile`, so they need a
known version too.

Both are fixed by a NODE_VERSION env var, matching how GO_VERSION is
already handled. It replaces the matrix entry rather than feeding it,
because the env context isn't available inside strategy.matrix.
2026-08-11 10:12:44 -05:00
jamaljsr
5ed07dbf71
app: patch vulnerable dev dependencies
Sweeps the build-time dependency tree, which is where most of what
`yarn audit` reports actually lives. This takes the frontend from 419
findings across 42 packages down to 16 across 9.

Most of it is lockfile-only. `yarn upgrade` does not re-resolve
transitive entries in yarn 1, so the affected entries were dropped from
yarn.lock and reinstalled, which picks the newest version each parent's
existing range already allows. That covers the four open dependabot PRs
(shell-quote, websocket-driver, @babel/core and
@babel/plugin-transform-modules-systemjs) along with @babel/helpers,
ajv, async, brace-expansion, form-data, glob, js-yaml, json5, nanoid,
terser and ws.

Re-resolving express to 4.22.2 pulls patched body-parser, cookie,
path-to-regexp and qs with it, and clears the stale minimatch 3.0.4 and
semver 7.0.0 pins at the same time.

browserslist and caniuse-lite had to move too. @babel/core 7.29 emits
'opera_mobile' as a compilation target and the pinned
@babel/helper-compilation-targets 7.17.7 rejects it, which broke the
production build until the whole toolchain was aligned.

storybook goes to 7.6.24 and webpack to 5.109.2, both direct
devDependencies pinned to vulnerable versions.

The `resolutions` additions are for packages whose parent pins a range
with no patched version in it. Each was verified against a build:

- svgo 2.8.3 removes the svgo 1.x subtree that @svgr/plugin-svgo drags
  in. react-scripts sets `svgo: false`, so that plugin never runs, and
  the production bundle is byte for byte identical with and without it.
- resolve-url-loader 5.0.0 is the only way to drop postcss 7. The
  emitted CSS is unchanged, same content hash.
- @tootallnate/once, esbuild, nth-check, serialize-javascript, tar and
  uuid are straightforward version forces.

What is left is either unfixable or would break the build:

- protoc-gen-js pulls got, http-cache-semantics, adm-zip and decompress
  through the unmaintained `download` package. decompress has no
  published fix. None of it runs outside `yarn protos`.
- webpack-dev-server 5 drops the onBeforeSetupMiddleware and
  onAfterSetupMiddleware hooks that react-scripts 5.0.1 calls directly,
  so forcing it would break `yarn start`.
- ip and lodash.pick have no patched version published.
- react-router needs v7, which is a separate migration.
2026-08-11 10:12:44 -05:00
jamaljsr
b6646d7868
app: patch vulnerable production dependencies
Bumps the two production dependencies flagged by `yarn audit` that have
fixes available.

http-proxy-middleware goes to 2.0.10 for the Host-header routing bypass.
It also needs a `resolutions` entry, otherwise yarn keeps a second copy
at 2.0.9 under react-scripts > webpack-dev-server because the existing
`^2.0.0` range is already satisfied.

react-router-dom goes to 6.30.4 for the untrusted-path open redirect. It
is pinned exactly rather than with a caret: @remix-run/router is a direct
dependency at an exact version and react-router-dom pins its own copy
exactly too, so a caret here would let a future patch release install a
second copy of the router and hand <HistoryRouter> a history built by a
different module instance.

That bump pulls in a few related changes:

- react-router 6.4 folded the `history` package into @remix-run/router,
  so the store now imports `createBrowserHistory` from there. Passing our
  own history object is discouraged and would bundle history twice.
- @remix-run/router's type declarations use accessors in interfaces,
  which needs TypeScript 4.3 or newer, so typescript goes to 4.9.5. TS
  4.4 also began typing `catch` variables as `unknown`, so `handleError`
  now takes `unknown` and narrows before reading `.message`, and the five
  sites that read it directly narrow first as well.
- @types/history and @types/react-router-dom are v5-era leftovers that
  now conflict with the types react-router-dom ships itself.
- The new history only accepts one active listener, but RouterStore and
  <HistoryRouter> both need one, so RouterStore subscribes once and fans
  updates out. It also needs `v5Compat` so listeners fire on push() and
  replace() rather than only on back/forward.

reactour still pulls in a vulnerable lodash.pick with no patched version
published. reactour 1.18.0 is the end of the v1 line and v2 is a rewrite,
so that one is left alone for now.
2026-08-11 10:12:44 -05:00
Elle
23433a05ed
Merge pull request #1369 from ViktorT-11/2026-08-configurable-spending-controls
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
accounts: add configurable account spending controls
2026-08-07 15:23:34 -07:00
Viktor Torstensson
6bca24793c
docs: document account operator controls
Document the configurable maximum account payment size and optional
channel balance reservation check, including their defaults, fee
handling, and operational limitations.
2026-08-07 12:27:24 +02:00
Viktor Torstensson
a493b6a3d6
accounts: optionally cap account balances at the node channel balance
Add an opt-in check that prevents the sum of all account balances from
exceeding the node's available local (outbound) channel balance. When
enabled via the new accounts.check-channel-balance config option, the
service rejects balance allocations that would over-provision the node:
creating an account, an administrative credit, or an administrative
balance increase now fails with ErrBalanceReservationExceeded if it
would push the total allocated balance above the node's local channel
balance.

Note that the total channel balance may still decrease below the already
allocated account balance. This can occur if the node operator decreases
the total channel balance through non-account related activity.

The check is a no-op by default to preserve the historical behaviour
where the operator manages over-provisioning themselves (accounts can
legitimately be created before channels are funded). Invoice-driven
credits are unaffected, as they are backed by real inbound payments.
2026-08-07 12:26:29 +02:00
Viktor Torstensson
ae74392433
accounts: add a configurable maximum account payment size
Add an opt-in cap on the total amount a single account payment may
debit, including routing fees, enforced at the account interceptor for
both SendPaymentV2 and SendToRouteV2. When the new
accounts.max-payment-size-msat config option is set to a non-zero
value, payments whose amount plus fees exceeds it are rejected with
ErrPaymentExceedsMaxSize before the balance check and before any funds
are reserved.

For SendPaymentV2, the configured fee limit is included in the capped
amount. For SendToRouteV2, the route's stated fee is included.

This gives operators a guard rail against a compromised or misbehaving
account macaroon draining its balance in a single large payment. The
cap defaults to 0 (disabled), preserving existing behaviour, and is a
first step towards the finer-grained per-account spending controls
requested in the issue.
2026-08-07 00:04:20 +02:00
Elle
9907dabc9f
Merge pull request #1359 from ellemouton/kvdb-sql-mig-lnd-ready-timeout
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
multi: wait for lnd with a configurable timeout during the SQL migration
2026-07-29 16:19:36 -07:00
Elle Mouton
970a3fb55d
docs: add release note for the lnd-ready migration timeout
Document the new --lndreadytimeout option and the startup failure it
fixes in the 0.17.1 release notes.
2026-07-29 15:47:47 -07:00
Elle Mouton
fb5863e38c
multi: wait for lnd with a configurable timeout during SQL migration
The kvdb-to-SQL data migration polls lnd's ListMacaroonIDs RPC, which
only becomes available once lnd reaches its "RPC active" state. On
nodes with a large channel/graph state, lnd can take well over a
minute to get there after the wallet is unlocked, which exceeded the
previous fixed 60-second (120 x 500ms) poll budget and caused the
migration - and therefore litd startup - to fail permanently,
requiring a manual restart.

Replace the fixed attempt cap with a wait bounded by the new
--lndreadytimeout config option, defaulting to a generous 10 minutes,
while still aborting early if the daemon is shutting down. The wait
happens inside the migration's SQL write transaction, so it is kept
bounded rather than unbounded as a safety backstop.
2026-07-29 15:47:47 -07:00
Viktor Torstensson
83df345294
Merge pull request #1343 from lightninglabs/dependabot/go_modules/litrpc/golang.org/x/net-0.55.0
build(deps): bump golang.org/x/net from 0.53.0 to 0.55.0 in /litrpc
2026-07-29 11:33:13 +02:00
Viktor Torstensson
4ead197fe3
Merge pull request #1344 from lightninglabs/dependabot/go_modules/autopilotserverrpc/golang.org/x/net-0.55.0
build(deps): bump golang.org/x/net from 0.53.0 to 0.55.0 in /autopilotserverrpc
2026-07-29 11:32:33 +02:00
Viktor Torstensson
dc72c6225c
Merge pull request #1349 from lightninglabs/dependabot/go_modules/perms/golang.org/x/crypto-0.52.0
build(deps): bump golang.org/x/crypto from 0.50.0 to 0.52.0 in /perms
2026-07-29 11:29:33 +02:00
dependabot[bot]
0f5ebe7061
build(deps): bump golang.org/x/net from 0.53.0 to 0.55.0 in /litrpc
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.53.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.53.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 10:09:41 +00:00
dependabot[bot]
475c8609a0
build(deps): bump golang.org/x/crypto from 0.50.0 to 0.52.0 in /perms
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.50.0 to 0.52.0.
- [Commits](https://github.com/golang/crypto/compare/v0.50.0...v0.52.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.52.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 10:09:28 +00:00
dependabot[bot]
519ce8a154
build(deps): bump golang.org/x/net in /autopilotserverrpc
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.53.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.53.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-28 10:09:22 +00:00
Viktor Torstensson
810cbf78c8
Merge pull request #1364 from lightninglabs/dependabot/go_modules/perms/google.golang.org/grpc-1.82.1
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
build(deps): bump google.golang.org/grpc from 1.79.3 to 1.82.1 in /perms
2026-07-28 12:08:05 +02:00
Viktor Torstensson
728e310c52
Merge pull request #1363 from lightninglabs/dependabot/go_modules/autopilotserverrpc/google.golang.org/grpc-1.82.1
build(deps): bump google.golang.org/grpc from 1.79.3 to 1.82.1 in /autopilotserverrpc
2026-07-28 12:07:41 +02:00
Viktor Torstensson
0d2cbdcf49
Merge pull request #1362 from lightninglabs/dependabot/go_modules/litrpc/google.golang.org/grpc-1.82.1
build(deps): bump google.golang.org/grpc from 1.79.3 to 1.82.1 in /litrpc
2026-07-28 12:07:15 +02:00
bitromortac
875c48806f
Merge pull request #1358 from bitromortac/2607-fix-32-overflow
Some checks are pending
CI / frontend tests on macOS-latest (push) Waiting to run
CI / frontend tests on ubuntu-latest (push) Waiting to run
CI / frontend tests on windows-latest (push) Waiting to run
CI / backend build on macOS-latest (push) Waiting to run
CI / backend build on ubuntu-latest (push) Waiting to run
CI / backend build on windows-latest (push) Waiting to run
CI / cross compilation (push) Waiting to run
CI / cross compilation-1 (push) Waiting to run
CI / cross compilation-2 (push) Waiting to run
CI / RPC proto compilation check (push) Waiting to run
CI / check commits (push) Waiting to run
CI / Sqlc check (push) Waiting to run
CI / lint (push) Waiting to run
CI / run unit tests (push) Waiting to run
CI / run unit tests-1 (push) Waiting to run
CI / run unit tests-2 (push) Waiting to run
CI / run unit tests-3 (push) Waiting to run
CI / build itest binaries (push) Waiting to run
CI / integration test (push) Blocked by required conditions
CI / integration test-1 (push) Blocked by required conditions
CI / integration test-2 (push) Blocked by required conditions
CI / check release notes updated (push) Waiting to run
firewall: fix 32-bit int truncation and optimize CryptoRandIntn
2026-07-28 09:56:35 +02:00
bitromortac
f162f44a0d docs: add release notes for privacy mapper refactoring
Add a succinct release note entry detailing the 32-bit truncation
prevention and allocation optimization introduced by PR-1358.
2026-07-28 07:25:41 +00:00
Viktor Torstensson
0bd5af814c
Merge pull request #1324 from Cyberguru1/bake-super-macaroon-startup
terminal: auto-bake super macaroon on startup
2026-07-27 15:48:08 +02:00
cyberguru1
0c06130ea1
docs: add release notes for super macaroon auto-bake
Add release notes for the new auto-bake config options in release notes for version 0.17.1 and reference pull request #1324.
2026-07-27 06:09:05 -05:00
cyberguru1
7cde9c50ae
itest: add test for super macaroon auto-bake
Add testSuperMacaroonOnStartup to verify startup baking logic. The test
restarts the node with the auto-baking config flags and asserts the
macaroon is baked with read-only or read-write permissions accordingly.

Also add validation tests verifying that starting with an invalid path
suffix or in stateless-init mode with baking enabled fails as expected.

Verify permission addition/expansion by starting the node with sub-servers
disabled and then restarting with sub-servers re-enabled. Also verify
the none config choice, asserting that no super macaroon file is
baked/created on startup.
2026-07-27 06:08:48 -05:00
cyberguru1
c5fbbd4d77
terminal: auto-bake super macaroon on startup
Automatically bake a super macaroon on startup if it doesn't already
exist on disk and the `bake-super-macaroon` option is configured.

On startup, the node verifies if the super macaroon file exists. If
it does, it parses the macaroon, extracts and verifies the version and
root key ID, and asserts that the macaroon permissions exactly match
the expected active permissions. If there is a mismatch, the macaroon
is regenerated and overwritten on disk. If the file does not exist, a
new super macaroon is baked and written directly to disk.

Also validate that the `bake-super-macaroon` option is not enabled when
LND is running in stateless initialization mode, failing startup early
if they are used together.
2026-07-27 06:08:48 -05:00
cyberguru1
d3e51fac17
config: add super macaroon startup flags and path validation
Introduce configuration flags to enable baking a super macaroon on
startup. Add --bake-super-macaroon (none, read-only, read-write) and
--super-macaroon-path.

Also add validation logic to ensure that the configured super macaroon
path ends with the expected '.macaroon' suffix, rejecting startup early
otherwise.
2026-07-27 06:08:48 -05:00
cyberguru1
3975e83006
macaroons: add super macaroon helpers
Introduce helper functions in the macaroons package to manage super
macaroon files and permissions. Add SuperMacaroonExists,
MacaroonMatchesPermissions, and BakeAndWriteSuperMacaroon.
2026-07-27 06:08:48 -05:00
Viktor Torstensson
e093948a35
Merge pull request #1306 from erickcestari/harden-ci-workflow
ci: harden main.yml workflow against supply-chain risks
2026-07-27 11:59:27 +02:00
Viktor Torstensson
e631ccf0e5
Merge pull request #1316 from Cyberguru1/account-payments
accounts: add account payments subcommand
2026-07-27 11:58:01 +02:00
Viktor Torstensson
11526d0388
Merge pull request #1366 from lightninglabs/dependabot/go_modules/golang.org/x/crypto-0.52.0
build(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0
2026-07-27 11:44:37 +02:00
dependabot[bot]
47e1f4099b
build(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.51.0 to 0.52.0.
- [Commits](https://github.com/golang/crypto/compare/v0.51.0...v0.52.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.52.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-27 08:47:11 +00:00
dependabot[bot]
f82bd5719b
build(deps): bump google.golang.org/grpc in /litrpc
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.3 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-27 08:46:48 +00:00
dependabot[bot]
3a2acb6c31
build(deps): bump google.golang.org/grpc from 1.79.3 to 1.82.1 in /perms
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.3 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-27 08:46:46 +00:00
dependabot[bot]
bb4c85d427
build(deps): bump google.golang.org/grpc in /autopilotserverrpc
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.3 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-27 08:46:31 +00:00
Viktor Torstensson
5c5a4130f0
Merge pull request #1365 from lightninglabs/dependabot/go_modules/google.golang.org/grpc-1.82.1
build(deps): bump google.golang.org/grpc from 1.80.0 to 1.82.1
2026-07-27 10:44:59 +02:00
dependabot[bot]
6a7cc15c45
build(deps): bump google.golang.org/grpc from 1.80.0 to 1.82.1
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.80.0 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.80.0...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-25 04:17:36 +00:00
cyberguru1
c6d81a8713
docs: add release notes for account payments
Add release notes for the new accounts payments CLI subcommand and
associated RPC endpoint to the v0.17.1 release notes document.
2026-07-22 14:06:56 -05:00
cyberguru1
0977e375d6
itest: add integration tests for AccountPayments
Add integration tests inside itest/litd_accounts_test.go to verify
retrieval of account payments against a running LND node, validating
correct responses for ID/label lookup, offsets, pagination limits,
and counting of total payments.
2026-07-22 14:06:43 -05:00
cyberguru1
1abbbe649c
perms: add permissions for AccountPayments
Register the /litrpc.Accounts/AccountPayments RPC endpoint in the
RequiredPermissions map, requiring read access on the account entity.
2026-07-22 14:06:26 -05:00
cyberguru1
6754acc17b
litcli: add payments command to accounts
Introduce the 'payments' subcommand under 'litcli accounts' to query
account payment history. Supports parameters for offset, page size
limits, and counting of total payments.
2026-07-22 14:06:26 -05:00
cyberguru1
d553cb28d7
accounts: implement AccountPayments RPC handler
Implement the AccountPayments handler on the Accounts RPC server.

The handler resolves accounts by ID or label, loads its payment hashes,
applies pagination constraints, and fetches complete payment details
concurrently from LND's TrackPaymentV2. It also returns pagination
metadata including the total count of payments.
2026-07-22 14:06:26 -05:00
cyberguru1
009a5c28bc
accounts: add payment methods to Store interface
Introduce the ListAccountPayments and CountAccountPayments methods to
the accounts Store interface.

ListAccountPayments enables retrieval of a paginated list of payment
entries associated with a given account ID, supporting offset and
limit. CountAccountPayments returns the total number of payments
associated with the account.
2026-07-22 14:06:25 -05:00
cyberguru1
857fb900fd
accounts: implement ListAccountPayments in BoltStore
Implement ListAccountPayments in the Bolt-based kvdb account store.
The retrieved account payments are sorted in ascending lexicographical
order of their payment hash.
2026-07-22 14:06:25 -05:00
cyberguru1
5632998030
accounts: implement ListAccountPayments in SQLStore
Add sqlc queries to select account payment hashes from the database.
Implement the ListAccountPayments method in the SQLStore, allowing
retrieval of stored payment hashes for SQLite and Postgres backends.

Also define the AccountPaymentEntry helper struct in
accounts/interface.go to wrap payment hashes and details.
2026-07-22 14:06:25 -05:00
cyberguru1
a132fca1ba
db/sqlc: add queries for account payments
Add SQL queries to select account payments with pagination (limit and
offset), and a query to count the total payments for a given account.
2026-07-22 14:06:25 -05:00
cyberguru1
f5a1a020eb
proto: add AccountPayments to Accounts service
Define the AccountPayments RPC endpoint and its request/response
messages in lit-accounts.proto. This endpoint allows querying
paginated payment history for a specific account.

Also update the frontend JS/TS proto sanitization script to map the
imported lnd.proto file to its flat directory path, fixing app build.
2026-07-22 14:06:25 -05:00
bitromortac
ec6814e940
Merge pull request #1322 from Vandit1604/accounts-errored-payment-passthrough
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
accounts: don't mask payment errors when request values are absent
2026-07-22 12:15:06 +02:00
bitromortac
cf7e53842a firewall: optimize CryptoRandIntn with sync.Pool
Utilize a sync.Pool for *big.Int instances in CryptoRandIntn to avoid
frequent heap allocations. This significantly reduces garbage collection
pressure when randomizing timestamps, amounts, and fees inside loops
(e.g. iterating over forwarding history responses).
2026-07-21 12:04:33 +00:00
bitromortac
6d89f07037 firewall: fix 32-bit int truncation
Upgrade the randIntn function signature from int to int64 to prevent
unpredictable integer truncation on 32-bit systems (e.g., Raspberry Pi).
This ensures UnixNano timestamps in hideTimestamp and large amounts in
hideAmount do not cause unexpected runtime failures when calling
ForwardingHistory.
2026-07-21 12:04:33 +00:00
Vandit Singh
73569014f2 accounts: don't mask payment errors when request values are absent 2026-07-19 01:19:35 +05:30
Viktor Torstensson
745f70f194
Merge pull request #1355 from ViktorT-11/2026-07-add-new-release-notes
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
docs: Add new release notes for `v0.17.1-alpha`
2026-07-16 14:50:38 +02:00
Viktor Torstensson
7604bd297b
docs: move release note entries added after v0.17.0-alpha
Move the release note entries that were added after v0.17.0-alpha to the
v0.17.1 release notes.
2026-07-16 14:09:32 +02:00
Viktor Torstensson
403efb3b46
docs: add release notes for v0.17.1-alpha 2026-07-16 13:29:54 +02:00
Viktor Torstensson
19f09f6350
Merge pull request #1353 from bitromortac/2607-itest-flake
terminal: gate wallet-ready status on lnd's actual RPC readiness
2026-07-16 13:17:44 +02:00
bitromortac
9370a6aef3 docs: update release notes 2026-07-16 09:53:44 +00:00
bitromortac
6f8159e003 itest: wait for LiT's own Running status in WaitUntilStarted
HarnessNode.Start calls WaitUntilStarted, then immediately reads
LitMacPath off disk via connectLitRPC to set up hn.litConn. But
WaitUntilStarted only polled subservers.LND's Running status, not LiT's
own - and litd only bakes and writes its default macaroons to disk
during its own startInternalSubServers step, strictly after LND is
marked Running but before LiT itself is. That left a window where the
harness read LitMacPath before litd had written it, failing with
"open .../lit.macaroon: no such file or directory"
(TestLightningTerminal/.../terminal_stateless_init_mode, CI run
29407791912).

Wait for subservers.LIT's Running status alongside the existing
per-subserver checks so callers can't observe LiT as "started" before
it has finished baking its macaroons.
2026-07-16 09:53:44 +00:00
bitromortac
c478f8227a terminal: gate wallet-ready status on lnd's actual RPC readiness
readyChan/unlockChan only guarantee lnd's gRPC listener socket is bound,
not that lnd's RPC interceptor has left WAITING_TO_START. Callers that
poll litd's status (itest's WaitForLNDWalletReady, litcli status, the UI)
could observe "Wallet Ready" and then immediately hit a "waiting to
start, RPC services not available" error, which was the root cause of a
flake in TestLightningTerminal/.../terminal_stateless_init_mode (CI run
29286599979, PR #1322). Poll lnd's StateService, which is exempt from
both the macaroon and RPC-readiness checks, until it reports leaving
WAITING_TO_START before advertising the wallet as ready.
2026-07-16 09:53:44 +00:00
Viktor Torstensson
8efa0e0656
Merge pull request #1352 from ViktorT-11/2026-07-bump-protobuf
Some checks are pending
CI / frontend tests on macOS-latest (push) Waiting to run
CI / frontend tests on ubuntu-latest (push) Waiting to run
CI / frontend tests on windows-latest (push) Waiting to run
CI / backend build on macOS-latest (push) Waiting to run
CI / backend build on ubuntu-latest (push) Waiting to run
CI / backend build on windows-latest (push) Waiting to run
CI / cross compilation (push) Waiting to run
CI / cross compilation-1 (push) Waiting to run
CI / cross compilation-2 (push) Waiting to run
CI / RPC proto compilation check (push) Waiting to run
CI / check commits (push) Waiting to run
CI / Sqlc check (push) Waiting to run
CI / lint (push) Waiting to run
CI / run unit tests (push) Waiting to run
CI / run unit tests-1 (push) Waiting to run
CI / run unit tests-2 (push) Waiting to run
CI / run unit tests-3 (push) Waiting to run
CI / build itest binaries (push) Waiting to run
CI / integration test (push) Blocked by required conditions
CI / integration test-1 (push) Blocked by required conditions
CI / integration test-2 (push) Blocked by required conditions
CI / check release notes updated (push) Waiting to run
build: bump `protobuf-go-hex-display`
2026-07-15 11:36:10 +02:00
Viktor Torstensson
324f87d7da
build: bump protobuf-go-hex-display
Bump protobuf-go-hex-display to version v1.36.11-hex-display.
2026-07-14 23:08:07 +02:00
Viktor Torstensson
767250e982
Merge pull request #1350 from lightninglabs/update-sqldb-v2
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
build: bump sqldb/v2 to v2.0.0
2026-07-13 17:15:28 +02:00
Viktor Torstensson
95f1ba71e9
Merge pull request #1351 from lightninglabs/bump-go-1.25.11
build: bump go to v1.25.11
2026-07-13 17:12:24 +02:00
ziggie
2a4a57b55b
build: bump sqldb/v2 to v2.0.0 2026-07-10 19:34:19 -03:00
ziggie
41a6c8b3f7
build: bump go to v1.25.11 2026-07-10 19:24:26 -03:00
bitromortac
9bfcdbe9ae
Merge pull request #1333 from Cyberguru1/fix-itest-process-leak
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
itest: fix flaky testRequestLoggerDisable node cleanup
2026-07-10 08:33:52 +02:00
Viktor Torstensson
3c035fe862
Merge pull request #1337 from 0xfandom/fix/1091-litd-version
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
config: make litd -V report litd version
2026-07-08 19:33:35 +02:00
Viktor Torstensson
1093de57b4
Merge pull request #1347 from lightninglabs/dependabot/go_modules/tools/golang.org/x/crypto-0.52.0
build(deps): bump golang.org/x/crypto from 0.45.0 to 0.52.0 in /tools
2026-07-08 19:33:07 +02:00
dependabot[bot]
b3e7b2fc57
build(deps): bump golang.org/x/crypto from 0.45.0 to 0.52.0 in /tools
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.45.0 to 0.52.0.
- [Commits](https://github.com/golang/crypto/compare/v0.45.0...v0.52.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.52.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-07 17:17:15 +00:00
Viktor Torstensson
4baf098cdf
Merge pull request #1346 from lightninglabs/dependabot/go_modules/golang.org/x/net-0.55.0
Some checks are pending
CI / frontend tests on macOS-latest (push) Waiting to run
CI / frontend tests on ubuntu-latest (push) Waiting to run
CI / frontend tests on windows-latest (push) Waiting to run
CI / backend build on macOS-latest (push) Waiting to run
CI / backend build on ubuntu-latest (push) Waiting to run
CI / backend build on windows-latest (push) Waiting to run
CI / cross compilation (push) Waiting to run
CI / cross compilation-1 (push) Waiting to run
CI / cross compilation-2 (push) Waiting to run
CI / RPC proto compilation check (push) Waiting to run
CI / check commits (push) Waiting to run
CI / Sqlc check (push) Waiting to run
CI / lint (push) Waiting to run
CI / run unit tests (push) Waiting to run
CI / run unit tests-1 (push) Waiting to run
CI / run unit tests-2 (push) Waiting to run
CI / run unit tests-3 (push) Waiting to run
CI / build itest binaries (push) Waiting to run
CI / integration test (push) Blocked by required conditions
CI / integration test-1 (push) Blocked by required conditions
CI / integration test-2 (push) Blocked by required conditions
CI / check release notes updated (push) Waiting to run
build(deps): bump golang.org/x/net from 0.49.0 to 0.55.0
2026-07-07 19:15:04 +02:00
dependabot[bot]
053498caba
build(deps): bump golang.org/x/net from 0.49.0 to 0.55.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.49.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.49.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 19:42:23 +00:00
Viktor Torstensson
512617bfa2
Merge pull request #1345 from lightninglabs/dependabot/go_modules/perms/github.com/opencontainers/runc-1.3.6
Some checks are pending
CI / frontend tests on macOS-latest (push) Waiting to run
CI / frontend tests on ubuntu-latest (push) Waiting to run
CI / frontend tests on windows-latest (push) Waiting to run
CI / backend build on macOS-latest (push) Waiting to run
CI / backend build on ubuntu-latest (push) Waiting to run
CI / backend build on windows-latest (push) Waiting to run
CI / cross compilation (push) Waiting to run
CI / cross compilation-1 (push) Waiting to run
CI / cross compilation-2 (push) Waiting to run
CI / RPC proto compilation check (push) Waiting to run
CI / check commits (push) Waiting to run
CI / Sqlc check (push) Waiting to run
CI / lint (push) Waiting to run
CI / run unit tests (push) Waiting to run
CI / run unit tests-1 (push) Waiting to run
CI / run unit tests-2 (push) Waiting to run
CI / run unit tests-3 (push) Waiting to run
CI / build itest binaries (push) Waiting to run
CI / integration test (push) Blocked by required conditions
CI / integration test-1 (push) Blocked by required conditions
CI / integration test-2 (push) Blocked by required conditions
CI / check release notes updated (push) Waiting to run
build(deps): bump github.com/opencontainers/runc from 1.2.8 to 1.3.6 in /perms
2026-07-06 21:40:59 +02:00
Viktor Torstensson
8f6c8a968e
Merge pull request #1342 from lightninglabs/dependabot/go_modules/litrpc/github.com/opencontainers/runc-1.3.6
build(deps): bump github.com/opencontainers/runc from 1.2.8 to 1.3.6 in /litrpc
2026-07-06 21:39:18 +02:00
dependabot[bot]
4614ade31c
build(deps): bump github.com/opencontainers/runc in /perms
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.2.8 to 1.3.6.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.2.8...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-version: 1.3.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 05:23:17 +00:00
dependabot[bot]
e3091c9045
build(deps): bump github.com/opencontainers/runc in /litrpc
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.2.8 to 1.3.6.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.2.8...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-version: 1.3.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 13:12:40 +00:00
Viktor Torstensson
58568b5afc
Merge pull request #1340 from ViktorT-11/2026-07-improve-docker-space-cleanup
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
docker: improve cleanup of docker space in CI
2026-07-02 15:11:05 +02:00
Viktor Torstensson
4e72e798e6
docker: improve cleanup of docker space in CI
Mimic `lnd`s cleanup of docker space in CI by adding a new clean-space
job which removes the same docker related artifacts as `lnd` does prior
to running the rest of the docker.yml jobs.
2026-07-01 12:58:42 +02:00
Viktor Torstensson
08934deeef
Merge pull request #1331 from ViktorT-11/2026-06-cut-v0_17_0_final
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
Prepare for litd `v0.17.0-alpha` release
2026-06-30 17:15:06 +02:00
Viktor Torstensson
7a40162f84
Merge pull request #1338 from lightninglabs/dependabot/go_modules/github.com/opencontainers/runc-1.3.6
build(deps): bump github.com/opencontainers/runc from 1.2.8 to 1.3.6
2026-06-30 17:14:24 +02:00
0xfandom
bcdbe68fa1 config: make litd -V report litd's own version
The `-V` flag inherited from lnd printed the integrated lnd version
(`build.Version()`) instead of litd's own version, while litd's own
`--version` flag already reported the correct value via RichVersion().
Consolidate both flags so either one prints litd's version.

Fixes #1091

Signed-off-by: 0xfandom <kashyapshivank01@gmail.com>
2026-06-30 11:59:39 +05:30
dependabot[bot]
b4133b1a12
build(deps): bump github.com/opencontainers/runc from 1.2.8 to 1.3.6
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.2.8 to 1.3.6.
- [Release notes](https://github.com/opencontainers/runc/releases)
- [Changelog](https://github.com/opencontainers/runc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/opencontainers/runc/compare/v1.2.8...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/opencontainers/runc
  dependency-version: 1.3.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 19:36:47 +00:00
Viktor Torstensson
8cd75e44d9
version: bump version to v0.17.0-alpha 2026-06-26 22:45:44 +02:00
Viktor Torstensson
569fdee4f4
docs: prepare release notes for v0.17.0-alpha 2026-06-26 22:45:44 +02:00
Viktor Torstensson
901d60cc0e
docs: update compatibility notes
Add `v0.16.1-alpha` & `v0.17.0-alpha` to the compatibility notes.
2026-06-26 22:45:21 +02:00
Viktor Torstensson
503201ffa7
perms: bump lnd to v0.21.1-beta 2026-06-26 22:39:55 +02:00
Viktor Torstensson
5032e33d35
litrpc: bump lnd and looprpc
Bump `lnd` to `v0.21.1-beta`. We also bump `looprpc` to the version
compatible with `loop` `v0.33.3-beta`.
2026-06-26 22:39:54 +02:00
Viktor Torstensson
5c34be31f4
terminal: bump lnd, loop, looprpc & swapserverrpc
Bump `lnd` to version `v0.21.1-beta`, as well as `loop` to version
`v0.33.3-beta`. Additionally, we bump `looprpc` & `swapserverrpc` to
versions compatible with the new `loop` version.
2026-06-26 22:39:54 +02:00
Viktor Torstensson
69eb14cc63
Merge pull request #1334 from 0xfandom/accounts/test-checklabel
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
accounts: add test coverage for checkLabel
2026-06-24 12:07:30 +02:00
0xfandom
163cccba89 test: add coverage for account checkLabel validation
checkLabel rejects labels that could be mistaken for a hex-encoded
account ID. Add a table-driven test covering the accepted cases (empty,
plain text, short hex, and non-hex strings of account-ID length) as well
as the rejected cases (lower- and upper-case hex strings of account-ID
length).

Signed-off-by: 0xfandom <kashyapshivank01@gmail.com>
2026-06-23 14:59:05 +05:30
Viktor Torstensson
916ca71d70
Merge pull request #1317 from Cyberguru1/custom-session-permissions
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
sessions: support entity:action format for custom sessions
2026-06-22 12:38:44 +02:00
cyberguru1
02a1b31303
itest: fix testRequestLoggerDisable node cleanup
The test node variable was reassigned in a later step. This broke the
deferred cleanup closure and leaked the reqlog-on node process.

The leaked process caused random cleanup timeouts under heavy test load.
Fixed by copying the node to a local variable before the defer.
2026-06-22 03:27:39 -05:00
cyberguru1
05d0566986 docs: add release note for session custom perms
Add a release note to the upcoming release notes document for 0.17.0
detailing the newly added support for custom permissions via the
`--permission` flag in `litcli sessions add`.
2026-06-22 01:50:56 -05:00
cyberguru1
484d5d789e itest: add custom permissions integration tests
Add a new sub-test case to verify that custom `entity:action`
permissions are correctly handled and enforced for LNC custom
sessions. The test uses the `info:read` permission to
assert that the connection can only query LND's GetInfo endpoint
and is blocked on other endpoints. Tests are added for both
integrated mode and remote mode suites.
2026-06-22 01:50:56 -05:00
cyberguru1
04f06144bd litcli: add custom session permission unit tests
Add comprehensive unit tests for the parsing and validation logic of
the custom permissions parser (`parseCustomPermissions`) in `litcli`.
2026-06-22 01:50:56 -05:00
cyberguru1
0b8fd17457 litcli: add custom permissions to session add
Add support for the `--permission` flag (which takes `entity:action`
pairs), similar to `lncli bakemacaroon`, when creating custom sessions
with `litcli sessions add`. Supports repeated flags, comma-separated
lists, and mixed input.
2026-06-22 01:50:56 -05:00
Viktor Torstensson
f78be1d0bd
Merge pull request #1326 from ViktorT-11/2026-06-dont-create-kvdb-databases-on-migration
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled
[sql-75] Don't create kvdb database files in kvdb -> SQL migration if they don't exist
2026-06-17 00:59:20 +02:00
Viktor Torstensson
edbd4f51b4
Merge pull request #1330 from ViktorT-11/2026-06-stop-lnd-on-mig-prompt-abort
[sql-77] terminal: shut down LiT when declining the SQL migration prompt
2026-06-17 00:44:44 +02:00
Viktor Torstensson
51c56982fb
Merge pull request #1327 from ViktorT-11/2026-06-block-postgres-startup-if-sqlite-exists
[sql-76] config: block startup on mixed SQL backends
2026-06-17 00:44:28 +02:00
Viktor Torstensson
42c3105537
terminal: thread a context into postgresDatabaseExists 2026-06-16 21:45:47 +02:00
Viktor Torstensson
6d2bf233e8
config: block startup on mixed SQL backends
Reject `litd` startup when another SQL database exists than the selected
SQL `databasebackend` choice.

When starting with the `databasebackend` config flag set to `postgres`,
we check whether an `sqlite` database file exists at the default path
and fail startup if it does. When starting with `sqlite`, we instead
check whether `postgres` database configuration has been provided and,
if so, whether a database exists at the configured `postgres`
connection parameters.

This prevents accidental switches between SQL backends. This is
especially important because allowing such a switch would retrigger the
KVDB-to-SQL migration for the newly configured SQL backend. In that
scenario, the data would already have been migrated previously, meaning
the migration source would be stale and could result in outdated data
being imported. Additionally, it would create two divergent copies of
the data across separate SQL backends. Since we do not support
migrations between SQL backends, recovering from such a situation would
not be possible.
2026-06-16 21:45:47 +02:00
Viktor Torstensson
059554474e
db: skip SQL migration when no kvdb files exist
Avoid creating legacy bbolt database files when the kvdb-to-SQL
programmatic migration runs on a node that never had LiT kvdb state.

Note that we only skip the creating the database file if there is no
single bbolt database file. If for example there's only 1 existing
bbolt database file, we will still create the new bbolt database file
for the other 2 bbolt databases, as we for example need to pass the
accounts & session databases to the firewalldb migration, and we'd want
to avoid having the migrations become more complex by having to handle
the case where the other databases are missing.
2026-06-16 21:16:11 +02:00
Viktor Torstensson
27eba1e5c1
db: unify kvdb file existence checks
Add a tombstone helper for checking whether a legacy kvdb file exists on
disk, without inspecting any tombstone state inside the database.

Use this helper in the tombstone deprecation and activity checks so
missing-file handling is centralized in one place instead of being
reimplemented in each function.

We also export this helper function, as we'll use it in the upcoming
commit which checks if the legacy any bbolt database file exists before
attempting to migrate it.
2026-06-16 20:59:56 +02:00
Viktor Torstensson
d9b8d6d4b8
terminal: shut down on declined SQL migration
Treat a declined kvdb-to-SQL migration prompt as a startup-abort
condition that shuts litd down fully. This prevents litd from leaving
the status server running after the operator refuses the migration
prompt.

The reason why this is motivated, is that `litd` at this stage of the
startup process will not be ready to handle an `litcli stop` RPC call.
2026-06-16 20:49:23 +02:00
Viktor Torstensson
650fd92259
app: patch Loop proto import for local proto build
Rewrite Loop's lnd proto import during proto generation so make protos
continues to work with this repo's local proto layout.

This became necessary when Loop v0.32.0-beta started shipping the
looprpc/client.proto change from upstream commit
f47b6115571f6072fe68057cb4eff30cc4fc47df
("looprpc: extend client.proto with lnrpc.OutPoint and
lnrpc.OpenChannelRequest"). That update made the downloaded Loop proto
import lnrpc/lightning.proto, while LiT stores the downloaded lnd proto
locally as proto/lnd.proto and invokes protoc with ../proto as the
import root. As a result, the downloaded Loop proto overwrote the local
compatibility fix and make protos failed with
"lnrpc/lightning.proto: File not found".

Patch the downloaded proto in build-protos.js to import lnd.proto
instead, and keep proto/loop.proto aligned with the same import path.
This is needed because changing only the checked-in proto file is not
enough: the build script re-fetches and replaces it on every run.
2026-06-16 00:35:56 +02:00
Erick Cestari
409cba4f63
ci: harden main.yml workflow against supply-chain risks
Apply least-privilege and standard hardening to the CI workflow:

- Set top-level `permissions: contents: read` so `GITHUB_TOKEN` no
  longer defaults to broad read/write across the repo.
- Pass `github.base_ref` and `github.repository` via env vars in the
  `check-commits` job to prevent shell injection via expression
  interpolation in `run:` blocks.
- Pin third-party `jpribyl/action-docker-layer-caching` to a full
  commit SHA so a tag retag cannot swap in unreviewed code.
- Bump `actions/checkout@v3` to `@v4` in `sqlc-check` for consistency
  with the rest of the workflow.
- Use `yarn install --frozen-lockfile` for all dependency installs so
  CI fails on lockfile drift instead of silently resolving new
  package versions.
2026-05-13 11:25:41 -03:00
97 changed files with 9886 additions and 3276 deletions

View file

@ -0,0 +1,48 @@
name: "Clean up runner disk space"
description: "Removes large, non-essential toolsets to free up disk space."
runs:
using: "composite"
steps:
- name: Free up disk space
shell: bash
run: |
echo "Removing large toolsets to free up disk space..."
echo "Disk space before cleanup:"
df -h
# Remove large language/runtime toolsets that aren't needed for the
# Docker image build on the GitHub-hosted runner.
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /usr/share/swift
sudo rm -rf /usr/local/julia*
sudo rm -rf /opt/hostedtoolcache
# Reclaim any Docker images that may already be present on the runner.
docker image prune -a -f || true
# Remove large apt packages and clean related caches.
sudo apt-get remove -y \
'^aspnetcore-.*' \
'^dotnet-.*' \
'^llvm-.*' \
'php.*' \
'^mongodb-.*' \
'^mysql-.*' \
azure-cli \
google-chrome-stable \
firefox \
powershell \
mono-devel \
libgl1-mesa-dri 2>/dev/null || true
sudo apt-get autoremove -y
sudo apt-get clean
# Remove additional large caches and tool directories.
sudo rm -rf /usr/local/share/boost
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
echo "Disk space after cleanup:"
df -h

View file

@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: cleanup space
run: rm -rf /opt/hostedtoolcache
uses: ./.github/actions/cleanup-space
- name: Set up QEMU
uses: lightninglabs/gh-actions/setup-qemu-action@2021.01.25.00

View file

@ -13,6 +13,9 @@ concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
defaults:
run:
shell: bash
@ -21,7 +24,12 @@ env:
# If you change this value, please change it in the following files as well:
# /Dockerfile
# /dev.Dockerfile
GO_VERSION: 1.25.10
GO_VERSION: 1.25.11
# Only applies to the CI jobs below. The note above covers GO_VERSION alone:
# /Dockerfile and /dev.Dockerfile pin their own Node version for release
# builds, and it does not have to match this one.
NODE_VERSION: 24.x
jobs:
########################
@ -33,7 +41,6 @@ jobs:
strategy:
matrix:
node_version: [ 16.x ]
os: [ ubuntu-latest, windows-latest, macOS-latest ]
steps:
@ -42,14 +49,14 @@ jobs:
with:
fetch-depth: 0
- name: setup nodejs ${{ matrix.node_version }}
- name: setup nodejs ${{ env.NODE_VERSION }}
uses: ./.github/actions/setup-node
with:
node-version: '${{ matrix.node_version }}'
node-version: '${{ env.NODE_VERSION }}'
- name: install dependencies
working-directory: ./app
run: yarn
run: yarn install --frozen-lockfile
- name: lint code
working-directory: ./app
@ -81,10 +88,10 @@ jobs:
with:
fetch-depth: 0
- name: setup nodejs ${{ matrix.node_version }}
- name: setup nodejs ${{ env.NODE_VERSION }}
uses: ./.github/actions/setup-node
with:
node-version: '${{ matrix.node_version }}'
node-version: '${{ env.NODE_VERSION }}'
- name: setup go ${{ env.GO_VERSION }}
uses: ./.github/actions/setup-go
@ -141,10 +148,10 @@ jobs:
with:
fetch-depth: 0
- name: setup nodejs ${{ matrix.node_version }}
- name: setup nodejs ${{ env.NODE_VERSION }}
uses: ./.github/actions/setup-node
with:
node-version: '${{ matrix.node_version }}'
node-version: '${{ env.NODE_VERSION }}'
- name: setup go ${{ env.GO_VERSION }}
uses: ./.github/actions/setup-go
@ -153,7 +160,7 @@ jobs:
- name: install dependencies
working-directory: ./app
run: yarn
run: yarn install --frozen-lockfile
- name: run check
run: make rpc-js-compile && make protos-check
@ -183,15 +190,20 @@ jobs:
go-version: '${{ env.GO_VERSION }}'
- name: fetch and rebase on ${{ github.base_ref }}
env:
BASE_REF: ${{ github.base_ref }}
REPOSITORY: ${{ github.repository }}
run: |
git remote add upstream https://github.com/${{ github.repository }}
git remote add upstream "https://github.com/${REPOSITORY}"
git fetch upstream
export GIT_COMMITTER_EMAIL="litd-ci@example.com"
export GIT_COMMITTER_NAME="LiT CI"
git rebase upstream/${{ github.base_ref }}
git rebase "upstream/${BASE_REF}"
- name: check commits
run: scripts/check-each-commit.sh upstream/${{ github.base_ref }}
env:
BASE_REF: ${{ github.base_ref }}
run: scripts/check-each-commit.sh "upstream/${BASE_REF}"
#######################
# sql model generation
@ -201,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: git checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: setup go ${{ env.GO_VERSION }}
uses: ./.github/actions/setup-go
@ -209,7 +221,7 @@ jobs:
go-version: '${{ env.GO_VERSION }}'
- name: docker image cache
uses: jpribyl/action-docker-layer-caching@v0.1.1
uses: jpribyl/action-docker-layer-caching@c632825d12ec837065f49726ea27ddd40bcc7894 # v0.1.1
continue-on-error: true
- name: Generate sql models
@ -316,10 +328,10 @@ jobs:
with:
fetch-depth: 0
- name: setup nodejs ${{ matrix.node_version }}
- name: setup nodejs ${{ env.NODE_VERSION }}
uses: ./.github/actions/setup-node
with:
node-version: '${{ matrix.node_version }}'
node-version: '${{ env.NODE_VERSION }}'
- name: setup go ${{ env.GO_VERSION }}
uses: ./.github/actions/setup-go
@ -329,7 +341,7 @@ jobs:
- name: install dependencies
working-directory: ./app
run: yarn
run: yarn install --frozen-lockfile
- name: run itest ${{ matrix.name }}
run: make ${{ matrix.args }}

View file

@ -32,7 +32,7 @@ RUN apk add --no-cache --update alpine-sdk \
# The first stage is already done and all static assets should now be generated
# in the app/build sub directory.
FROM golang:1.25.10-alpine3.23@sha256:8d22e29d960bc50cd025d93d5b7c7d220b1ee9aa7a239b3c8f55a57e987e8d45 as golangbuilder
FROM golang:1.25.11-alpine3.23@sha256:60e626bbde32def8694687d03536ea4341b19e5f068e9a630225a1dfbd0505c9 as golangbuilder
# Instead of checking out from git again, we just copy the whole working
# directory of the previous stage that includes the generated static assets.

View file

@ -22,7 +22,7 @@ PUBLIC_URL :=
# GO_VERSION is the Go version used for the release build, docker files, and
# GitHub Actions. This is the reference version for the project. All other Go
# versions are checked against this version.
GO_VERSION = 1.25.10
GO_VERSION = 1.25.11
LOOP_COMMIT := $(shell cat go.mod | \
grep $(LOOP_PKG) | \

View file

@ -3,6 +3,7 @@ package accounts
import (
"context"
"fmt"
"math/bits"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
@ -79,8 +80,10 @@ type AccountChecker struct {
// NewAccountChecker creates a new account checker that can keep track of all
// account related requests, including invoices, payments and account balances.
func NewAccountChecker(service Service,
chainParams *chaincfg.Params) *AccountChecker {
// If maxPaymentSize is greater than zero, payments whose amount exceeds it are
// rejected.
func NewAccountChecker(service Service, chainParams *chaincfg.Params,
maxPaymentSize lnwire.MilliSatoshi) *AccountChecker {
// nolint:ll
checkers := CheckerMap{
@ -162,9 +165,9 @@ func NewAccountChecker(service Service,
}
return checkSend(
ctx, chainParams, service, r.Amt,
r.AmtMsat, r.PaymentRequest,
r.PaymentHash,
ctx, chainParams, service,
maxPaymentSize, r.Amt, r.AmtMsat,
r.PaymentRequest, r.PaymentHash,
&lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_FixedMsat{
FixedMsat: feeLimitMsat,
@ -196,7 +199,8 @@ func NewAccountChecker(service Service,
r *routerrpc.SendToRouteRequest) error {
return checkSendToRoute(
ctx, service, r.PaymentHash, r.Route,
ctx, service, maxPaymentSize,
r.PaymentHash, r.Route,
)
},
sendToRouteHTLCResponseHandler(service),
@ -465,11 +469,22 @@ func filterPayments(ctx context.Context,
return filteredPayments, nil
}
// checkMaxPaymentSize returns an error if a maximum account payment size is
// configured (non-zero) and the given total payment amount exceeds it.
func checkMaxPaymentSize(maxPaymentSize, amt lnwire.MilliSatoshi) error {
if maxPaymentSize != 0 && amt > maxPaymentSize {
return fmt.Errorf("%w: amount %v exceeds the maximum of %v",
ErrPaymentExceedsMaxSize, amt, maxPaymentSize)
}
return nil
}
// checkSend checks if a payment can be initiated by making sure the account in
// the context has enough balance to pay for it.
func checkSend(ctx context.Context, chainParams *chaincfg.Params,
service Service, amt, amtMsat int64, invoice string,
paymentHash []byte, feeLimit *lnrpc.FeeLimit) error {
service Service, maxPaymentSize lnwire.MilliSatoshi, amt, amtMsat int64,
invoice string, paymentHash []byte, feeLimit *lnrpc.FeeLimit) error {
log, acct, reqID, err := requestScopedValuesFromCtx(ctx)
if err != nil {
@ -540,7 +555,19 @@ func checkSend(ctx context.Context, chainParams *chaincfg.Params,
limit = &lnrpc.FeeLimit{}
}
fee := lnrpc.CalculateFeeLimit(limit, sendAmt)
sendAmt += fee
// Add the fee explicitly and reject amounts that cannot be represented.
total, carry := bits.Add64(uint64(sendAmt), uint64(fee), 0)
if carry != 0 {
return ErrAccBalanceInsufficient
}
sendAmt = lnwire.MilliSatoshi(total)
// Enforce the configured maximum payment size on the full amount that
// may be debited from the account.
if err := checkMaxPaymentSize(maxPaymentSize, sendAmt); err != nil {
return err
}
err = service.CheckBalance(ctx, acct.ID, sendAmt)
if err != nil {
@ -607,7 +634,8 @@ func checkSendResponse(ctx context.Context, service Service,
// checkSendToRoute checks if a payment can be sent to the route by making sure
// the account in the context has enough balance to pay for it.
func checkSendToRoute(ctx context.Context, service Service, paymentHash []byte,
func checkSendToRoute(ctx context.Context, service Service,
maxPaymentSize lnwire.MilliSatoshi, paymentHash []byte,
route *lnrpc.Route) error {
log, acct, reqID, err := requestScopedValuesFromCtx(ctx)
@ -640,7 +668,19 @@ func checkSendToRoute(ctx context.Context, service Service, paymentHash []byte,
if lnwire.MilliSatoshi(route.TotalFeesMsat) > fee {
fee = lnwire.MilliSatoshi(route.TotalFeesMsat)
}
sendAmt += fee
// Add the fee explicitly and reject amounts that cannot be represented.
total, carry := bits.Add64(uint64(sendAmt), uint64(fee), 0)
if carry != 0 {
return ErrAccBalanceInsufficient
}
sendAmt = lnwire.MilliSatoshi(total)
// Enforce the configured maximum payment size on the full amount that
// may be debited from the account.
if err := checkMaxPaymentSize(maxPaymentSize, sendAmt); err != nil {
return err
}
err = service.CheckBalance(ctx, acct.ID, sendAmt)
if err != nil {
@ -668,10 +708,18 @@ func erroredPaymentHandler(service Service) mid.ErrorHandler {
return nil, err
}
// On streaming sends a call can return a response and then a
// terminal error, so the same request's error may be processed
// more than once. If the request values are already gone there
// is nothing left to clean up and we let the error pass
// through.
reqVals, ok := service.GetValues(reqID)
if !ok {
return nil, fmt.Errorf("no request values found for "+
"request: %d", reqID)
log.Tracef("No request values found for request: %d, "+
"passing the response error through unchanged",
reqID)
return nil, nil
}
log.Tracef("Handling payment request error for payment with "+

View file

@ -144,7 +144,7 @@ var _ Service = (*mockService)(nil)
func TestAccountChecker(t *testing.T) {
t.Parallel()
checker := NewAccountChecker(nil, nil)
checker := NewAccountChecker(nil, nil, 0)
for checkerName := range checker.checkers {
t.Logf("Checker registered: %v", checkerName)
}
@ -442,7 +442,7 @@ func TestAccountCheckers(t *testing.T) {
tt.Parallel()
service := newMockService()
checkers := NewAccountChecker(service, chainParams)
checkers := NewAccountChecker(service, chainParams, 0)
acct := &OffChainBalanceAccount{
ID: testID,
Type: TypeInitialBalance,
@ -677,6 +677,42 @@ func TestSendPaymentV2(t *testing.T) {
_, err = service.checkers.handleErrorResponse(ctx, uri, nil)
require.NoError(t, err)
assertBalance(acct.ID, 2000)
// Finally, replicate the streaming scenario. Since SendPaymentV2 is a
// streaming endpoint, the request values are deleted as soon as a
// terminal Payment response is handled. If lnd then sends a terminal
// error response for the same request, the error handler finds no
// request values. It must pass the original error through unchanged
// instead of masking it with a confusing "no request values found"
// error.
reqID5 := nextRequestID()
ctx = AddRequestIDToContext(ctxWithAcct, reqID5)
// Send a valid request so the request values are registered.
err = service.checkers.checkIncomingRequest(
ctx, uri, &routerrpc.SendPaymentRequest{
AmtMsat: 1000,
PaymentHash: testHash2[:],
},
)
require.NoError(t, err)
// A failed Payment response deletes the stored request values.
_, err = service.checkers.replaceOutgoingResponse(
ctx, uri, &lnrpc.Payment{
PaymentHash: hex.EncodeToString(testHash2[:]),
Status: lnrpc.Payment_FAILED,
},
)
require.NoError(t, err)
// A subsequent error response for the same request must not be masked.
originalErr := fmt.Errorf("account balance insufficient")
returnedErr, err := service.checkers.handleErrorResponse(
ctx, uri, originalErr,
)
require.NoError(t, err)
require.ErrorContains(t, returnedErr, "account balance insufficient")
}
// TestSendToRouteV2 performs test coverage on the SendToRouteV2 checker.
@ -887,3 +923,109 @@ func assertMessagesEqual(t *testing.T, expected, actual proto.Message) {
require.Equal(t, string(expectedJSON), string(actualJSON))
}
// TestSendPaymentV2MaxPaymentSize tests that, when a maximum account payment
// size is configured, the SendPaymentV2 checker rejects payments whose total
// amount, including the fee limit, exceeds the cap (issue #583).
func TestSendPaymentV2MaxPaymentSize(t *testing.T) {
var (
uri = "/routerrpc.Router/SendPaymentV2"
ctx = context.Background()
requestID uint64
)
nextRequestID := func() uint64 {
requestID++
return requestID
}
lndMock := newMockLnd()
routerMock := newMockRouter()
errFunc := func(err error) {
lndMock.mainErrChan <- err
}
clk := clock.NewTestClock(time.Now())
store := NewTestDB(t, clk)
service, err := NewService(store, errFunc, WithMaxPaymentSize(2000))
require.NoError(t, err)
err = service.Start(ctx, lndMock, routerMock, chainParams)
require.NoError(t, err)
t.Cleanup(func() {
_ = service.Stop()
})
acct, err := service.NewAccount(
ctx, 1_000_000, clk.Now().Add(time.Hour), "max",
)
require.NoError(t, err)
ctxWithAcct := AddAccountToContext(ctx, acct)
// A payment whose total amount is exactly at the cap is allowed.
ctx1 := AddRequestIDToContext(ctxWithAcct, nextRequestID())
err = service.checkers.checkIncomingRequest(
ctx1, uri, &routerrpc.SendPaymentRequest{
AmtMsat: 2000,
PaymentHash: testHash[:],
},
)
require.NoError(t, err)
// A payment whose amount exceeds the cap is rejected.
ctx2 := AddRequestIDToContext(ctxWithAcct, nextRequestID())
err = service.checkers.checkIncomingRequest(
ctx2, uri, &routerrpc.SendPaymentRequest{
AmtMsat: 2001,
PaymentHash: testHash2[:],
},
)
require.ErrorIs(t, err, ErrPaymentExceedsMaxSize)
// A fee limit that brings an otherwise valid payment over the cap is
// rejected as well.
ctx3 := AddRequestIDToContext(ctxWithAcct, nextRequestID())
err = service.checkers.checkIncomingRequest(
ctx3, uri, &routerrpc.SendPaymentRequest{
AmtMsat: 1900,
FeeLimitMsat: 101,
PaymentHash: testHash3[:],
},
)
require.ErrorIs(t, err, ErrPaymentExceedsMaxSize)
}
func TestSendToRouteV2MaxPaymentSize(t *testing.T) {
const uri = "/routerrpc.Router/SendToRouteV2"
ctx := context.Background()
lndMock := newMockLnd()
routerMock := newMockRouter()
service, err := NewService(
NewTestDB(t, clock.NewTestClock(time.Now())),
func(err error) { lndMock.mainErrChan <- err },
WithMaxPaymentSize(2000),
)
require.NoError(t, err)
err = service.Start(ctx, lndMock, routerMock, chainParams)
require.NoError(t, err)
t.Cleanup(func() { _ = service.Stop() })
acct, err := service.NewAccount(
ctx, 1_000_000, time.Now().Add(time.Hour), "max",
)
require.NoError(t, err)
ctx = AddRequestIDToContext(AddAccountToContext(ctx, acct), 1)
err = service.checkers.checkIncomingRequest(
ctx, uri, &routerrpc.SendToRouteRequest{
PaymentHash: testHash[:],
Route: &lnrpc.Route{
TotalAmtMsat: 1900,
TotalFeesMsat: 101,
},
},
)
require.ErrorIs(t, err, ErrPaymentExceedsMaxSize)
}

View file

@ -107,6 +107,12 @@ type AccountInvoices map[lntypes.Hash]struct{}
// AccountPayments is the set of payments that are associated with an account.
type AccountPayments map[lntypes.Hash]*PaymentEntry
// AccountPaymentEntry wraps a payment hash with its entry details.
type AccountPaymentEntry struct {
Hash lntypes.Hash
*PaymentEntry
}
// OffChainBalanceAccount holds all information that is needed to keep track of
// a user's off-chain account balance. This balance can only be spent by paying
// invoices.
@ -188,6 +194,19 @@ var (
// account
ErrAccBalanceInsufficient = errors.New("account balance insufficient")
// ErrPaymentExceedsMaxSize is returned when a maximum account payment
// size is configured and a payment's total amount exceeds it.
ErrPaymentExceedsMaxSize = errors.New("payment amount exceeds the " +
"maximum allowed account payment size")
// ErrBalanceReservationExceeded is returned when the channel-balance
// check is enabled and an account balance allocation would push the sum
// of all account balances above the node's available local channel
// balance.
ErrBalanceReservationExceeded = errors.New("account balance " +
"allocation would exceed the node's available local channel " +
"balance")
// ErrNotSupportedWithAccounts is the error that is returned when an RPC
// is called that isn't supported to be handled by the account
// interceptor.
@ -279,6 +298,16 @@ type Store interface {
DeleteAccountPayment(_ context.Context, id AccountID,
hash lntypes.Hash) error
// ListAccountPayments returns a paginated list of payments
// associated with the given account, sorted in ascending
// lexicographical order of their payment hash.
ListAccountPayments(ctx context.Context, id AccountID, offset,
limit int32) ([]*AccountPaymentEntry, error)
// CountAccountPayments returns the total number of payments associated
// with the given account.
CountAccountPayments(ctx context.Context, id AccountID) (uint64, error)
// RemoveAccount finds an account by its ID and removes it from the¨
// store.
RemoveAccount(ctx context.Context, id AccountID) error

View file

@ -5,18 +5,43 @@ import (
"encoding/hex"
"errors"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lightning-terminal/litrpc"
litmac "github.com/lightninglabs/lightning-terminal/macaroons"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
"gopkg.in/macaroon.v2"
)
const (
// DefaultMaxPayments is the default page size for listing payments.
DefaultMaxPayments = 20
// MaxPaymentsLimit is the maximum page size allowed for listing
// payments.
MaxPaymentsLimit = 50
// MaxIndexOffset is the maximum index offset allowed.
MaxIndexOffset = 0x7fffffff
// DefaultTrackPaymentTimeout is the timeout for track payment RPC
// calls.
DefaultTrackPaymentTimeout = 8 * time.Second
// MaxConcurrentTrackPayments is the maximum number of concurrent track
// payment requests.
MaxConcurrentTrackPayments = 10
)
var (
// ErrServerNotActive indicates that the server has started but hasn't
// fully finished the startup process.
@ -371,3 +396,223 @@ func marshalAccount(acct *OffChainBalanceAccount) *litrpc.Account {
return rpcAccount
}
// AccountPayments returns the detailed payment history for the given account.
func (s *RPCServer) AccountPayments(ctx context.Context,
req *litrpc.AccountPaymentsRequest) (
*litrpc.AccountPaymentsResponse, error) {
if req.GetAccount() == nil {
return nil, fmt.Errorf("account param must be specified")
}
var id, label string
switch idType := req.Account.Identifier.(type) {
case *litrpc.AccountIdentifier_Id:
id = idType.Id
case *litrpc.AccountIdentifier_Label:
label = idType.Label
}
log.Infof("[accountpayments] id=%s, label=%v, max_payments=%d, "+
"index_offset=%d, count_total_payments=%v",
id, label, req.MaxPayments, req.IndexOffset,
req.CountTotalPayments)
accountID, err := s.findAccount(ctx, id, label)
if err != nil {
return nil, err
}
// Determine limits.
limit := req.MaxPayments
if limit == 0 {
limit = DefaultMaxPayments
} else if limit > MaxPaymentsLimit {
return nil, fmt.Errorf(
"max_payments cannot exceed %d", MaxPaymentsLimit,
)
}
if req.IndexOffset > MaxIndexOffset {
return nil, fmt.Errorf("index_offset out of range")
}
offset := req.IndexOffset
// Fetch the paginated payment entries from the store.
paymentsFromStore, err := s.service.store.ListAccountPayments(
ctx, accountID, int32(offset), int32(limit),
)
if err != nil {
return nil, fmt.Errorf(
"unable to list account payments: %w", err,
)
}
// Fetch total payment count if requested.
var totalNumPayments uint64
if req.CountTotalPayments {
total, err := s.service.store.CountAccountPayments(
ctx, accountID,
)
if err != nil {
return nil, fmt.Errorf(
"unable to count account payments: %w", err,
)
}
totalNumPayments = total
}
// Fetch the detailed payments concurrently from LND.
var (
wg sync.WaitGroup
mu sync.Mutex
payments = make(map[lntypes.Hash]*lnrpc.Payment)
errs []error
sem = make(chan struct{}, MaxConcurrentTrackPayments)
)
rawCtx, _, client := s.service.routerClient.RawClientWithMacAuth(ctx)
for _, entry := range paymentsFromStore {
entry := entry
wg.Add(1)
go func() {
defer wg.Done()
// Bounded concurrency using semaphore.
select {
case sem <- struct{}{}:
case <-rawCtx.Done():
return
}
defer func() { <-sem }()
// Add a timeout to prevent hanging streaming calls.
trackCtx, trackCancel := context.WithTimeout(
rawCtx, DefaultTrackPaymentTimeout,
)
defer trackCancel()
stream, err := client.TrackPaymentV2(
trackCtx, &routerrpc.TrackPaymentRequest{
PaymentHash: entry.Hash[:],
NoInflightUpdates: false,
},
)
if err != nil {
// Skip error logging and recording if the
// parent context was cancelled or timed out.
if rawCtx.Err() != nil {
return
}
sErr, ok := status.FromError(err)
if ok && sErr.Code() == codes.NotFound {
log.Warnf("Payment %x not found in "+
"lnd, creating placeholder: %v",
entry.Hash[:], err)
mu.Lock()
payments[entry.Hash] =
notFoundPayment(entry.Hash)
mu.Unlock()
return
}
log.Errorf("Failed to track payment %x: %v",
entry.Hash[:], err)
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
payment, err := stream.Recv()
if err != nil {
// Skip error logging and recording if the
// parent context was cancelled or timed out.
if rawCtx.Err() != nil {
return
}
sErr, ok := status.FromError(err)
if ok && sErr.Code() == codes.NotFound {
log.Warnf("Payment %x not found in "+
"lnd, creating placeholder: %v",
entry.Hash[:], err)
mu.Lock()
payments[entry.Hash] =
notFoundPayment(entry.Hash)
mu.Unlock()
return
}
log.Errorf("Failed to receive payment "+
"update for %x: %v", entry.Hash[:], err)
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
mu.Lock()
payments[entry.Hash] = payment
mu.Unlock()
}()
}
wg.Wait()
// Return immediately if the parent context was cancelled or timed out.
if err := rawCtx.Err(); err != nil {
return nil, err
}
// If there were any errors tracking the payments, return the
// error.
if len(errs) > 0 {
return nil, fmt.Errorf(
"failed to fetch payment details: %w", errs[0],
)
}
var finalPayments []*lnrpc.Payment
for _, entry := range paymentsFromStore {
if p, ok := payments[entry.Hash]; ok {
finalPayments = append(finalPayments, p)
}
}
var firstIndexOffset, lastIndexOffset uint64
if len(finalPayments) > 0 {
firstIndexOffset = offset
lastIndexOffset = offset + uint64(len(finalPayments))
}
return &litrpc.AccountPaymentsResponse{
Payments: finalPayments,
FirstIndexOffset: firstIndexOffset,
LastIndexOffset: lastIndexOffset,
TotalNumPayments: totalNumPayments,
}, nil
}
// notFoundPayment creates a placeholder payment for a desynced entry.
func notFoundPayment(hash lntypes.Hash) *lnrpc.Payment {
return &lnrpc.Payment{
PaymentHash: hex.EncodeToString(hash[:]),
Status: lnrpc.Payment_UNKNOWN,
FailureReason: lnrpc.
PaymentFailureReason_FAILURE_REASON_NONE,
}
}

View file

@ -19,9 +19,37 @@ import (
)
// Config holds the configuration options for the accounts service.
//
//nolint:ll
type Config struct {
// Disable will disable the accounts service if set.
Disable bool `long:"disable" description:"disable the accounts service"`
// MaxPaymentSizeMsat, when greater than zero, is the maximum value (in
// millisatoshis) that a single account payment may have. Payments whose
// amount exceeds this cap are rejected by the account interceptor. This
// provides a guard rail against a compromised or misbehaving account
// macaroon draining its balance in a single large payment.
//
// It defaults to 0, which disables the cap and preserves the historical
// behaviour of allowing payments up to the full account balance.
MaxPaymentSizeMsat uint64 `long:"max-payment-size-msat" description:"the maximum total amount in millisatoshis, including fees, that a single account payment may debit; 0 (the default value) disables the cap"`
// CheckChannelBalance, when set, makes the accounts service reject
// account balance allocations (new accounts, administrative credits and
// administrative balance increases) that would push the sum of all
// account balances above the node's available total local (outbound)
// channel balance.
//
// It defaults to false to preserve the historical behaviour, where the
// operator is trusted to manage over-provisioning themselves; accounts
// can legitimately be created before channels are opened or funded.
//
// NOTE: The total channel balance may still decrease below the already
// allocated account balance. This can occur if the node operator
// decreases the total channel balance through non-account related
// activity.
CheckChannelBalance bool `long:"check-channel-balance" description:"reject account balance allocations that would push the sum of all account balances above the node's available local channel balance. Note that the total channel balance can still decrease below the already allocated account balance. This can occur if the node operator decreases the total channel balance through non-account related activity."`
}
// trackedPayment is a struct that holds all information that identifies a
@ -54,7 +82,17 @@ type InterceptorService struct {
store Store
routerClient lndclient.RouterClient
routerClient lndclient.RouterClient
lightningClient lndclient.LightningClient
// checkChannelBalance, when set, makes the service reject account
// balance allocations that would exceed the node's available local
// channel balance. See Config.CheckChannelBalance.
checkChannelBalance bool
// maxPaymentSize, when greater than zero, is the maximum value that a
// single account payment may have. See Config.MaxPaymentSizeMsat.
maxPaymentSize lnwire.MilliSatoshi
mainCtx context.Context
contextCancel fn.Option[context.CancelFunc]
@ -77,12 +115,33 @@ type InterceptorService struct {
isEnabled bool
}
// ServiceOption is a functional option that can be used to modify the behaviour
// of the InterceptorService.
type ServiceOption func(*InterceptorService)
// WithMaxPaymentSize sets the maximum value that a single account payment may
// have. A value of zero (the default) disables the cap.
func WithMaxPaymentSize(maxPaymentSize lnwire.MilliSatoshi) ServiceOption {
return func(s *InterceptorService) {
s.maxPaymentSize = maxPaymentSize
}
}
// WithChannelBalanceCheck enables validation that the sum of all account
// balances never exceeds the node's available local channel balance when
// allocating account balances (new accounts, credits and balance increases).
func WithChannelBalanceCheck() ServiceOption {
return func(s *InterceptorService) {
s.checkChannelBalance = true
}
}
// NewService returns a service backed by the macaroon Bolt DB stored in the
// passed-in directory.
func NewService(store Store, errCallback func(error)) (*InterceptorService,
error) {
func NewService(store Store, errCallback func(error),
opts ...ServiceOption) (*InterceptorService, error) {
return &InterceptorService{
s := &InterceptorService{
store: store,
invoiceToAccount: make(map[lntypes.Hash]AccountID),
pendingPayments: make(map[lntypes.Hash]*trackedPayment),
@ -90,7 +149,13 @@ func NewService(store Store, errCallback func(error)) (*InterceptorService,
mainErrCallback: errCallback,
quit: make(chan struct{}),
isEnabled: false,
}, nil
}
for _, opt := range opts {
opt(s)
}
return s, nil
}
// Start starts the account service and its interceptor capability.
@ -103,7 +168,8 @@ func (s *InterceptorService) Start(ctx context.Context,
s.contextCancel = fn.Some(contextCancel)
s.routerClient = routerClient
s.checkers = NewAccountChecker(s, params)
s.lightningClient = lightningClient
s.checkers = NewAccountChecker(s, params, s.maxPaymentSize)
s.isEnabled = true
@ -291,9 +357,22 @@ func (s *InterceptorService) NewAccount(ctx context.Context,
expirationDate time.Time, label string) (*OffChainBalanceAccount,
error) {
availableChannelBalance, err := s.fetchChannelBalance(ctx)
if err != nil {
return nil, err
}
s.Lock()
defer s.Unlock()
// Make sure allocating this account's balance doesn't over-provision
// the node's available local channel balance (no-op unless enabled).
if err := s.checkChannelBalanceReservationUnsafe(
ctx, balance, availableChannelBalance,
); err != nil {
return nil, err
}
return s.store.NewAccount(ctx, balance, expirationDate, label)
}
@ -304,6 +383,29 @@ func (s *InterceptorService) UpdateAccount(ctx context.Context,
expirationDate int64, newLabel string) (*OffChainBalanceAccount,
error) {
// Convert the requested account balance to millisatoshis. An empty
// option signals that the stored balance should not be updated.
var (
balance fn.Option[int64]
availableChannelBalance lnwire.MilliSatoshi
err error
)
if accountBalance >= 0 {
// If the new account balance was set, parse it as
// millisatoshis. A value of -1 signals "don't update the
// balance".
balance = fn.Some(int64(
// Convert from satoshis to millisatoshis for storage.
lnwire.NewMSatFromSatoshis(accountBalance),
))
availableChannelBalance, err = s.fetchChannelBalance(ctx)
if err != nil {
return nil, err
}
}
s.Lock()
defer s.Unlock()
@ -327,14 +429,6 @@ func (s *InterceptorService) UpdateAccount(ctx context.Context,
expiry = fn.Some(time.Time{})
}
// If the new account balance was set, parse it as millisatoshis. A
// value of -1 signals "don't update the balance".
var balance fn.Option[int64]
if accountBalance >= 0 {
// Convert from satoshis to millisatoshis for storage.
balance = fn.Some(int64(accountBalance) * 1000)
}
// If a new label was provided, wrap it in an option. An empty
// string is treated as "no update requested" because protobuf
// cannot distinguish an absent field from the zero value "".
@ -343,10 +437,32 @@ func (s *InterceptorService) UpdateAccount(ctx context.Context,
label = fn.Some(newLabel)
}
// If the balance is being increased, make sure the increase doesn't
// over-provision the node's available local channel balance (no-op
// unless the `checkChannelBalance` option is enabled).
if balance.IsSome() && s.checkChannelBalance {
acct, err := s.store.Account(ctx, accountID)
if err != nil {
return nil, fmt.Errorf("fetching account: %w", err)
}
newBalanceMsat := balance.UnwrapOr(0)
requestedBalanceIncrease := newBalanceMsat - acct.CurrentBalance
if requestedBalanceIncrease > 0 {
err := s.checkChannelBalanceReservationUnsafe(
ctx,
lnwire.MilliSatoshi(requestedBalanceIncrease),
availableChannelBalance,
)
if err != nil {
return nil, err
}
}
}
// Create the actual account in the macaroon account store.
err := s.store.UpdateAccount(
ctx, accountID, balance, expiry, label,
)
err = s.store.UpdateAccount(ctx, accountID, balance, expiry, label)
if err != nil {
return nil, fmt.Errorf("unable to update account: %w", err)
}
@ -359,6 +475,11 @@ func (s *InterceptorService) CreditAccount(ctx context.Context,
accountID AccountID,
amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) {
availableChannelBalance, err := s.fetchChannelBalance(ctx)
if err != nil {
return nil, err
}
s.Lock()
defer s.Unlock()
@ -370,8 +491,16 @@ func (s *InterceptorService) CreditAccount(ctx context.Context,
return nil, ErrAccountServiceDisabled
}
// Make sure crediting this amount doesn't over-provision the node's
// available local channel balance (no-op unless enabled).
if err := s.checkChannelBalanceReservationUnsafe(
ctx, amount, availableChannelBalance,
); err != nil {
return nil, err
}
// Credit the account in the DB.
err := s.store.CreditAccount(ctx, accountID, amount)
err = s.store.CreditAccount(ctx, accountID, amount)
if err != nil {
return nil, fmt.Errorf("unable to credit account: %w", err)
}
@ -468,13 +597,82 @@ func (s *InterceptorService) CheckBalance(ctx context.Context, id AccountID,
}
availableAmount := calcAvailableAccountBalance(account)
if availableAmount < int64(requiredBalance) {
// Ensure that the availableAmount is greater than the requiredBalance.
if availableAmount < 0 ||
lnwire.MilliSatoshi(availableAmount) < requiredBalance {
return ErrAccBalanceInsufficient
}
return nil
}
// fetchChannelBalance fetches the node's available local channel balance. It
// returns zero without querying the Lightning client if the
// `checkChannelBalance` option is not enabled.
//
// This network call must happen before acquiring the service lock so an
// unavailable Lightning client doesn't block unrelated account operations.
func (s *InterceptorService) fetchChannelBalance(ctx context.Context) (
lnwire.MilliSatoshi, error) {
if !s.checkChannelBalance {
return 0, nil
}
if s.lightningClient == nil {
return 0, errors.New("cannot check channel balance: " +
"lightning client is not available")
}
chanBalance, err := s.lightningClient.ChannelBalance(ctx)
if err != nil {
return 0, fmt.Errorf("error querying channel balance: %w", err)
}
return lnwire.NewMSatFromSatoshis(chanBalance.Balance), nil
}
// checkChannelBalanceReservationUnsafe ensures that increasing the total
// allocated account balance by the requested balance increase would not push
// the sum of all account balances above the node's available local (outbound)
// channel balance.
//
// The function is a no-op if the `checkChannelBalance` config flag isn't set.
//
// NOTE: the service lock MUST be held when calling this method.
func (s *InterceptorService) checkChannelBalanceReservationUnsafe(
ctx context.Context, requestedBalanceIncrease lnwire.MilliSatoshi,
availableChannelBalance lnwire.MilliSatoshi) error {
// If the `checkChannelBalance` config flag isn't set, the function is a
// no-op.
if !s.checkChannelBalance {
return nil
}
accounts, err := s.store.Accounts(ctx)
if err != nil {
return fmt.Errorf("error listing accounts: %w", err)
}
// Calculate the total account balance after applying the requested
// allocation, then ensure the node's local balance can cover it.
var allocated int64
for _, acct := range accounts {
allocated += acct.CurrentBalance
}
if allocated+int64(requestedBalanceIncrease) >
int64(availableChannelBalance) {
return ErrBalanceReservationExceeded
}
return nil
}
func calcAvailableAccountBalance(account *OffChainBalanceAccount) int64 {
var inFlightAmt int64
for _, payment := range account.Payments {
@ -654,15 +852,12 @@ func (s *InterceptorService) TrackPayment(ctx context.Context, id AccountID,
// option to ensure that we return an error if the payment has already
// succeeded. We can then match on the ErrAlreadySucceeded error and
// exit early if it is returned.
//
// Additionally, we ensure that the account's pending amount is
// preserved while the payment is in-flight.
opts := []UpsertPaymentOption{
WithErrIfAlreadySucceeded(),
}
// There is a case where the passed in fullAmt is zero but the pending
// amount is not. In that case, we should not overwrite the pending
// amount.
if fullAmt == 0 {
opts = append(opts, WithPendingAmount())
WithPendingAmount(),
}
known, err := s.store.UpsertAccountPayment(

View file

@ -6,6 +6,7 @@ import (
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/clock"
invpkg "github.com/lightningnetwork/lnd/invoices"
@ -35,6 +36,29 @@ type mockLnd struct {
invoiceSubscriptionErr error
invoiceErrChan chan error
invoiceChan chan *lndclient.Invoice
// channelBalance is the local channel balance reported by the mocked
// ChannelBalance call.
channelBalance btcutil.Amount
channelBalanceCalled chan struct{}
channelBalanceRelease chan struct{}
}
// ChannelBalance returns the mocked local channel balance.
func (m *mockLnd) ChannelBalance(_ context.Context) (*lndclient.ChannelBalance,
error) {
if m.channelBalanceCalled != nil {
m.channelBalanceCalled <- struct{}{}
}
if m.channelBalanceRelease != nil {
<-m.channelBalanceRelease
}
return &lndclient.ChannelBalance{
Balance: m.channelBalance,
}, nil
}
func newMockLnd() *mockLnd {
@ -876,3 +900,146 @@ func TestAccountService(t *testing.T) {
func assertEventually(t *testing.T, predicate func() bool) {
require.Eventually(t, predicate, testTimeout, testInterval)
}
// TestChannelBalanceReservationCheck tests that, when the channel-balance check
// is enabled, the accounts service rejects balance allocations that would push
// the sum of all account balances above the node's available local channel
// balance (issue #495).
func TestChannelBalanceReservationCheck(t *testing.T) {
t.Parallel()
ctx := context.Background()
lndMock := newMockLnd()
// The node has a 10 sat (10_000 msat) local channel balance.
lndMock.channelBalance = 10
routerMock := newMockRouter()
errFunc := func(err error) {
lndMock.mainErrChan <- err
}
store := NewTestDB(t, clock.NewTestClock(time.Now()))
service, err := NewService(store, errFunc, WithChannelBalanceCheck())
require.NoError(t, err)
err = service.Start(ctx, lndMock, routerMock, chainParams)
require.NoError(t, err)
t.Cleanup(func() {
_ = service.Stop()
})
// Allocating an account within the node's local balance works.
acct, err := service.NewAccount(ctx, 6000, testExpiration, "a")
require.NoError(t, err)
// A second account that would push the total allocation over the
// available balance is rejected.
_, err = service.NewAccount(ctx, 5000, testExpiration, "b")
require.ErrorIs(t, err, ErrBalanceReservationExceeded)
// Crediting the existing account beyond the limit is rejected too.
_, err = service.CreditAccount(ctx, acct.ID, 5000)
require.ErrorIs(t, err, ErrBalanceReservationExceeded)
// But a credit that stays within the limit succeeds.
updated, err := service.CreditAccount(ctx, acct.ID, 3000)
require.NoError(t, err)
require.EqualValues(t, 9000, updated.CurrentBalance)
// Increasing the account balance directly is subject to the same check.
_, err = service.UpdateAccount(ctx, acct.ID, 11, -1, "")
require.ErrorIs(t, err, ErrBalanceReservationExceeded)
updated, err = service.UpdateAccount(ctx, acct.ID, 10, -1, "")
require.NoError(t, err)
require.EqualValues(t, 10_000, updated.CurrentBalance)
// With the check disabled (the default), over-provisioning is allowed.
plainService, err := NewService(store, errFunc)
require.NoError(t, err)
err = plainService.Start(ctx, lndMock, routerMock, chainParams)
require.NoError(t, err)
t.Cleanup(func() {
_ = plainService.Stop()
})
_, err = plainService.NewAccount(ctx, 999999, testExpiration, "c")
require.NoError(t, err)
}
// TestChannelBalanceReservationRequiresLnd tests that the optional channel
// balance check fails closed if the service has no Lightning client.
func TestChannelBalanceReservationRequiresLnd(t *testing.T) {
t.Parallel()
store := NewTestDB(t, clock.NewTestClock(time.Now()))
service, err := NewService(
store, func(error) {}, WithChannelBalanceCheck(),
)
require.NoError(t, err)
_, err = service.NewAccount(
context.Background(), 1000, testExpiration, "account",
)
require.ErrorContains(t, err, "lightning client is not available")
}
// TestChannelBalanceRequestDoesNotHoldLock tests that a slow ChannelBalance
// RPC does not block unrelated account operations.
func TestChannelBalanceRequestDoesNotHoldLock(t *testing.T) {
t.Parallel()
ctx := context.Background()
lndMock := newMockLnd()
lndMock.channelBalance = 10
lndMock.channelBalanceCalled = make(chan struct{}, 1)
lndMock.channelBalanceRelease = make(chan struct{})
store := NewTestDB(t, clock.NewTestClock(time.Now()))
service, err := NewService(
store, func(error) {}, WithChannelBalanceCheck(),
)
require.NoError(t, err)
require.NoError(t, service.Start(
ctx, lndMock, newMockRouter(), chainParams,
))
t.Cleanup(func() {
_ = service.Stop()
})
accountErr := make(chan error, 1)
go func() {
_, err := service.NewAccount(
ctx, 1000, testExpiration, "account",
)
accountErr <- err
}()
select {
case <-lndMock.channelBalanceCalled:
case <-time.After(testTimeout):
t.Fatal("ChannelBalance was not called")
}
type accountsResult struct {
accounts []*OffChainBalanceAccount
err error
}
resultChan := make(chan accountsResult, 1)
go func() {
accounts, err := service.Accounts(ctx)
resultChan <- accountsResult{accounts: accounts, err: err}
}()
select {
case result := <-resultChan:
require.NoError(t, result.err)
require.Empty(t, result.accounts)
case <-time.After(testTimeout):
close(lndMock.channelBalanceRelease)
t.Fatal("account read blocked by ChannelBalance")
}
close(lndMock.channelBalanceRelease)
require.NoError(t, <-accountErr)
}

View file

@ -9,6 +9,7 @@ import (
"math"
"os"
"path/filepath"
"sort"
"time"
"github.com/btcsuite/btcwallet/walletdb"
@ -445,6 +446,60 @@ func (s *BoltStore) DeleteAccountPayment(_ context.Context, id AccountID,
return s.updateAccount(id, update)
}
// ListAccountPayments returns a paginated list of payments
// associated with the given account, sorted in ascending lexicographical
// order of their payment hash.
func (s *BoltStore) ListAccountPayments(ctx context.Context, id AccountID,
offset, limit int32) ([]*AccountPaymentEntry, error) {
account, err := s.Account(ctx, id)
if err != nil {
return nil, err
}
var matchedPayments []*AccountPaymentEntry
for hash, entry := range account.Payments {
matchedPayments = append(matchedPayments, &AccountPaymentEntry{
Hash: hash,
PaymentEntry: entry,
})
}
// Sort target hashes lexicographically to ensure pagination is
// deterministic across all store backends.
sort.Slice(matchedPayments, func(i, j int) bool {
hashI := matchedPayments[i].Hash[:]
hashJ := matchedPayments[j].Hash[:]
return bytes.Compare(hashI, hashJ) < 0
})
// Apply pagination limits and offsets.
total := int32(len(matchedPayments))
if offset >= total {
return nil, nil
}
end := offset + limit
if limit < 0 || end > total {
end = total
}
return matchedPayments[offset:end], nil
}
// CountAccountPayments returns the total number of payments associated with
// the given account.
func (s *BoltStore) CountAccountPayments(ctx context.Context,
id AccountID) (uint64, error) {
account, err := s.Account(ctx, id)
if err != nil {
return 0, err
}
return uint64(len(account.Payments)), nil
}
func (s *BoltStore) updateAccount(id AccountID,
updateFn func(*OffChainBalanceAccount) error) error {

View file

@ -33,8 +33,6 @@ const (
//
//nolint:ll
type SQLQueries interface {
sqldb.BaseQuerier
AddAccountInvoice(ctx context.Context, arg sqlc.AddAccountInvoiceParams) error
DeleteAccount(ctx context.Context, id int64) error
DeleteAccountPayment(ctx context.Context, arg sqlc.DeleteAccountPaymentParams) error
@ -48,6 +46,8 @@ type SQLQueries interface {
ListAllAccountPayments(ctx context.Context) ([]sqlc.AccountPayment, error)
ListAccountInvoices(ctx context.Context, id int64) ([]sqlc.AccountInvoice, error)
ListAccountPayments(ctx context.Context, id int64) ([]sqlc.AccountPayment, error)
AccountPaymentsPaginated(ctx context.Context, arg sqlc.AccountPaymentsPaginatedParams) ([]sqlc.AccountPayment, error)
CountAccountPayments(ctx context.Context, accountID int64) (int64, error)
ListAllAccounts(ctx context.Context) ([]sqlc.Account, error)
SetAccountIndex(ctx context.Context, arg sqlc.SetAccountIndexParams) error
UpdateAccountBalance(ctx context.Context, arg sqlc.UpdateAccountBalanceParams) (int64, error)
@ -79,7 +79,7 @@ type SQLStore struct {
clock clock.Clock
}
type sqlQueriesExecutor[T sqldb.BaseQuerier] struct {
type sqlQueriesExecutor[T any] struct {
*sqldb.TransactionExecutor[T]
SQLQueries
@ -781,6 +781,80 @@ func (s *SQLStore) DeleteAccountPayment(ctx context.Context, alias AccountID,
}, sqldb.NoOpReset)
}
// ListAccountPayments returns a paginated list of payments
// associated with the given account, sorted in ascending lexicographical
// order of their payment hash.
func (s *SQLStore) ListAccountPayments(ctx context.Context, alias AccountID,
offset, limit int32) ([]*AccountPaymentEntry, error) {
var (
readTxOpts = db.NewQueryReadTx()
payments []*AccountPaymentEntry
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
id, err := getAccountIDByAlias(ctx, db, alias)
if err != nil {
return err
}
var dbPayments []sqlc.AccountPayment
dbPayments, err = db.AccountPaymentsPaginated(
ctx, sqlc.AccountPaymentsPaginatedParams{
AccountID: id,
Limit: limit,
Offset: offset,
},
)
if err != nil {
return err
}
payments = make([]*AccountPaymentEntry, len(dbPayments))
for i, p := range dbPayments {
var hash lntypes.Hash
copy(hash[:], p.Hash)
payments[i] = &AccountPaymentEntry{
Hash: hash,
PaymentEntry: &PaymentEntry{
Status: lnrpc.Payment_PaymentStatus(
p.Status,
),
FullAmount: lnwire.MilliSatoshi(
p.FullAmountMsat,
),
},
}
}
return nil
}, sqldb.NoOpReset)
return payments, err
}
// CountAccountPayments returns the total number of payments associated with
// the given account.
func (s *SQLStore) CountAccountPayments(ctx context.Context,
alias AccountID) (uint64, error) {
var (
readTxOpts = db.NewQueryReadTx()
count int64
)
err := s.db.ExecTx(ctx, &readTxOpts, func(db SQLQueries) error {
id, err := getAccountIDByAlias(ctx, db, alias)
if err != nil {
return err
}
count, err = db.CountAccountPayments(ctx, id)
return err
}, sqldb.NoOpReset)
return uint64(count), err
}
// LastIndexes returns the last invoice add and settle index or
// ErrNoInvoiceIndexKnown if no indexes are known yet.
//

View file

@ -848,3 +848,135 @@ func TestLastInvoiceIndexes(t *testing.T) {
require.EqualValues(t, 7, add)
require.EqualValues(t, 99, settle)
}
// TestCheckLabel ensures that only labels that could be mistaken for a hex
// encoded account ID are rejected, while all other labels (including the empty
// label) are accepted.
func TestCheckLabel(t *testing.T) {
t.Parallel()
tests := []struct {
name string
label string
expectErr bool
}{{
name: "empty label is allowed",
label: "",
}, {
name: "plain text label is allowed",
label: "my account",
}, {
name: "short hex label is allowed",
label: "00112233",
}, {
name: "non-hex label with account ID length is allowed",
// 16 characters long, matching an encoded account ID, but not
// valid hex.
label: "zzzzzzzzzzzzzzzz",
}, {
name: "lowercase hex label with account ID length is rejected",
// 16 characters of valid hex, exactly the length of an encoded
// account ID.
label: "0011223344556677",
expectErr: true,
}, {
name: "uppercase hex label with account ID length is rejected",
// hex.DecodeString also accepts uppercase digits, so this must
// be rejected as well.
label: "00112233445566AA",
expectErr: true,
}}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := checkLabel(tc.label)
if tc.expectErr {
require.ErrorContains(
t, err, "is not allowed as it can be "+
"mistaken",
)
return
}
require.NoError(t, err)
})
}
}
// TestListAccountPayments tests listing and counting payment entries associated
// with a given account.
func TestListAccountPayments(t *testing.T) {
t.Parallel()
ctx := context.Background()
store := NewTestDB(t, clock.NewTestClock(time.Now()))
// Listing payments for non-existent account should fail.
_, err := store.ListAccountPayments(
ctx, AccountID{}, 0, 0,
)
require.ErrorIs(t, err, ErrAccNotFound)
acct, err := store.NewAccount(
ctx, 10000, time.Time{}, "payment-list",
)
require.NoError(t, err)
// Initially, there should be no payments.
payments, err := store.ListAccountPayments(
ctx, acct.ID, 0, 0,
)
require.NoError(t, err)
require.Empty(t, payments)
count, err := store.CountAccountPayments(ctx, acct.ID)
require.NoError(t, err)
require.Zero(t, count)
// Add 3 payments.
hash1 := lntypes.Hash{1}
hash2 := lntypes.Hash{2}
hash3 := lntypes.Hash{3}
_, err = store.UpsertAccountPayment(
ctx, acct.ID, hash1, 100, lnrpc.Payment_IN_FLIGHT,
)
require.NoError(t, err)
_, err = store.UpsertAccountPayment(
ctx, acct.ID, hash2, 200, lnrpc.Payment_SUCCEEDED,
)
require.NoError(t, err)
_, err = store.UpsertAccountPayment(
ctx, acct.ID, hash3, 300, lnrpc.Payment_FAILED,
)
require.NoError(t, err)
// Test counting all payments.
count, err = store.CountAccountPayments(ctx, acct.ID)
require.NoError(t, err)
require.EqualValues(t, 3, count)
// List all payments in default order (ascending by hash).
payments, err = store.ListAccountPayments(
ctx, acct.ID, 0, 3,
)
require.NoError(t, err)
require.Len(t, payments, 3)
require.Equal(t, hash1, payments[0].Hash)
require.Equal(t, hash2, payments[1].Hash)
require.Equal(t, hash3, payments[2].Hash)
// Test offset and limit.
payments, err = store.ListAccountPayments(
ctx, acct.ID, 1, 1,
)
require.NoError(t, err)
require.Len(t, payments, 1)
require.Equal(t, hash2, payments[0].Hash)
}

View file

@ -22,6 +22,7 @@
"@emotion/react": "11.4.0",
"@emotion/styled": "11.3.0",
"@improbable-eng/grpc-web": "0.14.0",
"@remix-run/router": "1.23.3",
"@types/react-collapse": "^5.0.1",
"big.js": "6.1.1",
"bootstrap": "4.6.1",
@ -31,7 +32,7 @@
"date-fns": "2.14.0",
"debug": "4.3.1",
"file-saver": "2.0.2",
"http-proxy-middleware": "2.0.9",
"http-proxy-middleware": "2.0.10",
"i18next": "19.5.1",
"i18next-browser-languagedetector": "5.0.0",
"lodash": "4.18.1",
@ -48,7 +49,7 @@
"react-collapse": "^5.1.1",
"react-dom": "17.0.2",
"react-i18next": "13.5.0",
"react-router-dom": "^6.3.0",
"react-router-dom": "6.30.4",
"react-toastify": "6.0.6",
"react-virtualized": "9.21.2",
"reactour": "1.18.0",
@ -56,13 +57,13 @@
"styled-components": "5.1.1"
},
"devDependencies": {
"@storybook/addon-actions": "7.5.3",
"@storybook/addon-docs": "7.5.3",
"@storybook/addon-links": "7.5.3",
"@storybook/addons": "7.5.3",
"@storybook/preset-create-react-app": "7.5.3",
"@storybook/react": "7.5.3",
"@storybook/react-webpack5": "7.5.3",
"@storybook/addon-actions": "7.6.24",
"@storybook/addon-docs": "7.6.24",
"@storybook/addon-links": "7.6.24",
"@storybook/addons": "7.6.24",
"@storybook/preset-create-react-app": "7.6.24",
"@storybook/react": "7.6.24",
"@storybook/react-webpack5": "7.6.24",
"@testing-library/jest-dom": "5.11.5",
"@testing-library/react": "11.1.1",
"@testing-library/user-event": "12.2.0",
@ -71,13 +72,11 @@
"@types/debug": "4.1.5",
"@types/file-saver": "2.0.1",
"@types/google-protobuf": "3.15.10",
"@types/history": "4.7.6",
"@types/jest": "27.4.1",
"@types/lodash": "4.14.157",
"@types/node": "14.0.14",
"@types/react": "17.0.13",
"@types/react-dom": "17.0.8",
"@types/react-router-dom": "^5.3.3",
"@types/react-virtualized": "9.21.10",
"@types/reactour": "1.17.1",
"@types/semver": "^7.3.9",
@ -94,14 +93,23 @@
"protoc-gen-js": "3.21.2",
"react-scripts": "^5.0.1",
"sass": "1.43.4",
"storybook": "7.5.3",
"storybook": "7.6.24",
"ts-protoc-gen": "0.15.0",
"typescript": "4.1.6",
"webpack": "5.89.0"
"typescript": "4.9.5",
"webpack": "5.109.2"
},
"resolutions": {
"strip-ansi": "6.0.1",
"@tootallnate/once": "2.0.1",
"esbuild": "0.25.12",
"http-proxy-middleware": "2.0.10",
"jackspeak": "2.1.1",
"nth-check": "2.1.1",
"resolve-url-loader": "5.0.0",
"serialize-javascript": "7.1.0",
"strip-ansi": "6.0.1",
"svgo": "2.8.3",
"tar": "7.5.22",
"uuid": "11.1.1",
"wrap-ansi": "7.0.0"
},
"eslintConfig": {

View file

@ -124,6 +124,14 @@ const sanitize = async () => {
'import "reservation.proto"',
'import "swapserverrpc/reservation.proto"',
);
content = content.replace(
'import "lnrpc/lightning.proto"',
'import "lnd.proto"',
);
content = content.replace(
'import "proto/lnd.proto";',
'import "lnd.proto";',
);
await fs.writeFile(path, content);
}
};

View file

@ -36,6 +36,16 @@ describe('AppView', () => {
expect(store.alerts.size).toBe(1);
});
it('should handle values thrown that are not Errors', () => {
// Nothing in the language guarantees a thrown value is an Error, and
// showing the user an empty alert is worse than showing the raw value.
store.handleError('something went wrong', 'title');
expect(store.alerts.size).toBe(1);
const alert = values(store.alerts)[0];
expect(alert.message).toBe('something went wrong');
expect(alert.title).toBe('title');
});
it('should handle authentication errors', () => {
rootStore.authStore.authenticated = true;
expect(store.alerts.size).toBe(0);

View file

@ -111,7 +111,7 @@ const AuthPage: React.FC = () => {
try {
await store.authStore.login(pass);
} catch (err) {
setError(err.message);
setError(err instanceof Error ? err.message : String(err));
const errors = store.authStore.errors;
setErrorDetailLit(errors.litDetail);
setErrorDetailLnd(errors.lndDetail);

View file

@ -1,6 +1,6 @@
import { autorun, makeAutoObservable, runInAction } from 'mobx';
import { IS_DEV, IS_TEST, USE_SAMPLE_DATA } from 'config';
import { createBrowserHistory, History } from 'history';
import { createBrowserHistory, History } from '@remix-run/router';
import AppStorage from 'util/appStorage';
import CsvExporter from 'util/csv';
import { actionLog, Logger } from 'util/log';
@ -217,7 +217,9 @@ export const createStore = (grpcClient?: GrpcClient, appStorage?: AppStorage) =>
const poolApi = new PoolApi(grpc);
const litApi = new LitApi(grpc);
const csv = new CsvExporter();
const history = createBrowserHistory();
// v5Compat is required for listeners to be notified of push() and replace()
// calls, not just browser back/forward navigation.
const history = createBrowserHistory({ v5Compat: true });
const store = new Store(
lndApi,

View file

@ -126,7 +126,9 @@ export default class AuthStore {
this.setCredentials('');
this._store.log.error('connection failure');
this.errors = { mainErr: '', litDetail: '', lndDetail: '' };
throw new Error(await this.getErrMsg(error.message));
throw new Error(
await this.getErrMsg(error instanceof Error ? error.message : String(error)),
);
}
}

View file

@ -150,7 +150,7 @@ export default class BatchStore {
this.loading = false;
});
} catch (error) {
if (error.message !== 'batch snapshot not found') {
if (!(error instanceof Error) || error.message !== 'batch snapshot not found') {
this._store.appView.handleError(error, `Unable to fetch batch with id ${prevId}`);
}
}
@ -171,7 +171,7 @@ export default class BatchStore {
this._store.log.info('updated batchStore.markets', toJS(this.markets));
});
} catch (error) {
if (error.message === 'batch snapshot not found') return;
if (error instanceof Error && error.message === 'batch snapshot not found') return;
this._store.appView.handleError(error, 'Unable to fetch the latest batch');
}
}

View file

@ -1,5 +1,8 @@
import { makeAutoObservable, runInAction } from 'mobx';
import { History, Location } from 'history';
import { History, Location } from '@remix-run/router';
/** the listener callback accepted by the history object */
type Listener = Parameters<History['listen']>[0];
export default class RouterStore {
/** the history object */
@ -11,13 +14,31 @@ export default class RouterStore {
constructor(history: History) {
makeAutoObservable(this, { history: false }, { deep: false, autoBind: true });
this.history = history;
this.location = history.location;
history.listen(({ location }) => {
// The router's history implementation only accepts a single active
// listener, but both this store and the <HistoryRouter> component need to
// observe navigation. Subscribe once here and fan the updates out to any
// additional listeners.
const listeners = new Set<Listener>();
history.listen(update => {
runInAction(() => {
this.location = location;
this.location = update.location;
});
listeners.forEach(listener => listener(update));
});
// Expose a history that hands out fan-out subscriptions instead of the
// single underlying one. A proxy is used so that the `location` and
// `action` getters continue to read through to the real history object.
this.history = new Proxy(history, {
get: (target, prop, receiver) => {
if (prop !== 'listen') return Reflect.get(target, prop, receiver);
return (listener: Listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
},
});
}

View file

@ -210,13 +210,13 @@ export default class AppView {
}
/** handle errors by showing a notification and/or the auth screen */
handleError(error: Error, title?: string) {
handleError(error: unknown, title?: string) {
if (error instanceof AuthenticationError) {
// this will automatically redirect to the auth page
this._store.authStore.authenticated = false;
this.notify(l('authErrorMsg'), l('authErrorTitle'));
} else {
this.notify(error.message, title);
this.notify(error instanceof Error ? error.message : String(error), title);
}
}
}

View file

@ -2,6 +2,7 @@
// file: lit-accounts.proto
import * as jspb from "google-protobuf";
import * as lnd_pb from "./lnd_pb";
export class CreateAccountRequest extends jspb.Message {
getAccountBalance(): string;
@ -432,3 +433,71 @@ export namespace AccountIdentifier {
}
}
export class AccountPaymentsRequest extends jspb.Message {
hasAccount(): boolean;
clearAccount(): void;
getAccount(): AccountIdentifier | undefined;
setAccount(value?: AccountIdentifier): void;
getMaxPayments(): string;
setMaxPayments(value: string): void;
getIndexOffset(): string;
setIndexOffset(value: string): void;
getCountTotalPayments(): boolean;
setCountTotalPayments(value: boolean): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): AccountPaymentsRequest.AsObject;
static toObject(includeInstance: boolean, msg: AccountPaymentsRequest): AccountPaymentsRequest.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: AccountPaymentsRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): AccountPaymentsRequest;
static deserializeBinaryFromReader(message: AccountPaymentsRequest, reader: jspb.BinaryReader): AccountPaymentsRequest;
}
export namespace AccountPaymentsRequest {
export type AsObject = {
account?: AccountIdentifier.AsObject,
maxPayments: string,
indexOffset: string,
countTotalPayments: boolean,
}
}
export class AccountPaymentsResponse extends jspb.Message {
clearPaymentsList(): void;
getPaymentsList(): Array<lnd_pb.Payment>;
setPaymentsList(value: Array<lnd_pb.Payment>): void;
addPayments(value?: lnd_pb.Payment, index?: number): lnd_pb.Payment;
getFirstIndexOffset(): string;
setFirstIndexOffset(value: string): void;
getLastIndexOffset(): string;
setLastIndexOffset(value: string): void;
getTotalNumPayments(): string;
setTotalNumPayments(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): AccountPaymentsResponse.AsObject;
static toObject(includeInstance: boolean, msg: AccountPaymentsResponse): AccountPaymentsResponse.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: AccountPaymentsResponse, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): AccountPaymentsResponse;
static deserializeBinaryFromReader(message: AccountPaymentsResponse, reader: jspb.BinaryReader): AccountPaymentsResponse;
}
export namespace AccountPaymentsResponse {
export type AsObject = {
paymentsList: Array<lnd_pb.Payment.AsObject>,
firstIndexOffset: string,
lastIndexOffset: string,
totalNumPayments: string,
}
}

View file

@ -24,12 +24,16 @@ var global =
(function () { return this; }).call(null) ||
Function('return this')();
var lnd_pb = require('./lnd_pb.js');
goog.object.extend(proto, lnd_pb);
goog.exportSymbol('proto.litrpc.Account', null, global);
goog.exportSymbol('proto.litrpc.AccountIdentifier', null, global);
goog.exportSymbol('proto.litrpc.AccountIdentifier.IdentifierCase', null, global);
goog.exportSymbol('proto.litrpc.AccountInfoRequest', null, global);
goog.exportSymbol('proto.litrpc.AccountInvoice', null, global);
goog.exportSymbol('proto.litrpc.AccountPayment', null, global);
goog.exportSymbol('proto.litrpc.AccountPaymentsRequest', null, global);
goog.exportSymbol('proto.litrpc.AccountPaymentsResponse', null, global);
goog.exportSymbol('proto.litrpc.CreateAccountRequest', null, global);
goog.exportSymbol('proto.litrpc.CreateAccountResponse', null, global);
goog.exportSymbol('proto.litrpc.CreditAccountRequest', null, global);
@ -377,6 +381,48 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.litrpc.AccountIdentifier.displayName = 'proto.litrpc.AccountIdentifier';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.litrpc.AccountPaymentsRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.litrpc.AccountPaymentsRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.litrpc.AccountPaymentsRequest.displayName = 'proto.litrpc.AccountPaymentsRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.litrpc.AccountPaymentsResponse = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, proto.litrpc.AccountPaymentsResponse.repeatedFields_, null);
};
goog.inherits(proto.litrpc.AccountPaymentsResponse, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.litrpc.AccountPaymentsResponse.displayName = 'proto.litrpc.AccountPaymentsResponse';
}
@ -3351,4 +3397,495 @@ proto.litrpc.AccountIdentifier.prototype.hasLabel = function() {
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.litrpc.AccountPaymentsRequest.prototype.toObject = function(opt_includeInstance) {
return proto.litrpc.AccountPaymentsRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.litrpc.AccountPaymentsRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.litrpc.AccountPaymentsRequest.toObject = function(includeInstance, msg) {
var f, obj = {
account: (f = msg.getAccount()) && proto.litrpc.AccountIdentifier.toObject(includeInstance, f),
maxPayments: jspb.Message.getFieldWithDefault(msg, 2, "0"),
indexOffset: jspb.Message.getFieldWithDefault(msg, 3, "0"),
countTotalPayments: jspb.Message.getBooleanFieldWithDefault(msg, 4, false)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.litrpc.AccountPaymentsRequest}
*/
proto.litrpc.AccountPaymentsRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.litrpc.AccountPaymentsRequest;
return proto.litrpc.AccountPaymentsRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.litrpc.AccountPaymentsRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.litrpc.AccountPaymentsRequest}
*/
proto.litrpc.AccountPaymentsRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new proto.litrpc.AccountIdentifier;
reader.readMessage(value,proto.litrpc.AccountIdentifier.deserializeBinaryFromReader);
msg.setAccount(value);
break;
case 2:
var value = /** @type {string} */ (reader.readUint64String());
msg.setMaxPayments(value);
break;
case 3:
var value = /** @type {string} */ (reader.readUint64String());
msg.setIndexOffset(value);
break;
case 4:
var value = /** @type {boolean} */ (reader.readBool());
msg.setCountTotalPayments(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.litrpc.AccountPaymentsRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.litrpc.AccountPaymentsRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.litrpc.AccountPaymentsRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.litrpc.AccountPaymentsRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getAccount();
if (f != null) {
writer.writeMessage(
1,
f,
proto.litrpc.AccountIdentifier.serializeBinaryToWriter
);
}
f = message.getMaxPayments();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
2,
f
);
}
f = message.getIndexOffset();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
3,
f
);
}
f = message.getCountTotalPayments();
if (f) {
writer.writeBool(
4,
f
);
}
};
/**
* optional AccountIdentifier account = 1;
* @return {?proto.litrpc.AccountIdentifier}
*/
proto.litrpc.AccountPaymentsRequest.prototype.getAccount = function() {
return /** @type{?proto.litrpc.AccountIdentifier} */ (
jspb.Message.getWrapperField(this, proto.litrpc.AccountIdentifier, 1));
};
/**
* @param {?proto.litrpc.AccountIdentifier|undefined} value
* @return {!proto.litrpc.AccountPaymentsRequest} returns this
*/
proto.litrpc.AccountPaymentsRequest.prototype.setAccount = function(value) {
return jspb.Message.setWrapperField(this, 1, value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.litrpc.AccountPaymentsRequest} returns this
*/
proto.litrpc.AccountPaymentsRequest.prototype.clearAccount = function() {
return this.setAccount(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.litrpc.AccountPaymentsRequest.prototype.hasAccount = function() {
return jspb.Message.getField(this, 1) != null;
};
/**
* optional uint64 max_payments = 2;
* @return {string}
*/
proto.litrpc.AccountPaymentsRequest.prototype.getMaxPayments = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0"));
};
/**
* @param {string} value
* @return {!proto.litrpc.AccountPaymentsRequest} returns this
*/
proto.litrpc.AccountPaymentsRequest.prototype.setMaxPayments = function(value) {
return jspb.Message.setProto3StringIntField(this, 2, value);
};
/**
* optional uint64 index_offset = 3;
* @return {string}
*/
proto.litrpc.AccountPaymentsRequest.prototype.getIndexOffset = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "0"));
};
/**
* @param {string} value
* @return {!proto.litrpc.AccountPaymentsRequest} returns this
*/
proto.litrpc.AccountPaymentsRequest.prototype.setIndexOffset = function(value) {
return jspb.Message.setProto3StringIntField(this, 3, value);
};
/**
* optional bool count_total_payments = 4;
* @return {boolean}
*/
proto.litrpc.AccountPaymentsRequest.prototype.getCountTotalPayments = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false));
};
/**
* @param {boolean} value
* @return {!proto.litrpc.AccountPaymentsRequest} returns this
*/
proto.litrpc.AccountPaymentsRequest.prototype.setCountTotalPayments = function(value) {
return jspb.Message.setProto3BooleanField(this, 4, value);
};
/**
* List of repeated fields within this message type.
* @private {!Array<number>}
* @const
*/
proto.litrpc.AccountPaymentsResponse.repeatedFields_ = [1];
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.litrpc.AccountPaymentsResponse.prototype.toObject = function(opt_includeInstance) {
return proto.litrpc.AccountPaymentsResponse.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.litrpc.AccountPaymentsResponse} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.litrpc.AccountPaymentsResponse.toObject = function(includeInstance, msg) {
var f, obj = {
paymentsList: jspb.Message.toObjectList(msg.getPaymentsList(),
lnd_pb.Payment.toObject, includeInstance),
firstIndexOffset: jspb.Message.getFieldWithDefault(msg, 2, "0"),
lastIndexOffset: jspb.Message.getFieldWithDefault(msg, 3, "0"),
totalNumPayments: jspb.Message.getFieldWithDefault(msg, 4, "0")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.litrpc.AccountPaymentsResponse}
*/
proto.litrpc.AccountPaymentsResponse.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.litrpc.AccountPaymentsResponse;
return proto.litrpc.AccountPaymentsResponse.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.litrpc.AccountPaymentsResponse} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.litrpc.AccountPaymentsResponse}
*/
proto.litrpc.AccountPaymentsResponse.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = new lnd_pb.Payment;
reader.readMessage(value,lnd_pb.Payment.deserializeBinaryFromReader);
msg.addPayments(value);
break;
case 2:
var value = /** @type {string} */ (reader.readUint64String());
msg.setFirstIndexOffset(value);
break;
case 3:
var value = /** @type {string} */ (reader.readUint64String());
msg.setLastIndexOffset(value);
break;
case 4:
var value = /** @type {string} */ (reader.readUint64String());
msg.setTotalNumPayments(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.litrpc.AccountPaymentsResponse.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.litrpc.AccountPaymentsResponse.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.litrpc.AccountPaymentsResponse} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.litrpc.AccountPaymentsResponse.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getPaymentsList();
if (f.length > 0) {
writer.writeRepeatedMessage(
1,
f,
lnd_pb.Payment.serializeBinaryToWriter
);
}
f = message.getFirstIndexOffset();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
2,
f
);
}
f = message.getLastIndexOffset();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
3,
f
);
}
f = message.getTotalNumPayments();
if (parseInt(f, 10) !== 0) {
writer.writeUint64String(
4,
f
);
}
};
/**
* repeated lnrpc.Payment payments = 1;
* @return {!Array<!proto.lnrpc.Payment>}
*/
proto.litrpc.AccountPaymentsResponse.prototype.getPaymentsList = function() {
return /** @type{!Array<!proto.lnrpc.Payment>} */ (
jspb.Message.getRepeatedWrapperField(this, lnd_pb.Payment, 1));
};
/**
* @param {!Array<!proto.lnrpc.Payment>} value
* @return {!proto.litrpc.AccountPaymentsResponse} returns this
*/
proto.litrpc.AccountPaymentsResponse.prototype.setPaymentsList = function(value) {
return jspb.Message.setRepeatedWrapperField(this, 1, value);
};
/**
* @param {!proto.lnrpc.Payment=} opt_value
* @param {number=} opt_index
* @return {!proto.lnrpc.Payment}
*/
proto.litrpc.AccountPaymentsResponse.prototype.addPayments = function(opt_value, opt_index) {
return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.lnrpc.Payment, opt_index);
};
/**
* Clears the list making it empty but non-null.
* @return {!proto.litrpc.AccountPaymentsResponse} returns this
*/
proto.litrpc.AccountPaymentsResponse.prototype.clearPaymentsList = function() {
return this.setPaymentsList([]);
};
/**
* optional uint64 first_index_offset = 2;
* @return {string}
*/
proto.litrpc.AccountPaymentsResponse.prototype.getFirstIndexOffset = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "0"));
};
/**
* @param {string} value
* @return {!proto.litrpc.AccountPaymentsResponse} returns this
*/
proto.litrpc.AccountPaymentsResponse.prototype.setFirstIndexOffset = function(value) {
return jspb.Message.setProto3StringIntField(this, 2, value);
};
/**
* optional uint64 last_index_offset = 3;
* @return {string}
*/
proto.litrpc.AccountPaymentsResponse.prototype.getLastIndexOffset = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "0"));
};
/**
* @param {string} value
* @return {!proto.litrpc.AccountPaymentsResponse} returns this
*/
proto.litrpc.AccountPaymentsResponse.prototype.setLastIndexOffset = function(value) {
return jspb.Message.setProto3StringIntField(this, 3, value);
};
/**
* optional uint64 total_num_payments = 4;
* @return {string}
*/
proto.litrpc.AccountPaymentsResponse.prototype.getTotalNumPayments = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "0"));
};
/**
* @param {string} value
* @return {!proto.litrpc.AccountPaymentsResponse} returns this
*/
proto.litrpc.AccountPaymentsResponse.prototype.setTotalNumPayments = function(value) {
return jspb.Message.setProto3StringIntField(this, 4, value);
};
goog.object.extend(exports, proto.litrpc);

View file

@ -67,6 +67,15 @@ type AccountsRemoveAccount = {
readonly responseType: typeof lit_accounts_pb.RemoveAccountResponse;
};
type AccountsAccountPayments = {
readonly methodName: string;
readonly service: typeof Accounts;
readonly requestStream: false;
readonly responseStream: false;
readonly requestType: typeof lit_accounts_pb.AccountPaymentsRequest;
readonly responseType: typeof lit_accounts_pb.AccountPaymentsResponse;
};
export class Accounts {
static readonly serviceName: string;
static readonly CreateAccount: AccountsCreateAccount;
@ -76,6 +85,7 @@ export class Accounts {
static readonly ListAccounts: AccountsListAccounts;
static readonly AccountInfo: AccountsAccountInfo;
static readonly RemoveAccount: AccountsRemoveAccount;
static readonly AccountPayments: AccountsAccountPayments;
}
export type ServiceError = { message: string, code: number; metadata: grpc.Metadata }
@ -173,5 +183,14 @@ export class AccountsClient {
requestMessage: lit_accounts_pb.RemoveAccountRequest,
callback: (error: ServiceError|null, responseMessage: lit_accounts_pb.RemoveAccountResponse|null) => void
): UnaryResponse;
accountPayments(
requestMessage: lit_accounts_pb.AccountPaymentsRequest,
metadata: grpc.Metadata,
callback: (error: ServiceError|null, responseMessage: lit_accounts_pb.AccountPaymentsResponse|null) => void
): UnaryResponse;
accountPayments(
requestMessage: lit_accounts_pb.AccountPaymentsRequest,
callback: (error: ServiceError|null, responseMessage: lit_accounts_pb.AccountPaymentsResponse|null) => void
): UnaryResponse;
}

View file

@ -73,6 +73,15 @@ Accounts.RemoveAccount = {
responseType: lit_accounts_pb.RemoveAccountResponse
};
Accounts.AccountPayments = {
methodName: "AccountPayments",
service: Accounts,
requestStream: false,
responseStream: false,
requestType: lit_accounts_pb.AccountPaymentsRequest,
responseType: lit_accounts_pb.AccountPaymentsResponse
};
exports.Accounts = Accounts;
function AccountsClient(serviceHost, options) {
@ -297,5 +306,36 @@ AccountsClient.prototype.removeAccount = function removeAccount(requestMessage,
};
};
AccountsClient.prototype.accountPayments = function accountPayments(requestMessage, metadata, callback) {
if (arguments.length === 2) {
callback = arguments[1];
}
var client = grpc.unary(Accounts.AccountPayments, {
request: requestMessage,
host: this.serviceHost,
metadata: metadata,
transport: this.options.transport,
debug: this.options.debug,
onEnd: function (response) {
if (callback) {
if (response.status !== grpc.Code.OK) {
var err = new Error(response.statusMessage);
err.code = response.status;
err.metadata = response.trailers;
callback(err, null);
} else {
callback(null, response.message);
}
}
}
});
return {
cancel: function () {
callback = null;
client.close();
}
};
};
exports.AccountsClient = AccountsClient;

126
app/src/types/generated/loop_pb.d.ts generated vendored
View file

@ -3,6 +3,49 @@
import * as jspb from "google-protobuf";
import * as swapserverrpc_common_pb from "./swapserverrpc/common_pb";
import * as lnd_pb from "./lnd_pb";
export class StaticOpenChannelRequest extends jspb.Message {
hasOpenChannelRequest(): boolean;
clearOpenChannelRequest(): void;
getOpenChannelRequest(): lnd_pb.OpenChannelRequest | undefined;
setOpenChannelRequest(value?: lnd_pb.OpenChannelRequest): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): StaticOpenChannelRequest.AsObject;
static toObject(includeInstance: boolean, msg: StaticOpenChannelRequest): StaticOpenChannelRequest.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: StaticOpenChannelRequest, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): StaticOpenChannelRequest;
static deserializeBinaryFromReader(message: StaticOpenChannelRequest, reader: jspb.BinaryReader): StaticOpenChannelRequest;
}
export namespace StaticOpenChannelRequest {
export type AsObject = {
openChannelRequest?: lnd_pb.OpenChannelRequest.AsObject,
}
}
export class StaticOpenChannelResponse extends jspb.Message {
getChannelOpenOutpoint(): string;
setChannelOpenOutpoint(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): StaticOpenChannelResponse.AsObject;
static toObject(includeInstance: boolean, msg: StaticOpenChannelResponse): StaticOpenChannelResponse.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: StaticOpenChannelResponse, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): StaticOpenChannelResponse;
static deserializeBinaryFromReader(message: StaticOpenChannelResponse, reader: jspb.BinaryReader): StaticOpenChannelResponse;
}
export namespace StaticOpenChannelResponse {
export type AsObject = {
channelOpenOutpoint: string,
}
}
export class StopDaemonRequest extends jspb.Message {
serializeBinary(): Uint8Array;
@ -1247,6 +1290,9 @@ export class LiquidityParameters extends jspb.Message {
setEasyAutoloopExcludedPeersList(value: Array<Uint8Array | string>): void;
addEasyAutoloopExcludedPeers(value: Uint8Array | string, index?: number): Uint8Array | string;
getLoopInSource(): LoopInSourceMap[keyof LoopInSourceMap];
setLoopInSource(value: LoopInSourceMap[keyof LoopInSourceMap]): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LiquidityParameters.AsObject;
static toObject(includeInstance: boolean, msg: LiquidityParameters): LiquidityParameters.AsObject;
@ -1286,6 +1332,7 @@ export namespace LiquidityParameters {
easyAssetParamsMap: Array<[string, EasyAssetAutoloopParams.AsObject]>,
fastSwapPublication: boolean,
easyAutoloopExcludedPeersList: Array<Uint8Array | string>,
loopInSource: LoopInSourceMap[keyof LoopInSourceMap],
}
}
@ -1450,6 +1497,11 @@ export class SuggestSwapsResponse extends jspb.Message {
setLoopInList(value: Array<LoopInRequest>): void;
addLoopIn(value?: LoopInRequest, index?: number): LoopInRequest;
clearStaticLoopInList(): void;
getStaticLoopInList(): Array<StaticAddressLoopInRequest>;
setStaticLoopInList(value: Array<StaticAddressLoopInRequest>): void;
addStaticLoopIn(value?: StaticAddressLoopInRequest, index?: number): StaticAddressLoopInRequest;
clearDisqualifiedList(): void;
getDisqualifiedList(): Array<Disqualified>;
setDisqualifiedList(value: Array<Disqualified>): void;
@ -1469,6 +1521,7 @@ export namespace SuggestSwapsResponse {
export type AsObject = {
loopOutList: Array<LoopOutRequest.AsObject>,
loopInList: Array<LoopInRequest.AsObject>,
staticLoopInList: Array<StaticAddressLoopInRequest.AsObject>,
disqualifiedList: Array<Disqualified.AsObject>,
}
}
@ -1921,9 +1974,9 @@ export namespace Utxo {
export class WithdrawDepositsRequest extends jspb.Message {
clearOutpointsList(): void;
getOutpointsList(): Array<OutPoint>;
setOutpointsList(value: Array<OutPoint>): void;
addOutpoints(value?: OutPoint, index?: number): OutPoint;
getOutpointsList(): Array<lnd_pb.OutPoint>;
setOutpointsList(value: Array<lnd_pb.OutPoint>): void;
addOutpoints(value?: lnd_pb.OutPoint, index?: number): lnd_pb.OutPoint;
getAll(): boolean;
setAll(value: boolean): void;
@ -1949,7 +2002,7 @@ export class WithdrawDepositsRequest extends jspb.Message {
export namespace WithdrawDepositsRequest {
export type AsObject = {
outpointsList: Array<OutPoint.AsObject>,
outpointsList: Array<lnd_pb.OutPoint.AsObject>,
all: boolean,
destAddr: string,
satPerVbyte: string,
@ -1981,36 +2034,6 @@ export namespace WithdrawDepositsResponse {
}
}
export class OutPoint extends jspb.Message {
getTxidBytes(): Uint8Array | string;
getTxidBytes_asU8(): Uint8Array;
getTxidBytes_asB64(): string;
setTxidBytes(value: Uint8Array | string): void;
getTxidStr(): string;
setTxidStr(value: string): void;
getOutputIndex(): number;
setOutputIndex(value: number): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): OutPoint.AsObject;
static toObject(includeInstance: boolean, msg: OutPoint): OutPoint.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: OutPoint, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): OutPoint;
static deserializeBinaryFromReader(message: OutPoint, reader: jspb.BinaryReader): OutPoint;
}
export namespace OutPoint {
export type AsObject = {
txidBytes: Uint8Array | string,
txidStr: string,
outputIndex: number,
}
}
export class ListStaticAddressDepositsRequest extends jspb.Message {
getStateFilter(): DepositStateMap[keyof DepositStateMap];
setStateFilter(value: DepositStateMap[keyof DepositStateMap]): void;
@ -2179,6 +2202,9 @@ export class StaticAddressSummaryResponse extends jspb.Message {
getValueHtlcTimeoutSweepsSatoshis(): string;
setValueHtlcTimeoutSweepsSatoshis(value: string): void;
getValueChannelsOpened(): string;
setValueChannelsOpened(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): StaticAddressSummaryResponse.AsObject;
static toObject(includeInstance: boolean, msg: StaticAddressSummaryResponse): StaticAddressSummaryResponse.AsObject;
@ -2200,6 +2226,7 @@ export namespace StaticAddressSummaryResponse {
valueWithdrawnSatoshis: string,
valueLoopedInSatoshis: string,
valueHtlcTimeoutSweepsSatoshis: string,
valueChannelsOpened: string,
}
}
@ -2318,6 +2345,21 @@ export class StaticAddressLoopInSwap extends jspb.Message {
setDepositsList(value: Array<Deposit>): void;
addDeposits(value?: Deposit, index?: number): Deposit;
getInitiationTime(): string;
setInitiationTime(value: string): void;
getLastUpdateTime(): string;
setLastUpdateTime(value: string): void;
getCostServer(): string;
setCostServer(value: string): void;
getCostOnchain(): string;
setCostOnchain(value: string): void;
getCostOffchain(): string;
setCostOffchain(value: string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): StaticAddressLoopInSwap.AsObject;
static toObject(includeInstance: boolean, msg: StaticAddressLoopInSwap): StaticAddressLoopInSwap.AsObject;
@ -2336,6 +2378,11 @@ export namespace StaticAddressLoopInSwap {
swapAmountSatoshis: string,
paymentRequestAmountSatoshis: string,
depositsList: Array<Deposit.AsObject>,
initiationTime: string,
lastUpdateTime: string,
costServer: string,
costOnchain: string,
costOffchain: string,
}
}
@ -2661,6 +2708,13 @@ export interface FailureReasonMap {
export const FailureReason: FailureReasonMap;
export interface LoopInSourceMap {
LOOP_IN_SOURCE_WALLET: 0;
LOOP_IN_SOURCE_STATIC_ADDRESS: 1;
}
export const LoopInSource: LoopInSourceMap;
export interface LiquidityRuleTypeMap {
UNKNOWN: 0;
THRESHOLD: 1;
@ -2683,6 +2737,8 @@ export interface AutoReasonMap {
AUTO_REASON_LIQUIDITY_OK: 11;
AUTO_REASON_BUDGET_INSUFFICIENT: 12;
AUTO_REASON_FEE_INSUFFICIENT: 13;
AUTO_REASON_STATIC_LOOP_IN_NO_CANDIDATE: 14;
AUTO_REASON_CUSTOM_CHANNEL_DATA: 15;
}
export const AutoReason: AutoReasonMap;
@ -2699,6 +2755,8 @@ export interface DepositStateMap {
PUBLISH_EXPIRED: 8;
WAIT_FOR_EXPIRY_SWEEP: 9;
EXPIRED: 10;
OPENING_CHANNEL: 11;
CHANNEL_PUBLISHED: 12;
}
export const DepositState: DepositStateMap;

File diff suppressed because it is too large Load diff

View file

@ -292,6 +292,15 @@ type SwapClientStaticAddressLoopIn = {
readonly responseType: typeof loop_pb.StaticAddressLoopInResponse;
};
type SwapClientStaticOpenChannel = {
readonly methodName: string;
readonly service: typeof SwapClient;
readonly requestStream: false;
readonly responseStream: false;
readonly requestType: typeof loop_pb.StaticOpenChannelRequest;
readonly responseType: typeof loop_pb.StaticOpenChannelResponse;
};
export class SwapClient {
static readonly serviceName: string;
static readonly LoopOut: SwapClientLoopOut;
@ -326,6 +335,7 @@ export class SwapClient {
static readonly ListStaticAddressSwaps: SwapClientListStaticAddressSwaps;
static readonly GetStaticAddressSummary: SwapClientGetStaticAddressSummary;
static readonly StaticAddressLoopIn: SwapClientStaticAddressLoopIn;
static readonly StaticOpenChannel: SwapClientStaticOpenChannel;
}
export type ServiceError = { message: string, code: number; metadata: grpc.Metadata }
@ -640,5 +650,14 @@ export class SwapClientClient {
requestMessage: loop_pb.StaticAddressLoopInRequest,
callback: (error: ServiceError|null, responseMessage: loop_pb.StaticAddressLoopInResponse|null) => void
): UnaryResponse;
staticOpenChannel(
requestMessage: loop_pb.StaticOpenChannelRequest,
metadata: grpc.Metadata,
callback: (error: ServiceError|null, responseMessage: loop_pb.StaticOpenChannelResponse|null) => void
): UnaryResponse;
staticOpenChannel(
requestMessage: loop_pb.StaticOpenChannelRequest,
callback: (error: ServiceError|null, responseMessage: loop_pb.StaticOpenChannelResponse|null) => void
): UnaryResponse;
}

View file

@ -298,6 +298,15 @@ SwapClient.StaticAddressLoopIn = {
responseType: loop_pb.StaticAddressLoopInResponse
};
SwapClient.StaticOpenChannel = {
methodName: "StaticOpenChannel",
service: SwapClient,
requestStream: false,
responseStream: false,
requestType: loop_pb.StaticOpenChannelRequest,
responseType: loop_pb.StaticOpenChannelResponse
};
exports.SwapClient = SwapClient;
function SwapClientClient(serviceHost, options) {
@ -1305,5 +1314,36 @@ SwapClientClient.prototype.staticAddressLoopIn = function staticAddressLoopIn(re
};
};
SwapClientClient.prototype.staticOpenChannel = function staticOpenChannel(requestMessage, metadata, callback) {
if (arguments.length === 2) {
callback = arguments[1];
}
var client = grpc.unary(SwapClient.StaticOpenChannel, {
request: requestMessage,
host: this.serviceHost,
metadata: metadata,
transport: this.options.transport,
debug: this.options.debug,
onEnd: function (response) {
if (callback) {
if (response.status !== grpc.Code.OK) {
var err = new Error(response.statusMessage);
err.code = response.status;
err.metadata = response.trailers;
callback(err, null);
} else {
callback(null, response.message);
}
}
}
});
return {
cancel: function () {
callback = null;
client.close();
}
};
};
exports.SwapClientClient = SwapClientClient;

View file

@ -1098,6 +1098,21 @@ export class SubscribeNotificationsResponse extends jspb.Message {
getUnfinishedSwap(): ServerUnfinishedSwapNotification | undefined;
setUnfinishedSwap(value?: ServerUnfinishedSwapNotification): void;
hasStaticLoopInRiskAccepted(): boolean;
clearStaticLoopInRiskAccepted(): void;
getStaticLoopInRiskAccepted(): ServerStaticLoopInRiskAcceptedNotification | undefined;
setStaticLoopInRiskAccepted(value?: ServerStaticLoopInRiskAcceptedNotification): void;
hasStaticLoopInRiskRejected(): boolean;
clearStaticLoopInRiskRejected(): void;
getStaticLoopInRiskRejected(): ServerStaticLoopInRiskRejectedNotification | undefined;
setStaticLoopInRiskRejected(value?: ServerStaticLoopInRiskRejectedNotification): void;
hasHtlcConfirmed(): boolean;
clearHtlcConfirmed(): void;
getHtlcConfirmed(): ServerHtlcConfirmedNotification | undefined;
setHtlcConfirmed(value?: ServerHtlcConfirmedNotification): void;
getNotificationCase(): SubscribeNotificationsResponse.NotificationCase;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): SubscribeNotificationsResponse.AsObject;
@ -1114,6 +1129,9 @@ export namespace SubscribeNotificationsResponse {
reservationNotification?: swapserverrpc_reservation_pb.ServerReservationNotification.AsObject,
staticLoopInSweep?: ServerStaticLoopInSweepNotification.AsObject,
unfinishedSwap?: ServerUnfinishedSwapNotification.AsObject,
staticLoopInRiskAccepted?: ServerStaticLoopInRiskAcceptedNotification.AsObject,
staticLoopInRiskRejected?: ServerStaticLoopInRiskRejectedNotification.AsObject,
htlcConfirmed?: ServerHtlcConfirmedNotification.AsObject,
}
export enum NotificationCase {
@ -1121,6 +1139,9 @@ export namespace SubscribeNotificationsResponse {
RESERVATION_NOTIFICATION = 1,
STATIC_LOOP_IN_SWEEP = 2,
UNFINISHED_SWAP = 3,
STATIC_LOOP_IN_RISK_ACCEPTED = 4,
STATIC_LOOP_IN_RISK_REJECTED = 5,
HTLC_CONFIRMED = 6,
}
}
@ -1161,6 +1182,50 @@ export namespace ServerStaticLoopInSweepNotification {
}
}
export class ServerStaticLoopInRiskAcceptedNotification extends jspb.Message {
getSwapHash(): Uint8Array | string;
getSwapHash_asU8(): Uint8Array;
getSwapHash_asB64(): string;
setSwapHash(value: Uint8Array | string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ServerStaticLoopInRiskAcceptedNotification.AsObject;
static toObject(includeInstance: boolean, msg: ServerStaticLoopInRiskAcceptedNotification): ServerStaticLoopInRiskAcceptedNotification.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ServerStaticLoopInRiskAcceptedNotification, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ServerStaticLoopInRiskAcceptedNotification;
static deserializeBinaryFromReader(message: ServerStaticLoopInRiskAcceptedNotification, reader: jspb.BinaryReader): ServerStaticLoopInRiskAcceptedNotification;
}
export namespace ServerStaticLoopInRiskAcceptedNotification {
export type AsObject = {
swapHash: Uint8Array | string,
}
}
export class ServerStaticLoopInRiskRejectedNotification extends jspb.Message {
getSwapHash(): Uint8Array | string;
getSwapHash_asU8(): Uint8Array;
getSwapHash_asB64(): string;
setSwapHash(value: Uint8Array | string): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ServerStaticLoopInRiskRejectedNotification.AsObject;
static toObject(includeInstance: boolean, msg: ServerStaticLoopInRiskRejectedNotification): ServerStaticLoopInRiskRejectedNotification.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ServerStaticLoopInRiskRejectedNotification, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ServerStaticLoopInRiskRejectedNotification;
static deserializeBinaryFromReader(message: ServerStaticLoopInRiskRejectedNotification, reader: jspb.BinaryReader): ServerStaticLoopInRiskRejectedNotification;
}
export namespace ServerStaticLoopInRiskRejectedNotification {
export type AsObject = {
swapHash: Uint8Array | string,
}
}
export class ServerUnfinishedSwapNotification extends jspb.Message {
getSwapHash(): Uint8Array | string;
getSwapHash_asU8(): Uint8Array;
@ -1187,6 +1252,40 @@ export namespace ServerUnfinishedSwapNotification {
}
}
export class ServerHtlcConfirmedNotification extends jspb.Message {
getSwapHash(): Uint8Array | string;
getSwapHash_asU8(): Uint8Array;
getSwapHash_asB64(): string;
setSwapHash(value: Uint8Array | string): void;
getHtlcOutpoint(): string;
setHtlcOutpoint(value: string): void;
getHtlcAddress(): string;
setHtlcAddress(value: string): void;
getSatPerVbyte(): number;
setSatPerVbyte(value: number): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): ServerHtlcConfirmedNotification.AsObject;
static toObject(includeInstance: boolean, msg: ServerHtlcConfirmedNotification): ServerHtlcConfirmedNotification.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: ServerHtlcConfirmedNotification, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): ServerHtlcConfirmedNotification;
static deserializeBinaryFromReader(message: ServerHtlcConfirmedNotification, reader: jspb.BinaryReader): ServerHtlcConfirmedNotification;
}
export namespace ServerHtlcConfirmedNotification {
export type AsObject = {
swapHash: Uint8Array | string,
htlcOutpoint: string,
htlcAddress: string,
satPerVbyte: number,
}
}
export interface ProtocolVersionMap {
LEGACY: 0;
MULTI_LOOP_OUT: 1;

View file

@ -46,6 +46,7 @@ goog.exportSymbol('proto.looprpc.ReportRoutingResultRes', null, global);
goog.exportSymbol('proto.looprpc.RouteCancel', null, global);
goog.exportSymbol('proto.looprpc.RoutePaymentType', null, global);
goog.exportSymbol('proto.looprpc.RoutingPlugin', null, global);
goog.exportSymbol('proto.looprpc.ServerHtlcConfirmedNotification', null, global);
goog.exportSymbol('proto.looprpc.ServerLoopInQuoteRequest', null, global);
goog.exportSymbol('proto.looprpc.ServerLoopInQuoteResponse', null, global);
goog.exportSymbol('proto.looprpc.ServerLoopInRequest', null, global);
@ -64,6 +65,8 @@ goog.exportSymbol('proto.looprpc.ServerProbeRequest', null, global);
goog.exportSymbol('proto.looprpc.ServerProbeResponse', null, global);
goog.exportSymbol('proto.looprpc.ServerPushKeyReq', null, global);
goog.exportSymbol('proto.looprpc.ServerPushKeyRes', null, global);
goog.exportSymbol('proto.looprpc.ServerStaticLoopInRiskAcceptedNotification', null, global);
goog.exportSymbol('proto.looprpc.ServerStaticLoopInRiskRejectedNotification', null, global);
goog.exportSymbol('proto.looprpc.ServerStaticLoopInSweepNotification', null, global);
goog.exportSymbol('proto.looprpc.ServerSwapState', null, global);
goog.exportSymbol('proto.looprpc.ServerUnfinishedSwapNotification', null, global);
@ -851,6 +854,48 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.looprpc.ServerStaticLoopInSweepNotification.displayName = 'proto.looprpc.ServerStaticLoopInSweepNotification';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.looprpc.ServerStaticLoopInRiskAcceptedNotification, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.displayName = 'proto.looprpc.ServerStaticLoopInRiskAcceptedNotification';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.looprpc.ServerStaticLoopInRiskRejectedNotification, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.displayName = 'proto.looprpc.ServerStaticLoopInRiskRejectedNotification';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
@ -872,6 +917,27 @@ if (goog.DEBUG && !COMPILED) {
*/
proto.looprpc.ServerUnfinishedSwapNotification.displayName = 'proto.looprpc.ServerUnfinishedSwapNotification';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.looprpc.ServerHtlcConfirmedNotification = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.looprpc.ServerHtlcConfirmedNotification, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.looprpc.ServerHtlcConfirmedNotification.displayName = 'proto.looprpc.ServerHtlcConfirmedNotification';
}
@ -8546,7 +8612,7 @@ proto.looprpc.SubscribeNotificationsRequest.prototype.setVersion = function(valu
* @private {!Array<!Array<number>>}
* @const
*/
proto.looprpc.SubscribeNotificationsResponse.oneofGroups_ = [[1,2,3]];
proto.looprpc.SubscribeNotificationsResponse.oneofGroups_ = [[1,2,3,4,5,6]];
/**
* @enum {number}
@ -8555,7 +8621,10 @@ proto.looprpc.SubscribeNotificationsResponse.NotificationCase = {
NOTIFICATION_NOT_SET: 0,
RESERVATION_NOTIFICATION: 1,
STATIC_LOOP_IN_SWEEP: 2,
UNFINISHED_SWAP: 3
UNFINISHED_SWAP: 3,
STATIC_LOOP_IN_RISK_ACCEPTED: 4,
STATIC_LOOP_IN_RISK_REJECTED: 5,
HTLC_CONFIRMED: 6
};
/**
@ -8598,7 +8667,10 @@ proto.looprpc.SubscribeNotificationsResponse.toObject = function(includeInstance
var f, obj = {
reservationNotification: (f = msg.getReservationNotification()) && swapserverrpc_reservation_pb.ServerReservationNotification.toObject(includeInstance, f),
staticLoopInSweep: (f = msg.getStaticLoopInSweep()) && proto.looprpc.ServerStaticLoopInSweepNotification.toObject(includeInstance, f),
unfinishedSwap: (f = msg.getUnfinishedSwap()) && proto.looprpc.ServerUnfinishedSwapNotification.toObject(includeInstance, f)
unfinishedSwap: (f = msg.getUnfinishedSwap()) && proto.looprpc.ServerUnfinishedSwapNotification.toObject(includeInstance, f),
staticLoopInRiskAccepted: (f = msg.getStaticLoopInRiskAccepted()) && proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.toObject(includeInstance, f),
staticLoopInRiskRejected: (f = msg.getStaticLoopInRiskRejected()) && proto.looprpc.ServerStaticLoopInRiskRejectedNotification.toObject(includeInstance, f),
htlcConfirmed: (f = msg.getHtlcConfirmed()) && proto.looprpc.ServerHtlcConfirmedNotification.toObject(includeInstance, f)
};
if (includeInstance) {
@ -8650,6 +8722,21 @@ proto.looprpc.SubscribeNotificationsResponse.deserializeBinaryFromReader = funct
reader.readMessage(value,proto.looprpc.ServerUnfinishedSwapNotification.deserializeBinaryFromReader);
msg.setUnfinishedSwap(value);
break;
case 4:
var value = new proto.looprpc.ServerStaticLoopInRiskAcceptedNotification;
reader.readMessage(value,proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.deserializeBinaryFromReader);
msg.setStaticLoopInRiskAccepted(value);
break;
case 5:
var value = new proto.looprpc.ServerStaticLoopInRiskRejectedNotification;
reader.readMessage(value,proto.looprpc.ServerStaticLoopInRiskRejectedNotification.deserializeBinaryFromReader);
msg.setStaticLoopInRiskRejected(value);
break;
case 6:
var value = new proto.looprpc.ServerHtlcConfirmedNotification;
reader.readMessage(value,proto.looprpc.ServerHtlcConfirmedNotification.deserializeBinaryFromReader);
msg.setHtlcConfirmed(value);
break;
default:
reader.skipField();
break;
@ -8703,6 +8790,30 @@ proto.looprpc.SubscribeNotificationsResponse.serializeBinaryToWriter = function(
proto.looprpc.ServerUnfinishedSwapNotification.serializeBinaryToWriter
);
}
f = message.getStaticLoopInRiskAccepted();
if (f != null) {
writer.writeMessage(
4,
f,
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.serializeBinaryToWriter
);
}
f = message.getStaticLoopInRiskRejected();
if (f != null) {
writer.writeMessage(
5,
f,
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.serializeBinaryToWriter
);
}
f = message.getHtlcConfirmed();
if (f != null) {
writer.writeMessage(
6,
f,
proto.looprpc.ServerHtlcConfirmedNotification.serializeBinaryToWriter
);
}
};
@ -8817,6 +8928,117 @@ proto.looprpc.SubscribeNotificationsResponse.prototype.hasUnfinishedSwap = funct
};
/**
* optional ServerStaticLoopInRiskAcceptedNotification static_loop_in_risk_accepted = 4;
* @return {?proto.looprpc.ServerStaticLoopInRiskAcceptedNotification}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.getStaticLoopInRiskAccepted = function() {
return /** @type{?proto.looprpc.ServerStaticLoopInRiskAcceptedNotification} */ (
jspb.Message.getWrapperField(this, proto.looprpc.ServerStaticLoopInRiskAcceptedNotification, 4));
};
/**
* @param {?proto.looprpc.ServerStaticLoopInRiskAcceptedNotification|undefined} value
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.setStaticLoopInRiskAccepted = function(value) {
return jspb.Message.setOneofWrapperField(this, 4, proto.looprpc.SubscribeNotificationsResponse.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.clearStaticLoopInRiskAccepted = function() {
return this.setStaticLoopInRiskAccepted(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.hasStaticLoopInRiskAccepted = function() {
return jspb.Message.getField(this, 4) != null;
};
/**
* optional ServerStaticLoopInRiskRejectedNotification static_loop_in_risk_rejected = 5;
* @return {?proto.looprpc.ServerStaticLoopInRiskRejectedNotification}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.getStaticLoopInRiskRejected = function() {
return /** @type{?proto.looprpc.ServerStaticLoopInRiskRejectedNotification} */ (
jspb.Message.getWrapperField(this, proto.looprpc.ServerStaticLoopInRiskRejectedNotification, 5));
};
/**
* @param {?proto.looprpc.ServerStaticLoopInRiskRejectedNotification|undefined} value
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.setStaticLoopInRiskRejected = function(value) {
return jspb.Message.setOneofWrapperField(this, 5, proto.looprpc.SubscribeNotificationsResponse.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.clearStaticLoopInRiskRejected = function() {
return this.setStaticLoopInRiskRejected(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.hasStaticLoopInRiskRejected = function() {
return jspb.Message.getField(this, 5) != null;
};
/**
* optional ServerHtlcConfirmedNotification htlc_confirmed = 6;
* @return {?proto.looprpc.ServerHtlcConfirmedNotification}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.getHtlcConfirmed = function() {
return /** @type{?proto.looprpc.ServerHtlcConfirmedNotification} */ (
jspb.Message.getWrapperField(this, proto.looprpc.ServerHtlcConfirmedNotification, 6));
};
/**
* @param {?proto.looprpc.ServerHtlcConfirmedNotification|undefined} value
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.setHtlcConfirmed = function(value) {
return jspb.Message.setOneofWrapperField(this, 6, proto.looprpc.SubscribeNotificationsResponse.oneofGroups_[0], value);
};
/**
* Clears the message field making it undefined.
* @return {!proto.looprpc.SubscribeNotificationsResponse} returns this
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.clearHtlcConfirmed = function() {
return this.setHtlcConfirmed(undefined);
};
/**
* Returns whether this field is set.
* @return {boolean}
*/
proto.looprpc.SubscribeNotificationsResponse.prototype.hasHtlcConfirmed = function() {
return jspb.Message.getField(this, 6) != null;
};
/**
* List of repeated fields within this message type.
@ -9122,6 +9344,314 @@ proto.looprpc.ServerStaticLoopInSweepNotification.prototype.clearPrevoutInfoList
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.toObject = function(opt_includeInstance) {
return proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.toObject = function(includeInstance, msg) {
var f, obj = {
swapHash: msg.getSwapHash_asB64()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.looprpc.ServerStaticLoopInRiskAcceptedNotification;
return proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setSwapHash(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getSwapHash_asU8();
if (f.length > 0) {
writer.writeBytes(
1,
f
);
}
};
/**
* optional bytes swap_hash = 1;
* @return {!(string|Uint8Array)}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.getSwapHash = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* optional bytes swap_hash = 1;
* This is a type-conversion wrapper around `getSwapHash()`
* @return {string}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.getSwapHash_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getSwapHash()));
};
/**
* optional bytes swap_hash = 1;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getSwapHash()`
* @return {!Uint8Array}
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.getSwapHash_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getSwapHash()));
};
/**
* @param {!(string|Uint8Array)} value
* @return {!proto.looprpc.ServerStaticLoopInRiskAcceptedNotification} returns this
*/
proto.looprpc.ServerStaticLoopInRiskAcceptedNotification.prototype.setSwapHash = function(value) {
return jspb.Message.setProto3BytesField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.toObject = function(opt_includeInstance) {
return proto.looprpc.ServerStaticLoopInRiskRejectedNotification.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.toObject = function(includeInstance, msg) {
var f, obj = {
swapHash: msg.getSwapHash_asB64()
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.looprpc.ServerStaticLoopInRiskRejectedNotification;
return proto.looprpc.ServerStaticLoopInRiskRejectedNotification.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setSwapHash(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getSwapHash_asU8();
if (f.length > 0) {
writer.writeBytes(
1,
f
);
}
};
/**
* optional bytes swap_hash = 1;
* @return {!(string|Uint8Array)}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.getSwapHash = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* optional bytes swap_hash = 1;
* This is a type-conversion wrapper around `getSwapHash()`
* @return {string}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.getSwapHash_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getSwapHash()));
};
/**
* optional bytes swap_hash = 1;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getSwapHash()`
* @return {!Uint8Array}
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.getSwapHash_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getSwapHash()));
};
/**
* @param {!(string|Uint8Array)} value
* @return {!proto.looprpc.ServerStaticLoopInRiskRejectedNotification} returns this
*/
proto.looprpc.ServerStaticLoopInRiskRejectedNotification.prototype.setSwapHash = function(value) {
return jspb.Message.setProto3BytesField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
@ -9303,6 +9833,250 @@ proto.looprpc.ServerUnfinishedSwapNotification.prototype.setIsLoopIn = function(
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.toObject = function(opt_includeInstance) {
return proto.looprpc.ServerHtlcConfirmedNotification.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.looprpc.ServerHtlcConfirmedNotification} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerHtlcConfirmedNotification.toObject = function(includeInstance, msg) {
var f, obj = {
swapHash: msg.getSwapHash_asB64(),
htlcOutpoint: jspb.Message.getFieldWithDefault(msg, 2, ""),
htlcAddress: jspb.Message.getFieldWithDefault(msg, 3, ""),
satPerVbyte: jspb.Message.getFieldWithDefault(msg, 4, 0)
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.looprpc.ServerHtlcConfirmedNotification}
*/
proto.looprpc.ServerHtlcConfirmedNotification.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.looprpc.ServerHtlcConfirmedNotification;
return proto.looprpc.ServerHtlcConfirmedNotification.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.looprpc.ServerHtlcConfirmedNotification} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.looprpc.ServerHtlcConfirmedNotification}
*/
proto.looprpc.ServerHtlcConfirmedNotification.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {!Uint8Array} */ (reader.readBytes());
msg.setSwapHash(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setHtlcOutpoint(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setHtlcAddress(value);
break;
case 4:
var value = /** @type {number} */ (reader.readUint32());
msg.setSatPerVbyte(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.looprpc.ServerHtlcConfirmedNotification.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.looprpc.ServerHtlcConfirmedNotification} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.looprpc.ServerHtlcConfirmedNotification.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getSwapHash_asU8();
if (f.length > 0) {
writer.writeBytes(
1,
f
);
}
f = message.getHtlcOutpoint();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getHtlcAddress();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
f = message.getSatPerVbyte();
if (f !== 0) {
writer.writeUint32(
4,
f
);
}
};
/**
* optional bytes swap_hash = 1;
* @return {!(string|Uint8Array)}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getSwapHash = function() {
return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* optional bytes swap_hash = 1;
* This is a type-conversion wrapper around `getSwapHash()`
* @return {string}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getSwapHash_asB64 = function() {
return /** @type {string} */ (jspb.Message.bytesAsB64(
this.getSwapHash()));
};
/**
* optional bytes swap_hash = 1;
* Note that Uint8Array is not supported on all browsers.
* @see http://caniuse.com/Uint8Array
* This is a type-conversion wrapper around `getSwapHash()`
* @return {!Uint8Array}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getSwapHash_asU8 = function() {
return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8(
this.getSwapHash()));
};
/**
* @param {!(string|Uint8Array)} value
* @return {!proto.looprpc.ServerHtlcConfirmedNotification} returns this
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.setSwapHash = function(value) {
return jspb.Message.setProto3BytesField(this, 1, value);
};
/**
* optional string htlc_outpoint = 2;
* @return {string}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getHtlcOutpoint = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.looprpc.ServerHtlcConfirmedNotification} returns this
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.setHtlcOutpoint = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* optional string htlc_address = 3;
* @return {string}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getHtlcAddress = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/**
* @param {string} value
* @return {!proto.looprpc.ServerHtlcConfirmedNotification} returns this
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.setHtlcAddress = function(value) {
return jspb.Message.setProto3StringField(this, 3, value);
};
/**
* optional uint32 sat_per_vbyte = 4;
* @return {number}
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.getSatPerVbyte = function() {
return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 4, 0));
};
/**
* @param {number} value
* @return {!proto.looprpc.ServerHtlcConfirmedNotification} returns this
*/
proto.looprpc.ServerHtlcConfirmedNotification.prototype.setSatPerVbyte = function(value) {
return jspb.Message.setProto3IntField(this, 4, value);
};
/**
* @enum {number}
*/

View file

@ -96,7 +96,10 @@ export default class AppStorage {
this.set(cacheKey, { expires, data });
log.info(`updated cache with ${keys.length} new ${cacheKey}`);
} catch (error) {
log.error(`failed to fetch ${cacheKey} from the API`, error.message);
log.error(
`failed to fetch ${cacheKey} from the API`,
error instanceof Error ? error.message : String(error),
);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,15 @@
module github.com/lightninglabs/lightning-terminal/autopilotserverrpc
go 1.25.10
go 1.25.11
require (
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.10
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
)
require (
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
)

View file

@ -12,27 +12,27 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=

View file

@ -30,6 +30,7 @@ var accountsCommands = []cli.Command{
listAccountsCommand,
accountInfoCommand,
removeAccountCommand,
accountPaymentsCommand,
},
Description: "Manage accounts.",
},
@ -541,3 +542,84 @@ func parseIDOrLabel(ctx *cli.Context) (string, string, cli.Args, error) {
return accountID, label, args, nil
}
var accountPaymentsCommand = cli.Command{
Name: "payments",
ShortName: "p",
Usage: "Show detailed payment history for a single " +
"off-chain account.",
ArgsUsage: "[id | label]",
Description: "Returns the detailed payment history for an " +
"account by fetching their stored hashes and querying " +
"LND. The results are returned paginated and " +
"sorted in ascending lexicographical order of their " +
"payment hash.",
Flags: []cli.Flag{
cli.StringFlag{
Name: idName,
Usage: "The ID of the account.",
},
cli.StringFlag{
Name: labelName,
Usage: "(optional) The unique label of the account.",
},
cli.Uint64Flag{
Name: "max_payments",
Usage: fmt.Sprintf("The maximum number of payments to "+
"return. The default value is %d and "+
"the maximum is %d.",
accounts.DefaultMaxPayments,
accounts.MaxPaymentsLimit),
Value: accounts.DefaultMaxPayments,
},
cli.Uint64Flag{
Name: "index_offset",
Usage: "The row offset into the list of payments " +
"that will be used as the start of the " +
"query.",
},
cli.BoolFlag{
Name: "count_total_payments",
Usage: "If true, the total number of payments " +
"matching the query will be returned.",
},
},
Action: accountPayments,
}
func accountPayments(cli *cli.Context) error {
ctx := getContext()
clientConn, cleanup, err := connectClient(cli, false)
if err != nil {
return err
}
defer cleanup()
client := litrpc.NewAccountsClient(clientConn)
account, _, err := parseAccountIdentifier(cli)
if err != nil {
return err
}
maxPayments := cli.Uint64("max_payments")
if maxPayments > accounts.MaxPaymentsLimit {
return fmt.Errorf(
"max_payments cannot exceed %d",
accounts.MaxPaymentsLimit,
)
}
req := &litrpc.AccountPaymentsRequest{
Account: account,
MaxPayments: maxPayments,
IndexOffset: cli.Uint64("index_offset"),
CountTotalPayments: cli.Bool("count_total_payments"),
}
resp, err := client.AccountPayments(ctx, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}

View file

@ -3,6 +3,7 @@ package main
import (
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/lightninglabs/lightning-terminal/litrpc"
@ -85,6 +86,20 @@ var addSessionCommand = cli.Command{
"For example, '/lnrpc\\..*' will result in " +
"all `lnrpc` permissions being included.",
},
cli.StringSliceFlag{
Name: "permission",
Usage: "A permission that should be included in the " +
"macaroon of a custom session, in the " +
"format entity:action (e.g. info:read), " +
"similar to lncli bakemacaroon. " +
"Note that this flag will only be used if " +
"the 'type' flag is set to 'custom'. " +
"This flag can be specified multiple times, " +
"comma-separated, or mixed (e.g., " +
"'info:read,onchain:write' or " +
"--permission info:read --permission " +
"onchain:write).",
},
cli.StringFlag{
Name: "account_id",
Usage: "The account id that should be used for " +
@ -109,12 +124,12 @@ func addSession(cli *cli.Context) error {
return err
}
var macPerms []*litrpc.MacaroonPermission
for _, uri := range cli.StringSlice("uri") {
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: macaroons.PermissionEntityCustomURI,
Action: uri,
})
macPerms, err := parseCustomPermissions(
cli.StringSlice("uri"),
cli.StringSlice("permission"),
)
if err != nil {
return err
}
sessionLength := time.Second * time.Duration(cli.Uint64("expiry"))
@ -304,3 +319,51 @@ func revokeSession(cli *cli.Context) error {
return nil
}
func parseCustomPermissions(uris, permissions []string) (
[]*litrpc.MacaroonPermission, error) {
var macPerms []*litrpc.MacaroonPermission
for _, uri := range uris {
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: macaroons.PermissionEntityCustomURI,
Action: uri,
})
}
for _, perm := range permissions {
parts := strings.Split(perm, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
subParts := strings.Split(part, ":")
if len(subParts) != 2 {
return nil, fmt.Errorf("invalid permission "+
"format '%s', must be entity:action",
part)
}
entity := strings.TrimSpace(subParts[0])
action := strings.TrimSpace(subParts[1])
if entity == "" || action == "" {
return nil, fmt.Errorf("invalid permission "+
"format '%s', entity and action "+
"must not be empty", part)
}
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: entity,
Action: action,
})
}
}
return macPerms, nil
}

173
cmd/litcli/sessions_test.go Normal file
View file

@ -0,0 +1,173 @@
package main
import (
"testing"
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/require"
)
// TestParseCustomPermissions tests that custom permissions (URIs and
// entity:action pairs) are parsed and validated correctly.
func TestParseCustomPermissions(t *testing.T) {
customURI := macaroons.PermissionEntityCustomURI
tests := []struct {
name string
uris []string
permissions []string
expected []*litrpc.MacaroonPermission
expectErr bool
errContains string
}{
{
name: "valid URIs only",
uris: []string{
"/lnrpc.Lightning/GetInfo",
"/lnrpc.Lightning/UpdateChannelPolicy",
},
permissions: nil,
expected: []*litrpc.MacaroonPermission{
{
Entity: customURI,
Action: "/lnrpc.Lightning/GetInfo",
},
{
Entity: customURI,
Action: "/lnrpc.Lightning/" +
"UpdateChannelPolicy",
},
},
expectErr: false,
},
{
name: "valid permissions only - repeated flags",
uris: nil,
permissions: []string{
"info:read",
"offchain:write",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "valid permissions only - comma separated",
uris: nil,
permissions: []string{
"info:read,offchain:write",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "valid permissions only - mixed with whitespace",
uris: nil,
permissions: []string{
" info:read , offchain:write ",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "combination of URIs and permissions",
uris: []string{
"/lnrpc.Lightning/GetInfo",
},
permissions: []string{
"info:read,offchain:write",
"onchain:read",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: customURI,
Action: "/lnrpc.Lightning/GetInfo",
},
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
{
Entity: "onchain",
Action: "read",
},
},
expectErr: false,
},
{
name: "invalid perm - missing colon",
uris: nil,
permissions: []string{"inforead"},
expectErr: true,
errContains: "must be entity:action",
},
{
name: "invalid perm - multiple colons",
uris: nil,
permissions: []string{"info:read:extra"},
expectErr: true,
errContains: "must be entity:action",
},
{
name: "invalid perm - empty entity",
uris: nil,
permissions: []string{":read"},
expectErr: true,
errContains: "entity and action must not be empty",
},
{
name: "invalid perm - empty action",
uris: nil,
permissions: []string{"info:"},
expectErr: true,
errContains: "entity and action must not be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := parseCustomPermissions(
tt.uris, tt.permissions,
)
if tt.expectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errContains)
require.Nil(t, actual)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, actual)
}
})
}
}

228
config.go
View file

@ -3,6 +3,7 @@ package terminal
import (
"context"
"crypto/tls"
"database/sql"
"errors"
"fmt"
"io/ioutil"
@ -15,6 +16,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/jessevdk/go-flags"
"github.com/lib/pq"
"github.com/lightninglabs/faraday"
"github.com/lightninglabs/faraday/chain"
"github.com/lightninglabs/faraday/frdrpcserver"
@ -25,6 +27,7 @@ import (
"github.com/lightninglabs/lightning-terminal/db/sqlc"
"github.com/lightninglabs/lightning-terminal/firewall"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/macaroons"
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/lightning-terminal/subservers"
@ -94,8 +97,36 @@ const (
// autogenerated lit macaroon.
DefaultMacaroonFilename = "lit.macaroon"
// DefaultSuperMacaroonFilename is the default file name for the
// autogenerated super macaroon.
DefaultSuperMacaroonFilename = "super.macaroon"
// noneChoice is the none choice for the bake-super-macaroon
// configuration option.
noneChoice = "none"
// readOnlyChoice is the read-only choice for the bake-super-macaroon
// configuration option.
readOnlyChoice = "read-only"
// defaultBakeSuperMacaroon is the default value for the
// bake-super-macaroon configuration option.
defaultBakeSuperMacaroon = noneChoice
defaultFirstLNCConnTimeout = 10 * time.Minute
// defaultLndReadyTimeout is the default maximum time that litd
// waits for lnd's RPC server to become ready. It currently applies
// to the one-time kvdb-to-SQL data migration, which must call into
// lnd's main RPC server (ListMacaroonIDs); that only becomes
// available once lnd reaches its "RPC active" state. On nodes with a
// large channel/graph state this can take well over a minute after
// the wallet is unlocked, and timing out aborts litd startup
// entirely (requiring a manual restart), so we default to a
// deliberately generous value that comfortably covers even large
// nodes.
defaultLndReadyTimeout = 10 * time.Minute
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
@ -167,6 +198,12 @@ var (
defaultSqliteDatabasePath = filepath.Join(
DefaultLitDir, DefaultNetwork, defaultSqliteDatabaseFileName,
)
// DefaultSuperMacaroonPath is the default full path of the super
// macaroon.
DefaultSuperMacaroonPath = filepath.Join(
DefaultLitDir, DefaultNetwork, DefaultSuperMacaroonFilename,
)
)
// Config is the main configuration struct of lightning-terminal. It contains
@ -201,6 +238,9 @@ type Config struct {
MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for litd's RPC and REST services if it doesn't exist."`
BakeSuperMacaroon string `long:"bake-super-macaroon" description:"Bake a super macaroon on startup if it doesn't exist." choice:"none" choice:"read-only" choice:"read-write"`
SuperMacaroonPath string `long:"super-macaroon-path" description:"Path to write the auto-baked super macaroon. This must include both the directory and the name of the macaroon file itself (which must end with the .macaroon suffix)."`
FirstLNCConnDeadline time.Duration `long:"firstlncconndeadline" description:"The duration after a new LNC session will be revoked if no connection is made with it. This only applies for the first connection which is made using the pairing phrase. "`
// DatabaseBackend is the database backend we will use for storing all
@ -218,6 +258,11 @@ type Config struct {
// resolved.
autoMigrateKVDBApproved bool
// LndReadyTimeout is the maximum time that litd will wait for lnd's RPC
// server to become ready. This currently applies to the one-time
// kvdb-to-SQL data migration, which must call into lnd's RPC.
LndReadyTimeout time.Duration `long:"lndreadytimeout" description:"The maximum time that litd will wait for lnd's RPC server to become ready. This currently applies to the one-time migration of litd's legacy kvdb data to the configured SQL backend, which must call into lnd's RPC. On nodes with a large channel and graph state, lnd can take a while to start serving RPC calls after the wallet is unlocked; increase this value if startup fails with an error mentioning that lnd's 'RPC server is in the process of starting up'."`
// Sqlite holds the configuration options for a SQLite database
// backend.
Sqlite *db.SqliteConfig `group:"sqlite" namespace:"sqlite"`
@ -339,6 +384,7 @@ func (c *Config) NewStores(ctx context.Context,
migsets.MakeMigrationSets(
ctx, basicClient, c.MacaroonPath,
c.LitDir, c.Network, clock,
c.LndReadyTimeout,
),
)
if err != nil {
@ -391,6 +437,7 @@ func (c *Config) NewStores(ctx context.Context,
migsets.MakeMigrationSets(
ctx, basicClient, c.MacaroonPath,
c.LitDir, c.Network, clock,
c.LndReadyTimeout,
),
)
if err != nil {
@ -537,10 +584,13 @@ func defaultConfig() *Config {
Lnd: &lndDefaultConfig,
LndRPCTimeout: defaultRPCTimeout,
LndConnectInterval: defaultStartupTimeout,
LndReadyTimeout: defaultLndReadyTimeout,
LitDir: DefaultLitDir,
LetsEncryptListen: defaultLetsEncryptListen,
LetsEncryptDir: defaultLetsEncryptDir,
MacaroonPath: DefaultMacaroonPath,
SuperMacaroonPath: DefaultSuperMacaroonPath,
BakeSuperMacaroon: defaultBakeSuperMacaroon,
DatabaseBackend: DatabaseBackendSqlite,
Sqlite: &db.SqliteConfig{
DatabaseFileName: defaultSqliteDatabasePath,
@ -576,7 +626,9 @@ func defaultConfig() *Config {
// loadAndValidateConfig loads the terminal's main configuration and validates
// its content.
func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
func loadAndValidateConfig(ctx context.Context,
interceptor signal.Interceptor) (*Config, error) {
// Start with the default configuration.
preCfg := defaultConfig()
@ -587,18 +639,14 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
return nil, fmt.Errorf("error parsing flags: %w", err)
}
// Show the version and exit if the version flag was specified.
// Show the version and exit if the version flag was specified. This
// applies for both the `--version` and the `-V` flag.
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
if preCfg.ShowVersion {
if preCfg.ShowVersion || preCfg.Lnd.ShowVersion {
fmt.Println(appName, "version", RichVersion())
os.Exit(0)
}
if preCfg.Lnd.ShowVersion {
fmt.Println(appName, "version", build.Version(),
"commit="+build.Commit)
os.Exit(0)
}
// The logging config we use to initialise the sub-logger manager below
// depends on whether we're running in remote mode or not.
@ -673,6 +721,15 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
"to avoid problems", minimumRPCTimeout)
}
// The work that waits on lnd's readiness (currently the kvdb-to-SQL
// migration) cannot proceed without lnd, so a non-positive timeout
// would make it fail immediately on any node where lnd isn't
// instantly ready. Require a positive value.
if cfg.LndReadyTimeout <= 0 {
return nil, fmt.Errorf("lndreadytimeout must be positive, got "+
"%v", cfg.LndReadyTimeout)
}
// Validate the lightning-terminal config options.
litDir := lnd.CleanAndExpandPath(preCfg.LitDir)
cfg.LetsEncryptDir = lncfg.CleanAndExpandPath(cfg.LetsEncryptDir)
@ -727,6 +784,37 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
)
}
err = validateExclusiveSQLBackends(ctx, cfg, litDir)
if err != nil {
return nil, err
}
if cfg.BakeSuperMacaroon != defaultBakeSuperMacaroon {
if cfg.SuperMacaroonPath == DefaultSuperMacaroonPath {
cfg.SuperMacaroonPath = filepath.Join(
litDir, cfg.Network,
DefaultSuperMacaroonFilename,
)
}
err = macaroons.HasMacaroonSuffix(
cfg.SuperMacaroonPath,
)
if err != nil {
return nil, err
}
// Clean and expand the super macaroon path
cfg.SuperMacaroonPath = lncfg.CleanAndExpandPath(
cfg.SuperMacaroonPath,
)
dir := filepath.Dir(cfg.SuperMacaroonPath)
if err := makeDirectories(dir); err != nil {
return nil, fmt.Errorf("unable to create super "+
"macaroon directory %v: %w", dir, err)
}
}
err = cfg.DevConfig.Validate()
if err != nil {
return nil, err
@ -1149,6 +1237,130 @@ func readAutoMigrateKVDB(config *Config) error {
return nil
}
// validateExclusiveSQLBackends errors and thereby rejects startup when the
// inactive SQL backend still has data at its default location. This prevents
// silently switching to a different SQL store and starting against an empty
// database.
func validateExclusiveSQLBackends(ctx context.Context, cfg *Config,
litDir string) error {
switch cfg.DatabaseBackend {
case DatabaseBackendPostgres:
sqlitePath := filepath.Join(
litDir, cfg.Network, defaultSqliteDatabaseFileName,
)
exists, err := sqliteDatabaseExists(sqlitePath)
if err != nil {
return fmt.Errorf("unable to check for existing "+
"sqlite database file at %s: %w", sqlitePath,
err)
}
if exists {
return fmt.Errorf("cannot start litd with postgres "+
"backend: sqlite database file already exists "+
"at %s. If you really intend to switch to "+
"postgres you must delete the sqlite "+
"database (effectively deleting all stored "+
"data) before restarting with postgres again",
sqlitePath)
}
case DatabaseBackendSqlite:
exists, err := postgresDatabaseExists(ctx, cfg.Postgres)
if err != nil {
return fmt.Errorf("unable to check for existing "+
"postgres database %q at %s:%d for user %q. "+
"Note that a postgres configuration is set "+
"despite sqlite being set as the "+
"databasebackend: %w", cfg.Postgres.DBName,
cfg.Postgres.Host, cfg.Postgres.Port,
cfg.Postgres.User, err)
}
if exists {
return fmt.Errorf("cannot start litd with sqlite "+
"backend: postgres database %q already exists "+
"at %s:%d for user %q; If you really intend "+
"to switch to sqlite you must delete the "+
"postgres database (effectively deleting all "+
"stored data) before restarting with sqlite "+
"again", cfg.Postgres.DBName, cfg.Postgres.Host,
cfg.Postgres.Port, cfg.Postgres.User)
}
}
return nil
}
// sqliteDatabaseExists reports whether a SQLite database file exists at the
// given path.
func sqliteDatabaseExists(path string) (bool, error) {
_, err := os.Stat(path)
switch {
case err == nil:
return true, nil
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}
// postgresDatabaseExists reports whether a Postgres database can be reached
// with the configured connection info. If the configuration does not identify
// a concrete database, the check is skipped.
func postgresDatabaseExists(ctx context.Context,
cfg *db.PostgresConfig) (bool, error) {
if !hasPostgresConnectionInfo(cfg) {
return false, nil
}
dbConn, err := sql.Open("postgres", cfg.DSN(false))
if err != nil {
return false, fmt.Errorf("unable to check for existing "+
"postgres database %q: %w", cfg.DBName, err)
}
defer dbConn.Close()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
err = dbConn.PingContext(ctx)
switch {
case err == nil:
return true, nil
case isMissingPostgresDatabase(err):
return false, nil
default:
return false, fmt.Errorf("unable to check for existing "+
"postgres database %q: %w", cfg.DBName, err)
}
}
// hasPostgresConnectionInfo reports whether the config identifies a concrete
// Postgres database to probe.
func hasPostgresConnectionInfo(cfg *db.PostgresConfig) bool {
return cfg != nil && cfg.Host != "" && cfg.Port != 0 &&
cfg.User != "" && cfg.DBName != ""
}
// isMissingPostgresDatabase reports whether the probe failed because the
// target database does not exist. The lib/pq driver surfaces that as SQLSTATE
// 3D000 (invalid_catalog_name).
func isMissingPostgresDatabase(err error) bool {
var pqErr *pq.Error
if errors.As(err, &pqErr) {
return string(pqErr.Code) == "3D000"
}
return false
}
func buildTLSConfigForHttp2(config *Config) (*tls.Config, error) {
var tlsConfig *tls.Config

151
config_test.go Normal file
View file

@ -0,0 +1,151 @@
package terminal
import (
"database/sql"
"os"
"path/filepath"
"testing"
"github.com/lightninglabs/lightning-terminal/db"
"github.com/stretchr/testify/require"
)
// TestBlockStartupForPostgresIfSqliteDBExists verifies that a Postgres startup
// is rejected when the default SQLite database file still exists for the
// selected network.
func TestBlockStartupForPostgresIfSqliteDBExists(t *testing.T) {
litDir := t.TempDir()
sqlitePath := filepath.Join(
litDir, "regtest", defaultSqliteDatabaseFileName,
)
fixture := db.NewTestPgFixture(
t, db.DefaultPostgresFixtureLifetime, true,
)
t.Cleanup(func() {
fixture.TearDown(t)
})
require.NoError(t, os.MkdirAll(filepath.Dir(sqlitePath), 0700))
require.NoError(t, os.WriteFile(sqlitePath, []byte("sqlite"), 0600))
cfg := &Config{
DatabaseBackend: DatabaseBackendPostgres,
LitDir: litDir,
Network: "regtest",
Postgres: fixture.GetConfig(),
}
err := validateExclusiveSQLBackends(t.Context(), cfg, litDir)
require.Error(t, err)
require.Contains(t, err.Error(), "sqlite database file already exists")
require.Contains(t, err.Error(), sqlitePath)
}
// TestBlockStartupForSqliteIfPostgresDBExists verifies that a SQLite startup is
// rejected when the configured Postgres database already exists.
func TestBlockStartupForSqliteIfPostgresDBExists(t *testing.T) {
fixture := db.NewTestPgFixture(
t, db.DefaultPostgresFixtureLifetime, true,
)
t.Cleanup(func() {
fixture.TearDown(t)
})
cfg := &Config{
DatabaseBackend: DatabaseBackendSqlite,
Postgres: fixture.GetConfig(),
}
err := validateExclusiveSQLBackends(t.Context(), cfg, "")
require.Error(t, err)
require.Contains(t, err.Error(), "postgres database")
require.Contains(t, err.Error(), cfg.Postgres.DBName)
}
// TestDontBlockSqliteOnlyStartup verifies that SQLite startup is allowed when
// no concrete Postgres database is configured.
func TestDontBlockSqliteOnlyStartup(t *testing.T) {
cfg := &Config{
DatabaseBackend: DatabaseBackendSqlite,
}
require.NoError(
t, validateExclusiveSQLBackends(t.Context(), cfg, ""),
)
}
// TestDontBlockSqliteStartupIfConfiguredPostgresDoesntExist verifies that a
// SQLite startup is allowed when the configured Postgres database does not
// exist.
func TestDontBlockSqliteStartupIfConfiguredPostgresDoesntExist(t *testing.T) {
fixture := db.NewTestPgFixture(
t, db.DefaultPostgresFixtureLifetime, true,
)
t.Cleanup(func() {
fixture.TearDown(t)
})
pgCfg := fixture.GetConfig()
pgCfg.DBName = "does_not_exist_for_config_validation"
cfg := &Config{
DatabaseBackend: DatabaseBackendSqlite,
Postgres: pgCfg,
}
require.NoError(
t, validateExclusiveSQLBackends(t.Context(), cfg, ""),
)
// We also validate that the validateExclusiveSQLBackends passed because
// the db with the configured DBName doesn't exist and not because the
// connection parameters are wrong. This proves that we're ok with a
// postgres setup existing, as long as the specific database doesn't
// exist.
dbConn, err := sql.Open("postgres", pgCfg.DSN(false))
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, dbConn.Close())
})
err = dbConn.Ping()
require.Error(t, err)
require.True(t, isMissingPostgresDatabase(err))
}
// TestDontBlockPostgresOnlyStartup verifies that a Postgres is allowed when
// no sqlite database file exists for the selected network, despite the actual
// folder where the file would be placed exists.
func TestDontBlockPostgresOnlyStartup(t *testing.T) {
litDir := t.TempDir()
sqlitePath := filepath.Join(
litDir, "regtest", defaultSqliteDatabaseFileName,
)
fixture := db.NewTestPgFixture(
t, db.DefaultPostgresFixtureLifetime, true,
)
t.Cleanup(func() {
fixture.TearDown(t)
})
require.NoError(t, os.MkdirAll(filepath.Dir(sqlitePath), 0700))
// NOTE: we don't write any file to the sqlite path here, so the sqlite
// database file never exists, only the default directory where it would
// be located.
cfg := &Config{
DatabaseBackend: DatabaseBackendPostgres,
LitDir: litDir,
Network: "regtest",
Postgres: fixture.GetConfig(),
}
require.NoError(
t, validateExclusiveSQLBackends(
t.Context(), cfg, litDir,
),
)
}

View file

@ -37,6 +37,43 @@ const (
LatestDevMigrationVersion = 0
)
// MakeMigrationDescriptors returns the ordered migration descriptors for LiT's
// production SQL schema.
func MakeMigrationDescriptors() []sqldb.MigrationDescriptor {
return []sqldb.MigrationDescriptor{
{
Name: "accounts",
Version: 1,
SchemaVersion: 1,
},
{
Name: "sessions",
Version: 2,
SchemaVersion: 2,
},
{
Name: "kvstores",
Version: 3,
SchemaVersion: 3,
},
{
Name: "privacy_pairs",
Version: 4,
SchemaVersion: 4,
},
{
Name: "actions",
Version: 5,
SchemaVersion: 5,
},
{
Name: "kvdb_to_sql",
Version: 6,
SchemaVersion: 6,
},
}
}
// HasDevMigrations reports whether any dev SQL migration files are embedded in
// the current build. This lets dev builds omit the separate dev migration set
// cleanly when the directory exists but currently contains no migration files.
@ -67,6 +104,7 @@ func MakeTestMigrationSets() []sqldb.MigrationSet {
//
// NOTE: This MUST be updated when a new migration is added.
LatestMigrationVersion: LatestMigrationVersion,
Descriptors: MakeMigrationDescriptors(),
MakeProgrammaticMigrations: func(db *sqldb.BaseDB) (
map[uint]migrate.ProgrammaticMigrEntry, error) {

View file

@ -6,12 +6,14 @@ import (
"encoding/binary"
"errors"
"fmt"
"path/filepath"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/db/sqlcmig6"
"github.com/lightninglabs/lightning-terminal/db/tombstone"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightningnetwork/lnd/clock"
@ -19,12 +21,19 @@ import (
"github.com/lightningnetwork/lnd/sqldb/v2"
)
// listMacaroonIDRetryDelay is the delay between successive attempts to reach
// lnd's ListMacaroonIDs RPC while waiting for lnd to become ready. 500ms keeps
// the poll responsive (lnd is typically ready within a minute or two) without
// busy-looping against a not-yet-ready RPC server.
const listMacaroonIDRetryDelay = 500 * time.Millisecond
// Mig6ProgrammaticMigration generates and returns the programmatic migration
// entry containing the kvdb to SQL migration for all of litd's database stores.
func Mig6ProgrammaticMigration(ctx context.Context,
basicClient lnrpc.LightningClient, db *sqldb.BaseDB,
accountsDir, networkDir string, clock clock.Clock,
migVersion uint) migrate.ProgrammaticMigrEntry {
migVersion uint,
lndReadyTimeout time.Duration) migrate.ProgrammaticMigrEntry {
mig6queries := sqlcmig6.NewForType(db, db.BackendType)
mig6executor := sqldb.NewTransactionExecutor(
@ -47,6 +56,7 @@ func Mig6ProgrammaticMigration(ctx context.Context,
return kvdbToSqlProgrammaticMigration(
ctx, basicClient, accountsDir,
networkDir, db, clock, q6,
lndReadyTimeout,
)
}, sqldb.NoOpReset,
)
@ -85,9 +95,44 @@ func Mig6ProgrammaticMigration(ctx context.Context,
func kvdbToSqlProgrammaticMigration(ctx context.Context,
basicClient lnrpc.LightningClient, accountsDir, networkDir string,
_ *sqldb.BaseDB, clock clock.Clock, q *sqlcmig6.Queries) error {
_ *sqldb.BaseDB, clock clock.Clock, q *sqlcmig6.Queries,
lndReadyTimeout time.Duration) error {
start := time.Now()
accountsActive, err := tombstone.KVDBFileExists(
filepath.Join(accountsDir, accounts.DBFilename),
)
if err != nil {
return fmt.Errorf("unable to inspect accounts kvdb: %w", err)
}
sessionsActive, err := tombstone.KVDBFileExists(
filepath.Join(networkDir, session.DBFilename),
)
if err != nil {
return fmt.Errorf("unable to inspect session kvdb: %w", err)
}
firewallActive, err := tombstone.KVDBFileExists(
filepath.Join(networkDir, firewalldb.DBFilename),
)
if err != nil {
return fmt.Errorf("unable to inspect rules kvdb: %w", err)
}
if !accountsActive && !sessionsActive && !firewallActive {
log.Infof("Skipping KVDB to SQL migration for all stores: " +
"no legacy database files exist")
return nil
}
if basicClient == nil {
return errors.New("lightning client is required for " +
"migration but was nil")
}
log.Infof("Starting KVDB to SQL migration for all stores")
accountStore, err := accounts.NewBoltStoreForMigration(
@ -149,43 +194,18 @@ func kvdbToSqlProgrammaticMigration(ctx context.Context,
}
}()
// We'll fetch the macaroonIDList from `lnd` next. Note that since lnd's
// RPC servers may not have been fully started yet if the execution of
// accounts and session migration were really quick, we poll the request
// up to 120 times with a 0.5 second delay between the attempts. This
// should be a sufficient amount of time for the wallet to have been
// loaded and for the RPC servers to started.
const (
maxListMacaroonIDAttempts = 120
listMacaroonIDRetryDelay = 500 * time.Millisecond
// The firewalldb migration below needs lnd's macaroon root key IDs, but
// lnd only starts serving that RPC once it has reached its "RPC active"
// state, which can take well over a minute on nodes with a large
// channel and graph state. So we cannot assume lnd is ready by the time
// the (fast) accounts and session migrations above have completed, and
// instead poll until it is.
macaroonIDList, err := listMacaroonIDsWhenLndReady(
ctx, basicClient, lndReadyTimeout,
)
var macaroonIDList *lnrpc.ListMacaroonIDsResponse
for i := 1; i <= maxListMacaroonIDAttempts; i++ {
macaroonIDList, err = basicClient.ListMacaroonIDs(
ctx, &lnrpc.ListMacaroonIDsRequest{},
)
if err == nil {
break
}
if i == maxListMacaroonIDAttempts {
return fmt.Errorf("error listing macaroon IDs when "+
"migrating stores to SQL after %d attempts: %w",
maxListMacaroonIDAttempts, err)
}
log.Warnf("Failed to list macaroon IDs when migrating "+
"stores to SQL (attempt %d/%d), retrying in %v: %v",
i, maxListMacaroonIDAttempts, listMacaroonIDRetryDelay,
err)
select {
case <-ctx.Done():
return fmt.Errorf("context canceled while retrying "+
"to list macaroon IDs: %w", ctx.Err())
case <-time.After(listMacaroonIDRetryDelay):
}
if err != nil {
return fmt.Errorf("error listing macaroon IDs when migrating "+
"stores to SQL: %w", err)
}
log.Infof("Successfully listed macaroon IDs during store migration.")
@ -214,6 +234,64 @@ func kvdbToSqlProgrammaticMigration(ctx context.Context,
return nil
}
// listMacaroonIDsWhenLndReady calls lnd's ListMacaroonIDs RPC, retrying every
// listMacaroonIDRetryDelay until it succeeds, until the lndReadyTimeout budget
// is exhausted, or until the passed context is canceled (litd shutting down).
//
// NOTE: lndReadyTimeout is meant to be a generous backstop rather than a tight
// bound (see its default in the main config). litd cannot complete the kvdb to
// SQL migration without lnd, so timing out here aborts litd startup entirely
// and forces a manual restart; waiting longer for a slow-but-healthy lnd is
// strictly preferable to that.
func listMacaroonIDsWhenLndReady(ctx context.Context,
basicClient lnrpc.LightningClient,
lndReadyTimeout time.Duration) (*lnrpc.ListMacaroonIDsResponse, error) {
start := time.Now()
waitCtx, cancel := context.WithTimeout(ctx, lndReadyTimeout)
defer cancel()
for attempt := 1; ; attempt++ {
// We pass waitCtx rather than ctx so that a single hanging call
// cannot outlive the lnd-ready budget.
macaroonIDList, err := basicClient.ListMacaroonIDs(
waitCtx, &lnrpc.ListMacaroonIDsRequest{},
)
if err == nil {
return macaroonIDList, nil
}
log.Warnf("Failed to list macaroon IDs when migrating stores "+
"to SQL (attempt %d after %v): %v", attempt,
time.Since(start), err)
select {
case <-waitCtx.Done():
// waitCtx is derived from ctx, so it also fires when
// the daemon shuts down. Check the parent explicitly
// first so that a shutdown mid-retry is reported as
// such instead of as a readiness timeout.
if ctx.Err() != nil {
return nil, fmt.Errorf("aborted after %d "+
"attempts over %v while waiting for "+
"lnd's RPC server to become ready: "+
"%w", attempt, time.Since(start),
ctx.Err())
}
return nil, fmt.Errorf("lnd's RPC server did not "+
"become ready within %v (%d attempts over "+
"%v); increase --lndreadytimeout if lnd "+
"legitimately needs longer to start up: %w",
lndReadyTimeout, attempt, time.Since(start),
err)
case <-time.After(listMacaroonIDRetryDelay):
}
}
}
// deprecateKVDBStores marks the old kvdb stores as deprecated after the SQL
// migration committed successfully. We do this after the SQL transaction is
// committed so a failed SQL migration cannot strand the user with an unusable

View file

@ -0,0 +1,215 @@
package migsets
import (
"context"
"errors"
"net"
"os"
"path/filepath"
"testing"
"time"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/db"
"github.com/lightninglabs/lightning-terminal/db/sqlcmig6"
"github.com/lightninglabs/lightning-terminal/firewalldb"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/sqldb/v2"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
// testLndReadyTimeout is the lnd RPC-ready timeout used for the migration in
// these tests. The test lnd server is ready immediately, so any positive value
// works; we use a short one so a broken poll loop fails fast rather than
// hanging the test.
const testLndReadyTimeout = 5 * time.Second
// TestKVDBToSQLProgrammaticMigrationSkipsMissingStores verifies that the kvdb
// to SQL migration does not create missing legacy kvdb files while scanning for
// stores to migrate.
func TestKVDBToSQLProgrammaticMigrationSkipsMissingStores(t *testing.T) {
t.Parallel()
accountsDir := t.TempDir()
networkDir := t.TempDir()
sqlStore := sqldb.NewTestSqliteDB(t, db.MakeTestMigrationSets())
queries := sqlcmig6.NewForType(
sqlStore.BaseDB, sqlStore.BackendType,
)
err := kvdbToSqlProgrammaticMigration(
context.Background(), nil, accountsDir, networkDir,
sqlStore.BaseDB, clock.NewDefaultClock(), queries,
testLndReadyTimeout,
)
require.NoError(t, err)
requireNoFile(t, filepath.Join(accountsDir, accounts.DBFilename))
requireNoFile(t, filepath.Join(networkDir, session.DBFilename))
requireNoFile(t, filepath.Join(networkDir, firewalldb.DBFilename))
}
// TestKVDBToSQLProgrammaticMigrationRunsWithOneBBoltDBFiles verifies that the
// migration still runs when only the accounts kvdb exists, creates the missing
// legacy files, and migrates the existing account data to SQL.
func TestKVDBToSQLProgrammaticMigrationRunsWithOneBBoltDBFiles(t *testing.T) {
t.Parallel()
ctx := context.Background()
testClock := clock.NewDefaultClock()
accountsDir := t.TempDir()
networkDir := t.TempDir()
accountStore, err := accounts.NewBoltStore(
accountsDir, accounts.DBFilename, testClock,
)
require.NoError(t, err)
_, err = accountStore.NewAccount(ctx, 1234, time.Time{}, "acct-1")
require.NoError(t, err)
require.NoError(t, accountStore.Close())
sqlStore := sqldb.NewTestSqliteDB(t, db.MakeTestMigrationSets())
queries := sqlcmig6.NewForType(
sqlStore.BaseDB, sqlStore.BackendType,
)
lndClient := newTestLightningClient(t, nil)
err = kvdbToSqlProgrammaticMigration(
ctx, lndClient, accountsDir, networkDir,
sqlStore.BaseDB, testClock, queries,
testLndReadyTimeout,
)
require.NoError(t, err)
requireFileExists(t, filepath.Join(accountsDir, accounts.DBFilename))
requireFileExists(t, filepath.Join(networkDir, session.DBFilename))
requireFileExists(t, filepath.Join(networkDir, firewalldb.DBFilename))
dbAccounts, err := queries.ListAllAccounts(ctx)
require.NoError(t, err)
require.Len(t, dbAccounts, 1)
dbSessions, err := queries.ListSessions(ctx)
require.NoError(t, err)
require.Empty(t, dbSessions)
}
// TestListMacaroonIDsWhenLndReadyTimeout asserts that we give up with a helpful
// error, pointing at the lndreadytimeout option, once the lnd-ready budget is
// exhausted without lnd ever becoming ready.
func TestListMacaroonIDsWhenLndReadyTimeout(t *testing.T) {
t.Parallel()
// The server never becomes ready, so the only way out is the timeout.
// We use a timeout well below listMacaroonIDRetryDelay so that the
// budget is exhausted during the first retry wait.
lndClient := newTestLightningClient(t, errNotReady)
_, err := listMacaroonIDsWhenLndReady(
context.Background(), lndClient, 50*time.Millisecond,
)
require.ErrorContains(t, err, "did not become ready")
require.ErrorContains(t, err, "--lndreadytimeout")
require.ErrorContains(t, err, errNotReady.Error())
}
// TestListMacaroonIDsWhenLndReadyCancel asserts that a canceled parent context
// (litd shutting down) is surfaced as a cancellation rather than as a readiness
// timeout.
func TestListMacaroonIDsWhenLndReadyCancel(t *testing.T) {
t.Parallel()
lndClient := newTestLightningClient(t, errNotReady)
// Cancel the parent context up front, so that the generous timeout
// below is never the reason we stop retrying.
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := listMacaroonIDsWhenLndReady(ctx, lndClient, time.Hour)
require.ErrorIs(t, err, context.Canceled)
require.NotContains(t, err.Error(), "did not become ready")
}
func requireNoFile(t *testing.T, path string) {
t.Helper()
_, err := os.Stat(path)
require.ErrorIs(t, err, os.ErrNotExist)
}
func requireFileExists(t *testing.T, path string) {
t.Helper()
_, err := os.Stat(path)
require.NoError(t, err)
}
// errNotReady mimics the error lnd's RPC server returns before it has reached
// its "RPC active" state.
var errNotReady = errors.New("the RPC server is in the process of starting up")
// newTestLightningClient returns a client backed by an in-memory lnd stub. If
// listMacaroonIDsErr is non-nil, all ListMacaroonIDs calls fail with it,
// simulating an lnd that never becomes ready.
func newTestLightningClient(t *testing.T,
listMacaroonIDsErr error) lnrpc.LightningClient {
t.Helper()
lis := bufconn.Listen(1024 * 1024)
server := grpc.NewServer()
lnrpc.RegisterLightningServer(server, &testLightningServer{
listMacaroonIDsErr: listMacaroonIDsErr,
})
go func() {
_ = server.Serve(lis)
}()
t.Cleanup(func() {
server.Stop()
require.NoError(t, lis.Close())
})
conn, err := grpc.DialContext(
context.Background(), "passthrough:///bufnet",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(
func(context.Context, string) (net.Conn, error) {
return lis.Dial()
},
),
)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, conn.Close())
})
return lnrpc.NewLightningClient(conn)
}
type testLightningServer struct {
lnrpc.UnimplementedLightningServer
listMacaroonIDsErr error
}
func (t *testLightningServer) ListMacaroonIDs(context.Context,
*lnrpc.ListMacaroonIDsRequest) (*lnrpc.ListMacaroonIDsResponse, error) {
if t.listMacaroonIDsErr != nil {
return nil, t.listMacaroonIDsErr
}
return &lnrpc.ListMacaroonIDsResponse{}, nil
}

View file

@ -5,6 +5,7 @@ package migsets
import (
"context"
"path/filepath"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/pgx/v5"
@ -16,8 +17,8 @@ import (
// MakeMigrationSets creates the migration sets for production environments.
func MakeMigrationSets(ctx context.Context, basicClient lnrpc.LightningClient,
macPath, litDir, network string,
clock clock.Clock) []sqldb.MigrationSet {
macPath, litDir, network string, clock clock.Clock,
lndReadyTimeout time.Duration) []sqldb.MigrationSet {
accountsDir := filepath.Dir(macPath)
networkDir := filepath.Join(litDir, network)
@ -35,6 +36,7 @@ func MakeMigrationSets(ctx context.Context, basicClient lnrpc.LightningClient,
//
// NOTE: This MUST be updated when a new migration is added.
LatestMigrationVersion: db.LatestMigrationVersion,
Descriptors: db.MakeMigrationDescriptors(),
MakeProgrammaticMigrations: func(baseDB *sqldb.BaseDB) (
map[uint]migrate.ProgrammaticMigrEntry, error) {
@ -49,6 +51,7 @@ func MakeMigrationSets(ctx context.Context, basicClient lnrpc.LightningClient,
res[db.KVDBtoSQLMigVersion] = Mig6ProgrammaticMigration(
ctx, basicClient, baseDB, accountsDir,
networkDir, clock, db.KVDBtoSQLMigVersion,
lndReadyTimeout,
)
return res, nil

View file

@ -5,6 +5,7 @@ package migsets
import (
"context"
"path/filepath"
"time"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/pgx/v5"
@ -17,7 +18,8 @@ import (
// MakeMigrationSets creates the migration sets for the dev environments.
func MakeMigrationSets(ctx context.Context,
basicClient lnrpc.LightningClient, macPath, litDir, network string,
clock clock.Clock) []sqldb.MigrationSet {
clock clock.Clock,
lndReadyTimeout time.Duration) []sqldb.MigrationSet {
accountsDir := filepath.Dir(macPath)
networkDir := filepath.Join(litDir, network)
@ -34,6 +36,7 @@ func MakeMigrationSets(ctx context.Context,
//
// NOTE: This MUST be updated when a new migration is added.
LatestMigrationVersion: db.LatestMigrationVersion,
Descriptors: db.MakeMigrationDescriptors(),
MakeProgrammaticMigrations: func(baseDB *sqldb.BaseDB) (
map[uint]migrate.ProgrammaticMigrEntry, error) {
@ -48,6 +51,7 @@ func MakeMigrationSets(ctx context.Context,
res[db.KVDBtoSQLMigVersion] = Mig6ProgrammaticMigration(
ctx, basicClient, baseDB, accountsDir,
networkDir, clock, db.KVDBtoSQLMigVersion,
lndReadyTimeout,
)
return res, nil

View file

@ -11,6 +11,48 @@ import (
"time"
)
const accountPaymentsPaginated = `-- name: AccountPaymentsPaginated :many
SELECT account_id, hash, status, full_amount_msat
FROM account_payments
WHERE account_id = $1
ORDER BY hash ASC
LIMIT $2 OFFSET $3
`
type AccountPaymentsPaginatedParams struct {
AccountID int64
Limit int32
Offset int32
}
func (q *Queries) AccountPaymentsPaginated(ctx context.Context, arg AccountPaymentsPaginatedParams) ([]AccountPayment, error) {
rows, err := q.db.QueryContext(ctx, accountPaymentsPaginated, arg.AccountID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
defer rows.Close()
var items []AccountPayment
for rows.Next() {
var i AccountPayment
if err := rows.Scan(
&i.AccountID,
&i.Hash,
&i.Status,
&i.FullAmountMsat,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const addAccountInvoice = `-- name: AddAccountInvoice :exec
INSERT INTO account_invoices (account_id, hash)
VALUES ($1, $2)
@ -26,6 +68,19 @@ func (q *Queries) AddAccountInvoice(ctx context.Context, arg AddAccountInvoicePa
return err
}
const countAccountPayments = `-- name: CountAccountPayments :one
SELECT COUNT(*)
FROM account_payments
WHERE account_id = $1
`
func (q *Queries) CountAccountPayments(ctx context.Context, accountID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, countAccountPayments, accountID)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteAccount = `-- name: DeleteAccount :exec
DELETE FROM accounts
WHERE id = $1

View file

@ -10,7 +10,9 @@ import (
)
type Querier interface {
AccountPaymentsPaginated(ctx context.Context, arg AccountPaymentsPaginatedParams) ([]AccountPayment, error)
AddAccountInvoice(ctx context.Context, arg AddAccountInvoiceParams) error
CountAccountPayments(ctx context.Context, accountID int64) (int64, error)
DeleteAccount(ctx context.Context, id int64) error
DeleteAccountPayment(ctx context.Context, arg DeleteAccountPaymentParams) error
DeleteAllTempKVStores(ctx context.Context) error

View file

@ -80,6 +80,18 @@ WHERE account_id = $1;
SELECT *
FROM account_payments;
-- name: AccountPaymentsPaginated :many
SELECT *
FROM account_payments
WHERE account_id = $1
ORDER BY hash ASC
LIMIT $2 OFFSET $3;
-- name: CountAccountPayments :one
SELECT COUNT(*)
FROM account_payments
WHERE account_id = $1;
-- name: ListAccountInvoices :many
SELECT *
FROM account_invoices

View file

@ -34,10 +34,31 @@ const (
dbFilePermission = 0600
)
// KVDBFileExists reports whether the legacy bbolt database file exists at the
// given path. This only checks file presence and intentionally ignores any
// tombstone state inside the database.
func KVDBFileExists(path string) (bool, error) {
fi, err := os.Stat(path)
switch {
case err == nil:
return !fi.IsDir(), nil
case os.IsNotExist(err):
return false, nil
default:
return false, err
}
}
// DeprecateKVDB marks the given legacy bbolt database as deprecated by
// writing the migration tombstone marker into the specified top-level bucket.
func DeprecateKVDB(path string, timeout time.Duration, bucketKey []byte) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
exists, err := KVDBFileExists(path)
if err != nil {
return err
}
if !exists {
return nil
}
@ -59,7 +80,11 @@ func DeprecateKVDB(path string, timeout time.Duration, bucketKey []byte) error {
func CheckKVDBDeprecated(path string, bucketKey []byte,
timeout time.Duration) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
exists, err := KVDBFileExists(path)
if err != nil {
return err
}
if !exists {
return nil
}
@ -93,11 +118,15 @@ func CheckKVDBDeprecated(path string, bucketKey []byte,
func HasActiveKVDB(path string, bucketKey []byte,
timeout time.Duration) (bool, error) {
if _, err := os.Stat(path); os.IsNotExist(err) {
exists, err := KVDBFileExists(path)
if err != nil {
return false, err
}
if !exists {
return false, nil
}
err := CheckKVDBDeprecated(path, bucketKey, timeout)
err = CheckKVDBDeprecated(path, bucketKey, timeout)
switch {
case errors.Is(err, ErrKVDBDeprecated):
return false, nil

View file

@ -20,7 +20,7 @@ RUN cd /go/src/github.com/lightninglabs/lightning-terminal/app \
# The first stage is already done and all static assets should now be generated
# in the app/build sub directory.
FROM golang:1.25.10-alpine3.23@sha256:8d22e29d960bc50cd025d93d5b7c7d220b1ee9aa7a239b3c8f55a57e987e8d45 as golangbuilder
FROM golang:1.25.11-alpine3.23@sha256:60e626bbde32def8694687d03536ea4341b19e5f068e9a630225a1dfbd0505c9 as golangbuilder
# Instead of checking out from git again, we just copy the whole working
# directory of the previous stage that includes the generated static assets.

View file

@ -69,6 +69,25 @@ enforces the following rules on the RPC interface:
mapped invoice is paid, the amount is credited to that account's virtual
balance.
## Operator controls
LiT provides optional account safeguards through its configuration:
* `accounts.max-payment-size-msat` limits the total amount a single account
payment may debit, including fees. For `SendPaymentV2`, the configured fee
limit is included; for `SendToRouteV2`, the route's stated fee is included.
The default value of `0` disables this limit.
* `accounts.check-channel-balance` rejects new accounts, administrative
credits and administrative balance increases that would make the sum of all
account balances exceed the node's current local channel balance. It is
disabled by default. When enabled, allocations fail if LiT cannot query the
Lightning client for the channel balance.
Note: The channel-balance check only applies when a balance is allocated. It
cannot prevent the node's local balance from subsequently falling below the
allocated account balance if the operator sends non-account Lightning
payments or other non-account activity.
## Use cases
The following (definitely non-exhaustive) list of use cases is made possible by

View file

@ -12,6 +12,8 @@ bundled version will always come with the correct compatible versioning.
| LiT | min LND version |
|-------------------|-----------------|
| **v0.17.0-alpha** | v0.19.0-beta |
| **v0.16.1-alpha** | v0.19.0-beta |
| **v0.16.0-alpha** | v0.19.0-beta |
| **v0.15.3-alpha** | v0.19.0-beta |
| **v0.15.2-alpha** | v0.19.0-beta |

View file

@ -85,6 +85,12 @@
`litcli accounts update debit` and `litcli accounts update credit` commands
to modify an account's balance.
* [Add custom permissions support to sessions](https://github.com/lightninglabs/lightning-terminal/pull/1317):
Added the `--permission` flag to `litcli sessions add` to allow specifying
custom `entity:action` permissions (e.g. `info:read`), similar to
`lncli bakemacaroon`, for sessions of type `custom`. Supports repeated flags,
comma-separated lists, and mixed input.
### Technical and Architectural Updates
## RPC Updates
@ -93,11 +99,16 @@
### LND
* [Bump lnd to v0.21.0-beta and lndclient to
v0.21.0-1](https://github.com/lightninglabs/lightning-terminal/pull/1300).
* [Bump lnd to
v0.21.1-beta](https://github.com/lightninglabs/lightning-terminal/pull/1331).
### Loop
* Bump [`loop` to
v0.33.3-beta](https://github.com/lightninglabs/lightning-terminal/pull/1331),
Additionally, `looprpc` is bumped to `v1.0.14` and `swapserverrpc` is bumped
to `v1.0.21` match the version used in `loop` `v0.33.3-beta`.
### Pool
* Updated [`pool` to

View file

@ -0,0 +1,120 @@
# Release Notes
- [Lightning Terminal](#lightning-terminal)
- [Bug Fixes](#bug-fixes)
- [Functional Changes/Additions](#functional-changesadditions)
- [Technical and Architectural Updates](#technical-and-architectural-updates)
- [Integrated Binary Updates](#integrated-binary-updates)
- [LND](#lnd)
- [Loop](#loop)
- [Pool](#pool)
- [Faraday](#faraday)
- [Taproot Assets](#taproot-assets)
- [Contributors](#contributors-alphabetical-order)
## Lightning Terminal
### Bug Fixes
* [Gate wallet-ready status on lnd's actual RPC
readiness](https://github.com/lightninglabs/lightning-terminal/pull/1353):
Fixed a startup race where litd could report the LND sub-server as "Wallet
Ready" before lnd's RPC interceptor had actually left its
`WAITING_TO_START` state, so the very next call could still fail with
`rpc error: ... waiting to start`.
* [Don't mask account payment errors when request values are
absent](https://github.com/lightninglabs/lightning-terminal/pull/1322):
When a streaming account payment (`SendPaymentV2`/`SendToRouteV2`) fails and
lnd returns a terminal error after the request values have already been
cleaned up, lnd's underlying error is now passed through to the caller instead
of being masked by a confusing `no request values found for request: <id>`
error.
* [Wait longer for lnd during the kvdb-to-SQL
migration](https://github.com/lightninglabs/lightning-terminal/pull/1359):
The kvdb-to-SQL data migration polls lnd's `ListMacaroonIDs` RPC, which only
becomes available once lnd reaches its "RPC active" state. On nodes with a
large channel/graph state, lnd can take well over a minute to get there after
the wallet is unlocked, which exceeded the previous fixed 60-second poll
budget and caused the migration (and therefore litd startup) to fail
permanently, requiring a manual restart. The wait is now bounded by a
configurable, generous timeout (`--lndreadytimeout`, defaulting to 10
minutes) instead of a fixed attempt count.
### Functional Changes/Additions
* [Add accounts payments history subcommand](https://github.com/lightninglabs/lightning-terminal/pull/1316):
Added the `litcli accounts payments` subcommand and corresponding gRPC
endpoint `AccountPayments` to retrieve the off-chain payment history of
an account, supporting pagination (sorted in ascending lexicographical
order of their payment hash) and counting of total payments.
* [Auto-bake super macaroon on startup](https://github.com/lightninglabs/lightning-terminal/pull/1324):
Added config options `--bake-super-macaroon` (choice: `none`, `read-only`,
`read-write`) and `--super-macaroon-path` to automatically bake a super
macaroon on startup and keep its permissions in sync. When set to `read-only`
or `read-write`, the daemon will automatically bake a super macaroon
containing read-only or read-write permissions, respectively, for all active
sub-servers on startup. If the macaroon already exists but has different
permissions, it will be automatically regenerated.
* [Add a configurable maximum account payment
size](https://github.com/lightninglabs/lightning-terminal/pull/1369):
Addresses [
#583](https://github.com/lightninglabs/lightning-terminal/issues/583).
Added an `accounts.max-payment-size-msat` config option. When set to a
non-zero value, the account interceptor rejects any single account payment
(`SendPaymentV2`/`SendToRouteV2`) whose total amount, including fees,
exceeds the configured cap,
providing a guard rail against a compromised or misbehaving account macaroon
draining its balance in one large payment. It defaults to 0 (no cap),
preserving existing behaviour. This is a first step towards the finer-grained
per-account spending controls tracked in the issue (e.g. per-interval spend
limits).
* [Optionally cap total account balances at the node's channel
balance](https://github.com/lightninglabs/lightning-terminal/pull/1369):
Addresses
[#495](https://github.com/lightninglabs/lightning-terminal/issues/495).
Added an opt-in `accounts.check-channel-balance` config option. When enabled,
the accounts service rejects balance allocations (new accounts, administrative
credits and administrative balance increases) that would push the sum of all
account balances above the node's available local (outbound) channel balance,
helping operators avoid over-provisioning custodial accounts beyond what the
node can actually pay out. It defaults to off to preserve existing behaviour.
### Technical and Architectural Updates
* [Report litd's own version for `litd
-V`](https://github.com/lightninglabs/lightning-terminal/pull/1337): The `-V`
flag now prints litd's version instead of the integrated lnd version.
* [Refactor privacy mapper to prevent 32-bit truncation and optimize
allocations](https://github.com/lightninglabs/lightning-terminal/pull/1358):
Refactored the privacy mapper's random number generation to use `int64`
instead of `int` to prevent architecture-dependent truncation on 32-bit
runtimes. This represents a breaking change to the `NewPrivacyMapper` and
`CryptoRandIntn` functions. The PR also introduced a `sync.Pool` for
`*big.Int` to optimize allocations.
## RPC Updates
## Integrated Binary Updates
### LND
### Loop
### Pool
### Faraday
### Taproot Assets
# Contributors (Alphabetical Order)
* 0xfandom
* bitromortac
* Cyberguru1
* Vandit Singh

View file

@ -7,9 +7,11 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"math/big"
"strconv"
"strings"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -61,14 +63,14 @@ var _ mid.RequestInterceptor = (*PrivacyMapper)(nil)
// requests to their real values and vice versa for responses.
type PrivacyMapper struct {
db firewalldb.PrivacyMapper
randIntn func(int) (int, error)
randIntn func(int64) (int64, error)
sessionDB firewalldb.SessionDB
}
// NewPrivacyMapper returns a new instance of PrivacyMapper. The randIntn
// function is used to draw randomness for request field obfuscation.
func NewPrivacyMapper(newDB firewalldb.PrivacyMapper,
randIntn func(int) (int, error),
randIntn func(int64) (int64, error),
sessionDB firewalldb.SessionDB) *PrivacyMapper {
return &PrivacyMapper{
@ -379,7 +381,7 @@ func handleGetInfoResponse(db firewalldb.PrivacyMapDB,
func handleFwdHistoryResponse(db firewalldb.PrivacyMapDB,
flags session.PrivacyFlags,
randIntn func(int) (int, error)) func(ctx context.Context,
randIntn func(int64) (int64, error)) func(ctx context.Context,
r *lnrpc.ForwardingHistoryResponse) (proto.Message, error) {
return func(ctx context.Context, r *lnrpc.ForwardingHistoryResponse) (
@ -582,7 +584,7 @@ func handleListChannelsRequest(db firewalldb.PrivacyMapDB,
func handleListChannelsResponse(db firewalldb.PrivacyMapDB,
flags session.PrivacyFlags,
randIntn func(int) (int, error)) func(ctx context.Context,
randIntn func(int64) (int64, error)) func(ctx context.Context,
r *lnrpc.ListChannelsResponse) (proto.Message, error) {
return func(ctx context.Context, r *lnrpc.ListChannelsResponse) (
@ -866,7 +868,7 @@ func handleUpdatePolicyResponse(db firewalldb.PrivacyMapDB,
func handleWalletBalanceResponse(_ firewalldb.PrivacyMapDB,
flags session.PrivacyFlags,
randIntn func(int) (int, error)) func(ctx context.Context,
randIntn func(int64) (int64, error)) func(ctx context.Context,
r *lnrpc.WalletBalanceResponse) (proto.Message, error) {
return func(_ context.Context, r *lnrpc.WalletBalanceResponse) (
@ -945,7 +947,7 @@ func handleWalletBalanceResponse(_ firewalldb.PrivacyMapDB,
func handleClosedChannelsResponse(db firewalldb.PrivacyMapDB,
flags session.PrivacyFlags,
randIntn func(int) (int, error)) func(ctx context.Context,
randIntn func(int64) (int64, error)) func(ctx context.Context,
r *lnrpc.ClosedChannelsResponse) (proto.Message, error) {
return func(ctx context.Context, r *lnrpc.ClosedChannelsResponse) (
@ -1065,7 +1067,7 @@ func handleClosedChannelsResponse(db firewalldb.PrivacyMapDB,
// channel.
func obfuscatePendingChannel(ctx context.Context,
c *lnrpc.PendingChannelsResponse_PendingChannel,
tx firewalldb.PrivacyMapTx, randIntn func(int) (int, error),
tx firewalldb.PrivacyMapTx, randIntn func(int64) (int64, error),
flags session.PrivacyFlags) (
*lnrpc.PendingChannelsResponse_PendingChannel, error) {
@ -1142,7 +1144,7 @@ func obfuscatePendingChannel(ctx context.Context,
func handlePendingChannelsResponse(db firewalldb.PrivacyMapDB,
flags session.PrivacyFlags,
randIntn func(int) (int, error)) func(ctx context.Context,
randIntn func(int64) (int64, error)) func(ctx context.Context,
r *lnrpc.PendingChannelsResponse) (proto.Message, error) {
return func(ctx context.Context, r *lnrpc.PendingChannelsResponse) (
@ -1729,7 +1731,7 @@ func handleConnectPeerRequest(db firewalldb.PrivacyMapDB,
}
// maybeHideAmount hides an amount if the privacy flag is not set.
func maybeHideAmount(flags session.PrivacyFlags, randIntn func(int) (int,
func maybeHideAmount(flags session.PrivacyFlags, randIntn func(int64) (int64,
error), a int64) (int64, error) {
if !flags.Contains(session.ClearAmounts) {
@ -1748,8 +1750,8 @@ func maybeHideAmount(flags session.PrivacyFlags, randIntn func(int) (int,
// hideAmount symmetrically randomizes an amount around a given relative
// variation interval. relativeVariation should be between 0 and 1.
func hideAmount(randIntn func(n int) (int, error), relativeVariation float64,
amount uint64) (uint64, error) {
func hideAmount(randIntn func(n int64) (int64, error),
relativeVariation float64, amount uint64) (uint64, error) {
if relativeVariation < 0 || relativeVariation > 1 {
return 0, fmt.Errorf("hide amount: relative variation is not "+
@ -1757,6 +1759,11 @@ func hideAmount(randIntn func(n int) (int, error), relativeVariation float64,
relativeVariation)
}
if amount > math.MaxInt64 {
return 0, fmt.Errorf("hide amount: amount %d overflows "+
"int64", amount)
}
if amount == 0 {
return 0, nil
}
@ -1765,8 +1772,13 @@ func hideAmount(randIntn func(n int) (int, error), relativeVariation float64,
// between 0 and 1.
fuzzInterval := uint64(float64(amount) * relativeVariation)
amountMin := int(amount - fuzzInterval)
amountMax := int(amount + fuzzInterval)
if amount+fuzzInterval > math.MaxInt64 {
return 0, fmt.Errorf("hide amount: amount %d with variation "+
"overflows int64", amount)
}
amountMin := int64(amount - fuzzInterval)
amountMax := int64(amount + fuzzInterval)
randAmount, err := randBetween(randIntn, amountMin, amountMax)
if err != nil {
@ -1778,7 +1790,7 @@ func hideAmount(randIntn func(n int) (int, error), relativeVariation float64,
// hideTimestamp symmetrically randomizes a unix timestamp given an absolute
// variation interval. The random input is expected to be rand.Intn.
func hideTimestamp(randIntn func(n int) (int, error),
func hideTimestamp(randIntn func(n int64) (int64, error),
absoluteVariation time.Duration,
timestamp time.Time) (time.Time, error) {
@ -1802,18 +1814,20 @@ func hideTimestamp(randIntn func(n int) (int, error),
timeMax := timestamp.Add(absoluteVariation)
timeNs, err := randBetween(
randIntn, int(timeMin.UnixNano()), int(timeMax.UnixNano()),
randIntn, timeMin.UnixNano(), timeMax.UnixNano(),
)
if err != nil {
return time.Time{}, err
}
return time.Unix(0, int64(timeNs)), nil
return time.Unix(0, timeNs), nil
}
// randBetween generates a random number between [min, max) given a source of
// randomness.
func randBetween(randIntn func(int) (int, error), min, max int) (int, error) {
func randBetween(randIntn func(int64) (int64, error),
min, max int64) (int64, error) {
if max < min {
return 0, fmt.Errorf("min is not allowed to be greater than "+
"max, (min: %v, max: %v)", min, max)
@ -1833,7 +1847,7 @@ func randBetween(randIntn func(int) (int, error), min, max int) (int, error) {
}
// hideBool generates a random bool given a random input.
func hideBool(randIntn func(n int) (int, error)) (bool, error) {
func hideBool(randIntn func(n int64) (int64, error)) (bool, error) {
random, err := randIntn(2)
if err != nil {
return false, err
@ -1844,18 +1858,31 @@ func hideBool(randIntn func(n int) (int, error)) (bool, error) {
return random >= 1, nil
}
// bigIntPool is a pool of *big.Int values to avoid frequent heap allocations in
// CryptoRandIntn.
var bigIntPool = sync.Pool{
New: func() any {
return new(big.Int)
},
}
// CryptoRandIntn generates a random number between [0, n).
func CryptoRandIntn(n int) (int, error) {
func CryptoRandIntn(n int64) (int64, error) {
if n == 0 {
return 0, nil
}
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
nBig := bigIntPool.Get().(*big.Int)
defer bigIntPool.Put(nBig)
nBig.SetInt64(n)
randBig, err := rand.Int(rand.Reader, nBig)
if err != nil {
return 0, err
}
return int(nBig.Int64()), nil
return randBig.Int64(), nil
}
// ObfuscateConfig alters the config string by replacing sensitive data with

View file

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"math"
"testing"
"time"
@ -919,7 +920,9 @@ func TestPrivacyMapper(t *testing.T) {
require.NoError(t, err)
// randIntn is used for deterministic testing.
randIntn := func(n int) (int, error) { return 100, nil }
randIntn := func(n int64) (int64, error) {
return 100, nil
}
p := NewPrivacyMapper(db, randIntn, pd)
rawMsg, err := proto.Marshal(test.msg)
@ -1188,8 +1191,10 @@ var _ firewalldb.PrivacyMapDB = (*mockPrivacyMapDB)(nil)
// TestRandBetween tests random number generation for numbers in an interval.
func TestRandBetween(t *testing.T) {
min := 0
max := 10
var (
min int64
max int64 = 10
)
for i := 0; i < 100; i++ {
val, err := randBetween(CryptoRandIntn, min, max)
@ -1210,38 +1215,40 @@ func TestHideAmount(t *testing.T) {
tests := []struct {
name string
amount uint64
randIntFn func(int) (int, error)
randIntFn func(int64) (int64, error)
expected uint64
}{
{
name: "zero test amount",
randIntFn: func(int) (int, error) { return 0, nil },
randIntFn: func(int64) (int64, error) { return 0, nil },
},
{
name: "test small amount",
randIntFn: func(int) (int, error) { return 0, nil },
randIntFn: func(int64) (int64, error) { return 0, nil },
amount: 1,
expected: 1,
},
{
name: "min value",
randIntFn: func(int) (int, error) { return 0, nil },
randIntFn: func(int64) (int64, error) { return 0, nil },
amount: testAmount,
expected: lowerBound,
},
{
name: "max value",
randIntFn: func(int) (int, error) {
return int(upperBound - lowerBound), nil
randIntFn: func(n int64) (int64, error) {
return int64(upperBound - lowerBound), nil
},
amount: testAmount,
expected: upperBound,
},
{
name: "some fuzz",
randIntFn: func(int) (int, error) { return 123, nil },
amount: testAmount,
expected: lowerBound + 123,
name: "some fuzz",
randIntFn: func(int64) (int64, error) {
return 123, nil
},
amount: testAmount,
expected: lowerBound + 123,
},
}
@ -1270,6 +1277,33 @@ func TestHideAmount(t *testing.T) {
require.NoError(t, err)
}
})
t.Run("hideAmount overflow validation", func(t *testing.T) {
// amount > math.MaxInt64 should fail.
_, err := hideAmount(
CryptoRandIntn,
relativeVariation,
math.MaxInt64+1,
)
require.Error(t, err)
// amount <= math.MaxInt64 but amount + fuzzInterval >
// math.MaxInt64 should fail.
_, err = hideAmount(
CryptoRandIntn,
0.05,
math.MaxInt64-100,
)
require.Error(t, err)
// A value that just fits should succeed.
_, err = hideAmount(
CryptoRandIntn,
0.1,
922337203685477580,
)
require.NoError(t, err)
})
}
// TestHideTimestamp test correct timestamp hiding.
@ -1281,31 +1315,33 @@ func TestHideTimestamp(t *testing.T) {
tests := []struct {
name string
randIntFn func(int) (int, error)
randIntFn func(int64) (int64, error)
timestamp time.Time
expected time.Time
}{
{
name: "zero timestamp",
randIntFn: func(int) (int, error) { return 0, nil },
randIntFn: func(int64) (int64, error) { return 0, nil },
},
{
name: "min value",
randIntFn: func(int) (int, error) { return 0, nil },
randIntFn: func(int64) (int64, error) { return 0, nil },
timestamp: timestamp,
expected: lowerBound,
},
{
name: "max value",
randIntFn: func(int) (int, error) {
return int(upperBound.Sub(lowerBound)), nil
randIntFn: func(n int64) (int64, error) {
return int64(upperBound.Sub(lowerBound)), nil
},
timestamp: timestamp,
expected: upperBound,
},
{
name: "some fuzz",
randIntFn: func(int) (int, error) { return 123, nil },
name: "some fuzz",
randIntFn: func(int64) (int64, error) {
return 123, nil
},
timestamp: timestamp,
expected: lowerBound.Add(time.Duration(123)),
},
@ -1328,17 +1364,37 @@ func TestHideTimestamp(t *testing.T) {
// TestHideBool test correct boolean hiding.
func TestHideBool(t *testing.T) {
val, err := hideBool(func(int) (int, error) { return 100, nil })
require.NoError(t, err)
require.True(t, val)
tests := []struct {
name string
random int64
expected bool
}{
{
name: "random value",
random: 100,
expected: true,
},
{
name: "exact threshold value",
random: 1,
expected: true,
},
{
name: "below threshold value",
random: 0,
expected: false,
},
}
val, err = hideBool(func(int) (int, error) { return 1, nil })
require.NoError(t, err)
require.True(t, val)
val, err = hideBool(func(int) (int, error) { return 0, nil })
require.NoError(t, err)
require.False(t, val)
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
val, err := hideBool(func(n int64) (int64, error) {
return tc.random, nil
})
require.NoError(t, err)
require.Equal(t, tc.expected, val)
})
}
}
// TestObfuscateConfig tests that we substitute substrings in the config
@ -1605,3 +1661,27 @@ func variance(numbers []uint64) uint64 {
return uint64(sum)
}
// Test32BitOverflowAndTruncation ensures values > MaxInt32 do not truncate on
// 32-bit runtimes.
func Test32BitOverflowAndTruncation(t *testing.T) {
largeValue := int64(math.MaxInt32) + 1
// Ensure randBetween successfully operates on values larger than
// MaxInt32 without any architecture-dependent truncation.
mockRand := func(n int64) (int64, error) {
return n - 1, nil
}
val, err := randBetween(mockRand, largeValue, largeValue+10)
require.NoError(t, err)
require.Equal(t, largeValue+9, val)
// Ensure hideTimestamp works with a 64-bit nanosecond timestamp
// representing a real far-future date (> MaxInt32).
farFutureTime := time.Unix(0, 1600000000000000000) // ~Year 2020 in ns
variation := 10 * time.Minute
resTime, err := hideTimestamp(mockRand, variation, farFutureTime)
require.NoError(t, err)
require.NotEmpty(t, resTime)
}

View file

@ -20,8 +20,6 @@ type SQLSessionQueries interface {
// SQLQueries is a subset of the sqlc.Queries interface that can be used to
// interact with various firewalldb tables.
type SQLQueries interface {
sqldb.BaseQuerier
SQLKVStoreQueries
SQLPrivacyPairQueries
SQLActionQueries
@ -48,7 +46,7 @@ type SQLDB struct {
clock clock.Clock
}
type sqlQueriesExecutor[T sqldb.BaseQuerier] struct {
type sqlQueriesExecutor[T any] struct {
*sqldb.TransactionExecutor[T]
SQLQueries

44
go.mod
View file

@ -1,6 +1,6 @@
module github.com/lightninglabs/lightning-terminal
go 1.25.10
go 1.25.11
require (
github.com/btcsuite/btcd v0.25.1-0.20260310163610-1c55c7c18179
@ -24,35 +24,35 @@ require (
github.com/lightninglabs/lightning-node-connect/mailbox v1.0.2-0.20250610182311-2f1d46ef18b7
github.com/lightninglabs/lightning-terminal/autopilotserverrpc v0.0.3
github.com/lightninglabs/lndclient v0.21.0-1
github.com/lightninglabs/loop v0.31.8-beta
github.com/lightninglabs/loop/looprpc v1.0.13
github.com/lightninglabs/loop/swapserverrpc v1.0.20
github.com/lightninglabs/loop v0.33.3-beta
github.com/lightninglabs/loop/looprpc v1.0.14
github.com/lightninglabs/loop/swapserverrpc v1.0.21
github.com/lightninglabs/pool v0.7.1-beta
github.com/lightninglabs/pool/auctioneerrpc v1.1.3
github.com/lightninglabs/pool/poolrpc v1.0.1
github.com/lightninglabs/taproot-assets v0.8.0
github.com/lightninglabs/taproot-assets/taprpc v1.1.0
github.com/lightningnetwork/lnd v0.21.0-beta
github.com/lightningnetwork/lnd v0.21.1-beta
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/fn v1.2.5
github.com/lightningnetwork/lnd/fn/v2 v2.0.9
github.com/lightningnetwork/lnd/kvdb v1.4.16
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0-20260326184657-f7cc56305bae
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0
github.com/lightningnetwork/lnd/tlv v1.3.2
github.com/lightningnetwork/lnd/tor v1.1.6
github.com/lightningnetwork/lnd/tor v1.1.7
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9
github.com/ory/dockertest/v3 v3.10.0
github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/testify v1.11.1
github.com/urfave/cli v1.22.14
github.com/urfave/cli v1.22.16
go.etcd.io/bbolt v1.4.3
golang.org/x/crypto v0.47.0
golang.org/x/crypto v0.52.0
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50
golang.org/x/net v0.49.0
golang.org/x/sync v0.19.0
google.golang.org/grpc v1.80.0
golang.org/x/net v0.55.0
golang.org/x/sync v0.20.0
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
gopkg.in/macaroon-bakery.v2 v2.3.0
gopkg.in/macaroon.v2 v2.1.0
@ -93,7 +93,7 @@ require (
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/decred/dcrd/lru v1.1.2 // indirect
@ -174,7 +174,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/opencontainers/runc v1.3.6 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
@ -222,15 +222,15 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@ -250,7 +250,7 @@ replace (
// We want to format raw bytes as hex instead of base64. The forked version
// allows us to specify that as an option. This is required for the
// taproot-assets dependency to function properly.
replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display
replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display
// We are using a fork of the migration library in tapd with custom
// functionality that did not yet make it into the upstream repository. Because

88
go.sum
View file

@ -600,7 +600,7 @@ git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3p
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -748,9 +748,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
@ -1144,12 +1143,12 @@ github.com/lightninglabs/lightning-node-connect/mailbox v1.0.2-0.20250610182311-
github.com/lightninglabs/lightning-node-connect/mailbox v1.0.2-0.20250610182311-2f1d46ef18b7/go.mod h1:cQA5pybYbERkMlE8j49LITZZozQZIXABbWJLIpMWnus=
github.com/lightninglabs/lndclient v0.21.0-1 h1:NuyccCK7tbMaH7hhqtewcx+qeBel4/RLJrlnQ/lMkkY=
github.com/lightninglabs/lndclient v0.21.0-1/go.mod h1:RUIcfPr82HrvZr3pu9f8nbD5v6VFbm+KgExqNNp5bE4=
github.com/lightninglabs/loop v0.31.8-beta h1:9vYO5kDLrtQ9Mx41r0dAcVmZM1aQmkaDWwLUK1qOAes=
github.com/lightninglabs/loop v0.31.8-beta/go.mod h1:WHtv21ZYDuYrPWTIXlvAW3x78LPW7LFDCEOYsS1LBzU=
github.com/lightninglabs/loop/looprpc v1.0.13 h1:bOWDp+XnG28wP9Q8ZXvhhVuAQ7yYhuvPIfJk68GVLWk=
github.com/lightninglabs/loop/looprpc v1.0.13/go.mod h1:+m2HQ5gfMpoq449gyTsub/e3dKrOzAR6iWY11NV610M=
github.com/lightninglabs/loop/swapserverrpc v1.0.20 h1:/svfGdnwXE0++IvcNc50zjnpLPcJcvf+i2zPDhrAITo=
github.com/lightninglabs/loop/swapserverrpc v1.0.20/go.mod h1:nIgLTTEZ/fdWi9UVjNZ8XkkDMZI9wcEEVkEl9Wltyb4=
github.com/lightninglabs/loop v0.33.3-beta h1:L8wYQZIrz9Xpsre0eSGEfO0ahthr7sVjLzDBxaedx7w=
github.com/lightninglabs/loop v0.33.3-beta/go.mod h1:nQIASE+Y6X5x7I0RY9hH/Q748+Oi90OdqibpwYAnAKE=
github.com/lightninglabs/loop/looprpc v1.0.14 h1:9gaAVGplNZMyalLV8qxILXDD9F8QSgdtOgrtOFw60V4=
github.com/lightninglabs/loop/looprpc v1.0.14/go.mod h1:vUYOyRjWWKSnCrFyugiXuS90eC/FajYoTY3OE18cRNo=
github.com/lightninglabs/loop/swapserverrpc v1.0.21 h1:MI8LhS7jtjqI6b7AcE/kHO9WKGVxiWCMmt302dBsM6A=
github.com/lightninglabs/loop/swapserverrpc v1.0.21/go.mod h1:c0rJiM9TiZ7gr/snaALWixTf3UU1GNWZ9sYASTTHSZk=
github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789 h1:7kX7vUgHUazAHcCJ6uzBDa4/2MEGEbMEfa01GtfqmTQ=
github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/lightninglabs/neutrino v0.17.1 h1:lNhgq7ix/N81R6oATroP/kHMzH1qzVVF2dEGcTlN2t4=
@ -1162,16 +1161,16 @@ github.com/lightninglabs/pool/auctioneerrpc v1.1.3 h1:Di5Nf8Gll9wl6tbXsusGXj9e4y
github.com/lightninglabs/pool/auctioneerrpc v1.1.3/go.mod h1:N/X9SxrBCjquK8XkwSgk4qvGC2g8Dra9uiw5sXWmEl0=
github.com/lightninglabs/pool/poolrpc v1.0.1 h1:XbNx28TYwEj/PVsnnF9TnveVCMCYfS1vVkcwz29vPmM=
github.com/lightninglabs/pool/poolrpc v1.0.1/go.mod h1:836icifg/SBnZbiae0v3jeRRzCrT6LWo32SqCS/JiGk=
github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g=
github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display h1:fjePiMfYbg3KodAiSXwkpGZpYdPeNd9VnggJWiY6AaY=
github.com/lightninglabs/protobuf-go-hex-display v1.36.11-hex-display/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
github.com/lightninglabs/taproot-assets v0.8.0 h1:8Mky342/f5hbVm94Owj0YveIdKuTasJwFB6uqAa5/gc=
github.com/lightninglabs/taproot-assets v0.8.0/go.mod h1:SEoMeNzpENVUPnvuqllkDhl7QvNE74+Dtb/dnkbrygg=
github.com/lightninglabs/taproot-assets/taprpc v1.1.0 h1:Oum7ddGygrEaT+NHqpaQI8U6pV5jJUH4hvhezl5y00k=
github.com/lightninglabs/taproot-assets/taprpc v1.1.0/go.mod h1:X7XP753o8xCgjVI2mRu1Tvpyk3k4uybGDMFhpF6IRxI=
github.com/lightningnetwork/lightning-onion v1.3.0 h1:FqILgHjD6euc/Muo1VOzZ4+XDPuFnw6EYROBq0rR/5c=
github.com/lightningnetwork/lightning-onion v1.3.0/go.mod h1:nP85zMHG7c0si/eHBbSQpuDCtnIXfSvFrK3tW6YWzmU=
github.com/lightningnetwork/lnd v0.21.0-beta h1:bDP5UH15E7DVGTztsmBPQLqgyilq5EXDrglvQFmRc3U=
github.com/lightningnetwork/lnd v0.21.0-beta/go.mod h1:HcKq9DyxbVEZXuR28TIyGbIIgAjANCxI+N6dqOnRBAA=
github.com/lightningnetwork/lnd v0.21.1-beta h1:XYr8CRY3EB5bQ8I43/6QbOZP1dRi/XI6dwzTHlS2jxQ=
github.com/lightningnetwork/lnd v0.21.1-beta/go.mod h1:iqqf8Cyq0F7ssUftb17+Y644OWVg2OMOpvWSyzympXs=
github.com/lightningnetwork/lnd/actor v0.0.6 h1:Ge8N2wivARG+27qJBwTlB0vwsypStZYZy8vk4Zl38sU=
github.com/lightningnetwork/lnd/actor v0.0.6/go.mod h1:YAsoniSbY/cAM9HTVNfZLvt7RI6swDxy6wzPspTcMZg=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
@ -1190,14 +1189,14 @@ github.com/lightningnetwork/lnd/queue v1.2.0 h1:sSrn+u84OLuOT/F+xGxgg8VfknXeIZEA
github.com/lightningnetwork/lnd/queue v1.2.0/go.mod h1:qLNP0L3B7piRGvDyhAyJKic4xTt+Mw4D7mWrQeuAwxY=
github.com/lightningnetwork/lnd/sqldb v1.0.13 h1:CcG9mrHNW/hIuZnqgosdiNmS7QhjSyfR/XkSFJB7EC8=
github.com/lightningnetwork/lnd/sqldb v1.0.13/go.mod h1:ew3kMfknA0B4djTtrQSAkxvro+8+c++L8LuNaoT7GQA=
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0-20260326184657-f7cc56305bae h1:ICRuZIkXed43iuqcJaayubPTcQolttttwNZFrapdwIo=
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0-20260326184657-f7cc56305bae/go.mod h1:T2F1Sfb0oSpZyylIEE3ijiSejaXvIExER5xEdoe5wEE=
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0 h1:c0tt+rlsXCPmPlw641ruOe+EmeyFPWmD+E4sONaj0lc=
github.com/lightningnetwork/lnd/sqldb/v2 v2.0.0/go.mod h1:TyfjJz1iAKJA3uw3Tk7Mto0FJZY/MR5mAUc5qI80zB4=
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM=
github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0=
github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc=
github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE=
github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI=
github.com/lightningnetwork/lnd/tor v1.1.7 h1:BfY1KQGz3LVaeaAGI4XYGYCwDQR1ADFD1P/uXsS2dQc=
github.com/lightningnetwork/lnd/tor v1.1.7/go.mod h1:DANQ6QCQBiR+CqrM+mKudgkMy6CR/RS4ALDbjb2W4FU=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
@ -1269,8 +1268,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
@ -1374,6 +1373,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
@ -1382,8 +1382,8 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
github.com/urfave/cli-docs/v3 v3.1.1-0.20251020101624-bec07369b4f6 h1:cm0BrTu3Q0CNo+vB5kErCcVMZqD2D1z7y3YVIiBlr+o=
github.com/urfave/cli-docs/v3 v3.1.1-0.20251020101624-bec07369b4f6/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
@ -1506,8 +1506,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1567,8 +1567,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1642,8 +1642,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1690,8 +1690,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1795,8 +1795,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1808,8 +1808,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1827,8 +1827,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1908,8 +1908,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2131,10 +2131,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -2176,8 +2176,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=

View file

@ -2,8 +2,11 @@ package itest
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"sort"
"strings"
"testing"
"time"
@ -14,8 +17,10 @@ import (
"github.com/lightninglabs/taproot-assets/rpcutils"
"github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
@ -164,12 +169,315 @@ func runAccountSystemTest(t *harnessTest, node *HarnessNode, hostPort,
ctxa, t, rawConn, charlie, acctBalance,
)
// Initiate a HODL payment (which remains IN_FLIGHT) and a FAILED
// payment to verify that the AccountPayments RPC correctly retrieves
// and reports payments across all potential lifecycles (SUCCEEDED,
// IN_FLIGHT, FAILED).
testCtx, testCancel := context.WithTimeout(ctxb, defaultTimeout)
defer testCancel()
routerClient := routerrpc.NewRouterClient(rawConn)
// 1. Initiate HODL payment (IN_FLIGHT)
var preimage lntypes.Preimage
_, err = rand.Read(preimage[:])
require.NoError(t.t, err)
holdHash := preimage.Hash()
holdInv, err := charlie.AddHoldInvoice(
testCtx, &invoicesrpc.AddHoldInvoiceRequest{
Hash: holdHash[:],
Value: 2222,
},
)
require.NoError(t.t, err)
sendReqHold := &routerrpc.SendPaymentRequest{
PaymentRequest: holdInv.PaymentRequest,
TimeoutSeconds: 2,
FeeLimitMsat: 1000,
}
holdStream, err := routerClient.SendPaymentV2(ctxa, sendReqHold)
require.NoError(t.t, err)
holdPayment, err := getPaymentResult(holdStream, true)
require.NoError(t.t, err)
require.Equal(t.t, lnrpc.Payment_IN_FLIGHT, holdPayment.Status)
// Wait for the hold invoice to be accepted by Charlie.
require.Eventually(t.t, func() bool {
inv, err := charlie.LookupInvoice(
testCtx, &lnrpc.PaymentHash{
RHash: holdHash[:],
},
)
return err == nil &&
inv.State == lnrpc.Invoice_ACCEPTED
}, defaultTimeout, 100*time.Millisecond)
// 2. Initiate a FAILED payment
var failedHash lntypes.Hash
_, err = rand.Read(failedHash[:])
require.NoError(t.t, err)
var fakePubKey [33]byte
fakePubKey[0] = 0x02
sendReqFailed := &routerrpc.SendPaymentRequest{
Dest: fakePubKey[:],
Amt: 1111,
PaymentHash: failedHash[:],
TimeoutSeconds: 2,
FeeLimitMsat: 1000,
}
failedStream, err := routerClient.SendPaymentV2(ctxa, sendReqFailed)
require.NoError(t.t, err)
failedPayment, err := getPaymentResult(failedStream, false)
require.NoError(t.t, err)
require.Equal(t.t, lnrpc.Payment_FAILED, failedPayment.Status)
// Test AccountPayments RPC with all 3 payments (succeeded,
// in-flight, failed).
paymentsResp, err := acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
CountTotalPayments: true,
},
)
require.NoError(t.t, err)
require.Len(t.t, paymentsResp.Payments, 3)
// Sort the payments by value descending to ensure index-based
// assertions are deterministic regardless of database sorting by hash.
sort.Slice(paymentsResp.Payments, func(i, j int) bool {
valI := paymentsResp.Payments[i].ValueSat
valJ := paymentsResp.Payments[j].ValueSat
return valI > valJ
})
// Check the succeeded payment. This corresponds to the 4444 sat
// payment initiated earlier in testAccountRestrictions using this
// account.
require.Equal(
t.t, uint64(4444),
uint64(paymentsResp.Payments[0].ValueSat),
)
require.Equal(
t.t, lnrpc.Payment_SUCCEEDED,
paymentsResp.Payments[0].Status,
)
// Check In-flight payment
require.Equal(
t.t, uint64(2222),
uint64(paymentsResp.Payments[1].ValueSat),
)
require.Equal(
t.t, lnrpc.Payment_IN_FLIGHT,
paymentsResp.Payments[1].Status,
)
// Check Failed payment
require.Equal(
t.t, uint64(1111),
uint64(paymentsResp.Payments[2].ValueSat),
)
require.Equal(
t.t, lnrpc.Payment_FAILED,
paymentsResp.Payments[2].Status,
)
require.EqualValues(t.t, 3, paymentsResp.TotalNumPayments)
require.EqualValues(t.t, 0, paymentsResp.FirstIndexOffset)
require.EqualValues(t.t, 3, paymentsResp.LastIndexOffset)
// Query by label.
paymentsResp, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Label{
Label: acctLabel,
},
},
},
)
require.NoError(t.t, err)
require.Len(t.t, paymentsResp.Payments, 3)
// Query with pagination limit.
paymentsResp, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
MaxPayments: 2,
},
)
require.NoError(t.t, err)
require.Len(t.t, paymentsResp.Payments, 2)
require.EqualValues(t.t, 0, paymentsResp.FirstIndexOffset)
require.EqualValues(t.t, 2, paymentsResp.LastIndexOffset)
// Query with pagination offset out of bounds.
paymentsResp, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
IndexOffset: 3,
},
)
require.NoError(t.t, err)
require.Empty(t.t, paymentsResp.Payments)
require.EqualValues(t.t, 0, paymentsResp.FirstIndexOffset)
require.EqualValues(t.t, 0, paymentsResp.LastIndexOffset)
// Query with pagination offset inside bounds, where the number of
// payments returned is fewer than MaxPayments.
paymentsResp, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
IndexOffset: 2,
MaxPayments: 2,
},
)
require.NoError(t.t, err)
require.Len(t.t, paymentsResp.Payments, 1)
require.EqualValues(t.t, 2, paymentsResp.FirstIndexOffset)
require.EqualValues(t.t, 3, paymentsResp.LastIndexOffset)
// Query with invalid pagination max_payments (exceeding 50).
_, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
MaxPayments: 51,
},
)
require.Error(t.t, err)
require.Contains(t.t, err.Error(), "max_payments cannot exceed 50")
// Query with invalid pagination index_offset (exceeding 31-bit int).
_, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
IndexOffset: 0x80000000,
},
)
require.Error(t.t, err)
require.Contains(t.t, err.Error(), "index_offset out of range")
// Test the same account restrictions with an LNC session that is bound
// to the account.
testAccountRestrictionsLNC(
ctxm, t, rawConn, newAcctBalance, acctResp.Account.Id,
)
// Make sure a payment that the account checker rejects surfaces the
// real error instead of the masked "no request values found" error.
testAccountPaymentErrorPassthrough(
ctxa, t, rawConn, charlie, newAcctBalance,
)
// Settle the HODL invoice to clean up node state.
_, err = charlie.SettleInvoice(testCtx, &invoicesrpc.SettleInvoiceMsg{
Preimage: preimage[:],
})
require.NoError(t.t, err)
// Delete a single failed payment from LND to simulate a desync case
// where this payment exists in LiT's account database but is
// deleted/absent in LND.
_, err = node.LightningClient.DeletePayment(
testCtx, &lnrpc.DeletePaymentRequest{
PaymentHash: failedHash[:],
},
)
require.NoError(t.t, err)
// Now call AccountPayments. It should find all 3 payments in the local
// store, returning a placeholder entry with status Payment_UNKNOWN for
// the deleted payment that was not found in LND.
paymentsResp, err = acctClient.AccountPayments(
ctxm, &litrpc.AccountPaymentsRequest{
Account: &litrpc.AccountIdentifier{
Identifier: &litrpc.AccountIdentifier_Id{
Id: acctResp.Account.Id,
},
},
CountTotalPayments: true,
},
)
require.NoError(t.t, err)
require.Len(t.t, paymentsResp.Payments, 3)
// Sort the payments by value descending to ensure index-based
// assertions are deterministic regardless of database sorting by hash.
sort.Slice(paymentsResp.Payments, func(i, j int) bool {
valI := paymentsResp.Payments[i].ValueSat
valJ := paymentsResp.Payments[j].ValueSat
return valI > valJ
})
// Succeeded payment
require.Equal(
t.t, uint64(4444),
uint64(paymentsResp.Payments[0].ValueSat),
)
require.Equal(
t.t, lnrpc.Payment_SUCCEEDED,
paymentsResp.Payments[0].Status,
)
// Settled hold payment
require.Equal(
t.t, uint64(2222),
uint64(paymentsResp.Payments[1].ValueSat),
)
require.Equal(
t.t, lnrpc.Payment_SUCCEEDED,
paymentsResp.Payments[1].Status,
)
// Placeholder payment for the desynced/deleted payment
require.Equal(
t.t, hex.EncodeToString(failedHash[:]),
paymentsResp.Payments[2].PaymentHash,
)
require.Equal(
t.t, lnrpc.Payment_UNKNOWN,
paymentsResp.Payments[2].Status,
)
require.Equal(
t.t, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
paymentsResp.Payments[2].FailureReason,
)
require.EqualValues(t.t, 3, paymentsResp.TotalNumPayments)
require.EqualValues(t.t, 0, paymentsResp.FirstIndexOffset)
require.EqualValues(t.t, 3, paymentsResp.LastIndexOffset)
// Clean up our channel and payments, so we can start the next test
// iteration with a clean slate.
closeChannelAndAssert(t, net, node, channelOp, false)
@ -240,7 +548,7 @@ func testAccountRestrictionsLNC(ctxm context.Context, t *harnessTest,
// There should be invoices and payments from the previous test over RPC
// directly.
assertNumInvoices(ctxt, t.t, lightningClient, 1)
assertNumPayments(ctxt, t.t, lightningClient, 1)
assertNumPayments(ctxt, t.t, lightningClient, 3)
}
// testAccountRestrictions tests the different scenarios in which the account
@ -313,6 +621,56 @@ func testAccountRestrictions(ctxa context.Context, t *harnessTest,
return initialAccountBalance + inboundPaymentAmt - outboundPaymentAmt
}
// testAccountPaymentErrorPassthrough verifies that a payment which the account
// checker rejects because the account balance is insufficient surfaces a clear
// account-balance error to the caller, and never the masked "no request values
// found for request: <id>" error.
//
// SendPaymentV2 is a streaming RPC, so lnd surfaces the account checker's
// rejection through the stream's terminal error path. That reaches
// erroredPaymentHandler with no stored request values, which is exactly the
// case where the error must pass through unmasked instead of being replaced by
// a confusing "no request values found" error.
func testAccountPaymentErrorPassthrough(ctxa context.Context, t *harnessTest,
rawConn grpc.ClientConnInterface, charlie *HarnessNode,
accountBalance uint64) {
ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
routerClient := routerrpc.NewRouterClient(rawConn)
// Create a routable invoice on Charlie whose amount exceeds the account
// balance, so the only possible reason the payment fails is the
// insufficient account balance.
excessiveAmt := int64(accountBalance) + 10_000
invoice, err := charlie.AddInvoice(ctxt, &lnrpc.Invoice{
Value: excessiveAmt,
Memo: "exceeds account balance",
})
require.NoError(t.t, err)
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: 1000,
}
stream, err := routerClient.SendPaymentV2(ctxa, sendReq)
require.NoError(t.t, err)
// The payment must fail with the underlying account-balance error and
// not be masked by the confusing "no request values found" error.
_, err = getPaymentResult(stream, false)
require.Error(t.t, err)
require.NotContains(t.t, err.Error(), "no request values found")
require.Contains(t.t, err.Error(), "account balance insufficient")
// The account balance must be untouched by the rejected payment.
lightningClient := lnrpc.NewLightningClient(rawConn)
assertChannelBalance(ctxa, t.t, lightningClient, accountBalance, 0)
}
func assertChannelBalance(ctx context.Context, t *testing.T,
client lnrpc.LightningClient, localBalance, remoteBalance uint64) {

View file

@ -308,8 +308,9 @@ func testRequestLoggerDisable(ctx context.Context, net *NetworkHarness,
"--autopilot.disable",
)
require.NoError(t.t, err)
reqLogOnNode := node
defer func() {
_ = net.ShutdownNode(node)
_ = net.ShutdownNode(reqLogOnNode)
}()
rawConn, err = connectLitRPC(

View file

@ -11,6 +11,7 @@ import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
@ -692,6 +693,55 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T,
})
}
})
t.Run("lnc auth custom entity action perms", func(tt *testing.T) {
cfg := net.Alice.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
customPerms := []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
}
rawLNCConn := setUpLNCConn(
ctxt, t, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_CUSTOM,
customPerms,
)
defer rawLNCConn.Close()
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
expectedErr := "permission denied"
if endpoint.noAuth {
expectedErr = "unknown service"
}
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
// Only lnrpc (GetInfo) is allowed, as we
// only granted the "info:read" permission.
allowed := endpoint.name == "lnrpc"
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, expectedErr,
endpointDisabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
}
func uiPasswordAuthCheck(t *testing.T, cfg *LitNodeConfig, subServersDisabled,
@ -1481,3 +1531,217 @@ func bakeSuperMacaroon(t *testing.T, cfg *LitNodeConfig,
return tempFile.Name()
}
// testSuperMacaroonOnStartup tests that the super macaroon is successfully
// baked on startup if configured.
func testSuperMacaroonOnStartup(ctx context.Context, net *NetworkHarness,
t *harnessTest) {
superMacPath := filepath.Join(
net.Alice.Cfg.LitDir, "startup-super.macaroon",
)
verifyMacaroonPermissions := func(path string, expectWrite bool) {
ctxTimeout, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
rawConn, err := connectRPC(
ctxTimeout, net.Alice.Cfg.LitAddr(),
net.Alice.Cfg.LitTLSCertPath,
)
require.NoError(t.t, err)
defer rawConn.Close()
macBytes, err := os.ReadFile(path)
require.NoError(t.t, err)
ctxm := macaroonContext(ctxTimeout, macBytes)
lnrpcConn := lnrpc.NewLightningClient(rawConn)
_, err = lnrpcConn.GetInfo(ctxm, &lnrpc.GetInfoRequest{})
require.NoError(t.t, err)
_, err = lnrpcConn.NewAddress(ctxm, &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
})
if expectWrite {
require.NoError(t.t, err)
} else {
require.Error(t.t, err)
require.Contains(t.t, err.Error(), "permission denied")
}
}
verifyMacaroonSubserverPermissions := func(path string,
expectSubservers bool) {
macBytes, err := os.ReadFile(path)
require.NoError(t.t, err)
mac := &macaroon.Macaroon{}
err = mac.UnmarshalBinary(macBytes)
require.NoError(t.t, err)
rawID := mac.Id()
require.NotEmpty(t.t, rawID)
decodedID := &lnrpc.MacaroonId{}
err = proto.Unmarshal(rawID[1:], decodedID)
require.NoError(t.t, err)
hasSubserver := false
for _, op := range decodedID.Ops {
if op == nil {
continue
}
if op.Entity == "loop" || op.Entity == "pool" ||
op.Entity == "faraday" {
hasSubserver = true
}
}
if expectSubservers {
require.True(
t.t, hasSubserver,
"expected macaroon to contain "+
"subserver permissions",
)
} else {
require.False(
t.t, hasSubserver,
"expected macaroon NOT to contain "+
"subserver permissions",
)
}
}
// Ensure any old file is removed first.
_ = os.Remove(superMacPath)
// Test that starting Alice with an invalid super-macaroon-path (not
// ending with .macaroon) fails.
invalidMacPath := filepath.Join(
net.Alice.Cfg.LitDir, "invalid-path.mac",
)
err := net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-only"),
WithLitArg("super-macaroon-path", invalidMacPath),
},
)
require.Error(t.t, err)
// Drain the expected process exit error from the error channel to
// prevent it from failing the test runner at the end of the test.
select {
case <-net.lndErrorChan:
case <-time.After(defaultTimeout):
t.t.Fatalf("expected process exit error in lndErrorChan")
}
// Restart Alice with read-only super macaroon baking enabled.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-only"),
WithLitArg("super-macaroon-path", superMacPath),
},
)
require.NoError(t.t, err)
// Verify that the super macaroon was created on startup.
require.FileExists(t.t, superMacPath)
// Verify permissions: write should be blocked.
verifyMacaroonPermissions(superMacPath, false)
// Restart Alice with a read-write super macaroon, WITHOUT
// deleting the file. This will test the overwrite behavior when
// permissions differ.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-write"),
WithLitArg("super-macaroon-path", superMacPath),
},
)
require.NoError(t.t, err)
require.FileExists(t.t, superMacPath)
// Verify permissions: write should succeed.
verifyMacaroonPermissions(superMacPath, true)
// Restart Alice back with a read-only super macaroon, WITHOUT
// deleting the file. This will test the overwrite behavior when
// switching back to read-only.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-only"),
WithLitArg("super-macaroon-path", superMacPath),
},
)
require.NoError(t.t, err)
require.FileExists(t.t, superMacPath)
// Verify permissions: write should be blocked again.
verifyMacaroonPermissions(superMacPath, false)
// Restart Alice with a sub-server disabled to bake a super macaroon
// with a subset of permissions.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-write"),
WithLitArg("super-macaroon-path", superMacPath),
WithLitArg("loop-mode", "disable"),
WithLitArg("pool-mode", "disable"),
WithLitArg("faraday-mode", "disable"),
},
)
require.NoError(t.t, err)
require.FileExists(t.t, superMacPath)
verifyMacaroonSubserverPermissions(superMacPath, false)
// Restart Alice with the sub-servers re-enabled. This will add
// permissions and trigger macaroon regeneration on startup.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "read-write"),
WithLitArg("super-macaroon-path", superMacPath),
},
)
require.NoError(t.t, err)
require.FileExists(t.t, superMacPath)
verifyMacaroonSubserverPermissions(superMacPath, true)
// Verify permissions: write should succeed.
verifyMacaroonPermissions(superMacPath, true)
// Clean up the super macaroon file.
_ = os.Remove(superMacPath)
// Restart Alice with super macaroon baking disabled and verify that
// no super macaroon is created.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithLitArg("bake-super-macaroon", "none"),
WithLitArg("super-macaroon-path", superMacPath),
},
)
require.NoError(t.t, err)
_, err = os.Stat(superMacPath)
require.True(t.t, os.IsNotExist(err))
// Clean up after ourselves.
err = net.RestartNode(
net.Alice, nil, []LitArgOption{
WithoutLitArg("bake-super-macaroon"),
WithoutLitArg("super-macaroon-path"),
},
)
require.NoError(t.t, err)
net.ConnectNodes(t.t, net.Alice, net.Bob)
}

View file

@ -259,6 +259,55 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T,
}
})
t.Run("lnc auth custom entity action perms", func(tt *testing.T) {
cfg := net.Bob.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
customPerms := []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
}
rawLNCConn := setUpLNCConn(
ctxt, tt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_CUSTOM,
customPerms,
)
defer rawLNCConn.Close()
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
expectedErr := "permission denied"
if endpoint.noAuth {
expectedErr = "unknown service"
}
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
// Only lnrpc (GetInfo) is allowed, as we
// only granted the "info:read" permission.
allowed := endpoint.name == "lnrpc"
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, expectedErr,
endpointDisabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
t.Run("gRPC super macaroon account system test", func(tt *testing.T) {
cfg := net.Bob.Cfg

View file

@ -899,31 +899,56 @@ func (hn *HarnessNode) WaitUntilStarted(conn grpc.ClientConnInterface,
return err
}
// LiT itself only reports Running once it has finished baking
// and writing its default macaroons to disk, so waiting for
// this closes the race between that and callers that read
// LitMacPath straight off disk right after we return.
litStatus, ok := states.SubServers[subservers.LIT]
if !ok || !litStatus.Running {
return fmt.Errorf("LiT has not yet started")
}
if faradayMode != terminal.ModeDisable {
faraday, ok := states.SubServers[subservers.FARADAY]
if !ok || !faraday.Running {
return fmt.Errorf("faraday has not yet started")
if !ok {
return fmt.Errorf("faraday status not found")
}
if faraday.Error != "" {
return fmt.Errorf("faraday failed to "+
"start: %s", faraday.Error)
}
}
if loopMode != terminal.ModeDisable {
loop, ok := states.SubServers[subservers.LOOP]
if !ok || !loop.Running {
return fmt.Errorf("loop has not yet started")
if !ok {
return fmt.Errorf("loop status not found")
}
if loop.Error != "" {
return fmt.Errorf("loop failed to "+
"start: %s", loop.Error)
}
}
if poolMode != terminal.ModeDisable {
pool, ok := states.SubServers[subservers.POOL]
if !ok || !pool.Running {
return fmt.Errorf("pool has not yet started")
if !ok {
return fmt.Errorf("pool status not found")
}
if pool.Error != "" {
return fmt.Errorf("pool failed to "+
"start: %s", pool.Error)
}
}
if tapMode != terminal.ModeDisable {
tap, ok := states.SubServers[subservers.TAP]
if !ok || !tap.Running {
return fmt.Errorf("tap has not yet started")
if !ok {
return fmt.Errorf("tap status not found")
}
if tap.Error != "" {
return fmt.Errorf("tap failed to "+
"start: %s", tap.Error)
}
}

View file

@ -31,4 +31,8 @@ var allTestCases = []*testCase{
name: "kvdb to sql migration",
test: testKvdbSQLMigration,
},
{
name: "terminal super macaroon on startup",
test: testSuperMacaroonOnStartup,
},
}

View file

@ -1,4 +1,4 @@
FROM golang:1.25.10-bookworm@sha256:154bd7001b6eb339e88c964442c0ad6ed5e53f09844cc818a41ce4ecb3ce3b43
FROM golang:1.25.11-bookworm@sha256:b96f24a8d7d010ea0acb9c3ba99064740f02b6b984612b28bd3c9c5ab9453e38
RUN apt-get update && apt-get install -y \
git \

View file

@ -195,4 +195,29 @@ func RegisterAccountsJSONCallbacks(registry map[string]func(ctx context.Context,
}
callback(string(respBytes), nil)
}
registry["litrpc.Accounts.AccountPayments"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &AccountPaymentsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewAccountsClient(conn)
resp, err := client.AccountPayments(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}

View file

@ -1,21 +1,22 @@
module github.com/lightninglabs/lightning-terminal/litrpc
go 1.25.10
go 1.25.11
require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
github.com/lightninglabs/faraday/frdrpc v1.0.1
github.com/lightninglabs/loop/looprpc v1.0.13
github.com/lightninglabs/loop/looprpc v1.0.14
github.com/lightninglabs/pool/poolrpc v1.0.1
github.com/lightninglabs/taproot-assets/taprpc v1.1.0
github.com/lightningnetwork/lnd v0.21.0-beta
google.golang.org/grpc v1.79.3
github.com/lightningnetwork/lnd v0.21.1-beta
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
)
require (
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
@ -107,7 +108,7 @@ require (
github.com/lightningnetwork/lnd/sqldb v1.0.13 // indirect
github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect
github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect
github.com/lightningnetwork/lnd/tor v1.1.6 // indirect
github.com/lightningnetwork/lnd/tor v1.1.7 // indirect
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
@ -122,7 +123,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/opencontainers/runc v1.3.6 // indirect
github.com/ory/dockertest/v3 v3.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
@ -152,32 +153,32 @@ require (
go.etcd.io/etcd/raft/v3 v3.5.12 // indirect
go.etcd.io/etcd/server/v3 v3.5.12 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.23.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.39.0 // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/macaroon-bakery.v2 v2.3.0 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect

View file

@ -36,7 +36,6 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@ -173,13 +172,10 @@ cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63
cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
@ -604,8 +600,8 @@ git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3p
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
@ -721,8 +717,6 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
@ -787,8 +781,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
@ -1098,8 +1090,8 @@ github.com/lightninglabs/faraday/frdrpc v1.0.1 h1:3YlP9UwT0bmT468oAdn4dxwsaJBI4Q
github.com/lightninglabs/faraday/frdrpc v1.0.1/go.mod h1:ot1R/RGzk61d3qCrZPL36jI5ziGmKbvvE7UQKsJKuvk=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/loop/looprpc v1.0.13 h1:bOWDp+XnG28wP9Q8ZXvhhVuAQ7yYhuvPIfJk68GVLWk=
github.com/lightninglabs/loop/looprpc v1.0.13/go.mod h1:+m2HQ5gfMpoq449gyTsub/e3dKrOzAR6iWY11NV610M=
github.com/lightninglabs/loop/looprpc v1.0.14 h1:9gaAVGplNZMyalLV8qxILXDD9F8QSgdtOgrtOFw60V4=
github.com/lightninglabs/loop/looprpc v1.0.14/go.mod h1:vUYOyRjWWKSnCrFyugiXuS90eC/FajYoTY3OE18cRNo=
github.com/lightninglabs/loop/swapserverrpc v1.0.14 h1:0+UrC2oNFsWYqGZjmU+Fkcn8iXsea89VZdfmBXSyPPg=
github.com/lightninglabs/loop/swapserverrpc v1.0.14/go.mod h1:HDRyzFOZeX0e1P9f9RSFE7FzE5u6Eta0hPqx5W7Wp24=
github.com/lightninglabs/neutrino v0.17.1 h1:lNhgq7ix/N81R6oATroP/kHMzH1qzVVF2dEGcTlN2t4=
@ -1116,8 +1108,8 @@ github.com/lightninglabs/taproot-assets/taprpc v1.1.0 h1:Oum7ddGygrEaT+NHqpaQI8U
github.com/lightninglabs/taproot-assets/taprpc v1.1.0/go.mod h1:X7XP753o8xCgjVI2mRu1Tvpyk3k4uybGDMFhpF6IRxI=
github.com/lightningnetwork/lightning-onion v1.3.0 h1:FqILgHjD6euc/Muo1VOzZ4+XDPuFnw6EYROBq0rR/5c=
github.com/lightningnetwork/lightning-onion v1.3.0/go.mod h1:nP85zMHG7c0si/eHBbSQpuDCtnIXfSvFrK3tW6YWzmU=
github.com/lightningnetwork/lnd v0.21.0-beta h1:bDP5UH15E7DVGTztsmBPQLqgyilq5EXDrglvQFmRc3U=
github.com/lightningnetwork/lnd v0.21.0-beta/go.mod h1:HcKq9DyxbVEZXuR28TIyGbIIgAjANCxI+N6dqOnRBAA=
github.com/lightningnetwork/lnd v0.21.1-beta h1:XYr8CRY3EB5bQ8I43/6QbOZP1dRi/XI6dwzTHlS2jxQ=
github.com/lightningnetwork/lnd v0.21.1-beta/go.mod h1:iqqf8Cyq0F7ssUftb17+Y644OWVg2OMOpvWSyzympXs=
github.com/lightningnetwork/lnd/actor v0.0.6 h1:Ge8N2wivARG+27qJBwTlB0vwsypStZYZy8vk4Zl38sU=
github.com/lightningnetwork/lnd/actor v0.0.6/go.mod h1:YAsoniSbY/cAM9HTVNfZLvt7RI6swDxy6wzPspTcMZg=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
@ -1136,8 +1128,8 @@ github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6
github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0=
github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc=
github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE=
github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI=
github.com/lightningnetwork/lnd/tor v1.1.7 h1:BfY1KQGz3LVaeaAGI4XYGYCwDQR1ADFD1P/uXsS2dQc=
github.com/lightningnetwork/lnd/tor v1.1.7/go.mod h1:DANQ6QCQBiR+CqrM+mKudgkMy6CR/RS4ALDbjb2W4FU=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
@ -1202,8 +1194,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
@ -1350,14 +1342,14 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0 h1:DeFD0VgTZ+Cj6hxravYYZE2W4GlneVH81iAOPjZkzk8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.20.0/go.mod h1:GijYcYmNpX1KazD5JmWGsi4P7dDTTTnfv1UbGn84MnU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0 h1:gvmNvqrPYovvyRmCSygkUDyL8lC5Tl845MLEwqpxhEU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.20.0/go.mod h1:vNUq47TGFioo+ffTSnKNdob241vePmtNZnAODKapKd0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
@ -1371,8 +1363,8 @@ go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLh
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -1416,8 +1408,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1477,8 +1469,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1549,8 +1541,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1580,8 +1572,6 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1598,8 +1588,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1700,8 +1690,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1713,8 +1703,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1732,8 +1722,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1813,8 +1803,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1829,8 +1819,8 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0=
gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
@ -2034,10 +2024,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -2079,8 +2069,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -7,6 +7,7 @@
package litrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
@ -960,11 +961,162 @@ func (*AccountIdentifier_Id) isAccountIdentifier_Identifier() {}
func (*AccountIdentifier_Label) isAccountIdentifier_Identifier() {}
type AccountPaymentsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The identifier of the account to query payments for.
Account *AccountIdentifier `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"`
// The maximum number of payments to return. If set to 0, it will default
// to 20. Capped at 50.
MaxPayments uint64 `protobuf:"varint,2,opt,name=max_payments,json=maxPayments,proto3" json:"max_payments,omitempty"`
// The row offset into the list of payments that will be used as the start of
// the query. The payments are returned in ascending lexicographical order of
// their payment hash.
IndexOffset uint64 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"`
// If set, the total number of payments matching the query will be returned
// in the response.
CountTotalPayments bool `protobuf:"varint,4,opt,name=count_total_payments,json=countTotalPayments,proto3" json:"count_total_payments,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AccountPaymentsRequest) Reset() {
*x = AccountPaymentsRequest{}
mi := &file_lit_accounts_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AccountPaymentsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AccountPaymentsRequest) ProtoMessage() {}
func (x *AccountPaymentsRequest) ProtoReflect() protoreflect.Message {
mi := &file_lit_accounts_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AccountPaymentsRequest.ProtoReflect.Descriptor instead.
func (*AccountPaymentsRequest) Descriptor() ([]byte, []int) {
return file_lit_accounts_proto_rawDescGZIP(), []int{16}
}
func (x *AccountPaymentsRequest) GetAccount() *AccountIdentifier {
if x != nil {
return x.Account
}
return nil
}
func (x *AccountPaymentsRequest) GetMaxPayments() uint64 {
if x != nil {
return x.MaxPayments
}
return 0
}
func (x *AccountPaymentsRequest) GetIndexOffset() uint64 {
if x != nil {
return x.IndexOffset
}
return 0
}
func (x *AccountPaymentsRequest) GetCountTotalPayments() bool {
if x != nil {
return x.CountTotalPayments
}
return false
}
type AccountPaymentsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The detailed payments associated with the account, sorted in ascending
// lexicographical order of their payment hash.
Payments []*lnrpc.Payment `protobuf:"bytes,1,rep,name=payments,proto3" json:"payments,omitempty"`
// The row offset of the first payment returned.
FirstIndexOffset uint64 `protobuf:"varint,2,opt,name=first_index_offset,json=firstIndexOffset,proto3" json:"first_index_offset,omitempty"`
// The row offset of the last payment returned. This can be used as the
// index_offset in a subsequent query to paginate forwards.
LastIndexOffset uint64 `protobuf:"varint,3,opt,name=last_index_offset,json=lastIndexOffset,proto3" json:"last_index_offset,omitempty"`
// The total number of payments matching the query (only set if
// count_total_payments was true in the request).
TotalNumPayments uint64 `protobuf:"varint,4,opt,name=total_num_payments,json=totalNumPayments,proto3" json:"total_num_payments,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AccountPaymentsResponse) Reset() {
*x = AccountPaymentsResponse{}
mi := &file_lit_accounts_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AccountPaymentsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AccountPaymentsResponse) ProtoMessage() {}
func (x *AccountPaymentsResponse) ProtoReflect() protoreflect.Message {
mi := &file_lit_accounts_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AccountPaymentsResponse.ProtoReflect.Descriptor instead.
func (*AccountPaymentsResponse) Descriptor() ([]byte, []int) {
return file_lit_accounts_proto_rawDescGZIP(), []int{17}
}
func (x *AccountPaymentsResponse) GetPayments() []*lnrpc.Payment {
if x != nil {
return x.Payments
}
return nil
}
func (x *AccountPaymentsResponse) GetFirstIndexOffset() uint64 {
if x != nil {
return x.FirstIndexOffset
}
return 0
}
func (x *AccountPaymentsResponse) GetLastIndexOffset() uint64 {
if x != nil {
return x.LastIndexOffset
}
return 0
}
func (x *AccountPaymentsResponse) GetTotalNumPayments() uint64 {
if x != nil {
return x.TotalNumPayments
}
return 0
}
var File_lit_accounts_proto protoreflect.FileDescriptor
const file_lit_accounts_proto_rawDesc = "" +
"\n" +
"\x12lit-accounts.proto\x12\x06litrpc\"~\n" +
"\x12lit-accounts.proto\x12\x06litrpc\x1a\x0fproto/lnd.proto\"~\n" +
"\x14CreateAccountRequest\x12'\n" +
"\x0faccount_balance\x18\x01 \x01(\x04R\x0eaccountBalance\x12'\n" +
"\x0fexpiration_date\x18\x02 \x01(\x03R\x0eexpirationDate\x12\x14\n" +
@ -1019,7 +1171,17 @@ const file_lit_accounts_proto_rawDesc = "" +
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x12\x16\n" +
"\x05label\x18\x02 \x01(\tH\x00R\x05labelB\f\n" +
"\n" +
"identifier2\x86\x04\n" +
"identifier\"\xc5\x01\n" +
"\x16AccountPaymentsRequest\x123\n" +
"\aaccount\x18\x01 \x01(\v2\x19.litrpc.AccountIdentifierR\aaccount\x12!\n" +
"\fmax_payments\x18\x02 \x01(\x04R\vmaxPayments\x12!\n" +
"\findex_offset\x18\x03 \x01(\x04R\vindexOffset\x120\n" +
"\x14count_total_payments\x18\x04 \x01(\bR\x12countTotalPayments\"\xcd\x01\n" +
"\x17AccountPaymentsResponse\x12*\n" +
"\bpayments\x18\x01 \x03(\v2\x0e.lnrpc.PaymentR\bpayments\x12,\n" +
"\x12first_index_offset\x18\x02 \x01(\x04R\x10firstIndexOffset\x12*\n" +
"\x11last_index_offset\x18\x03 \x01(\x04R\x0flastIndexOffset\x12,\n" +
"\x12total_num_payments\x18\x04 \x01(\x04R\x10totalNumPayments2\xda\x04\n" +
"\bAccounts\x12L\n" +
"\rCreateAccount\x12\x1c.litrpc.CreateAccountRequest\x1a\x1d.litrpc.CreateAccountResponse\x12>\n" +
"\rUpdateAccount\x12\x1c.litrpc.UpdateAccountRequest\x1a\x0f.litrpc.Account\x12L\n" +
@ -1027,7 +1189,8 @@ const file_lit_accounts_proto_rawDesc = "" +
"\fDebitAccount\x12\x1b.litrpc.DebitAccountRequest\x1a\x1c.litrpc.DebitAccountResponse\x12I\n" +
"\fListAccounts\x12\x1b.litrpc.ListAccountsRequest\x1a\x1c.litrpc.ListAccountsResponse\x12:\n" +
"\vAccountInfo\x12\x1a.litrpc.AccountInfoRequest\x1a\x0f.litrpc.Account\x12L\n" +
"\rRemoveAccount\x12\x1c.litrpc.RemoveAccountRequest\x1a\x1d.litrpc.RemoveAccountResponseB4Z2github.com/lightninglabs/lightning-terminal/litrpcb\x06proto3"
"\rRemoveAccount\x12\x1c.litrpc.RemoveAccountRequest\x1a\x1d.litrpc.RemoveAccountResponse\x12R\n" +
"\x0fAccountPayments\x12\x1e.litrpc.AccountPaymentsRequest\x1a\x1f.litrpc.AccountPaymentsResponseB4Z2github.com/lightninglabs/lightning-terminal/litrpcb\x06proto3"
var (
file_lit_accounts_proto_rawDescOnce sync.Once
@ -1041,24 +1204,27 @@ func file_lit_accounts_proto_rawDescGZIP() []byte {
return file_lit_accounts_proto_rawDescData
}
var file_lit_accounts_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_lit_accounts_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_lit_accounts_proto_goTypes = []any{
(*CreateAccountRequest)(nil), // 0: litrpc.CreateAccountRequest
(*CreateAccountResponse)(nil), // 1: litrpc.CreateAccountResponse
(*Account)(nil), // 2: litrpc.Account
(*AccountInvoice)(nil), // 3: litrpc.AccountInvoice
(*AccountPayment)(nil), // 4: litrpc.AccountPayment
(*UpdateAccountRequest)(nil), // 5: litrpc.UpdateAccountRequest
(*CreditAccountRequest)(nil), // 6: litrpc.CreditAccountRequest
(*CreditAccountResponse)(nil), // 7: litrpc.CreditAccountResponse
(*DebitAccountRequest)(nil), // 8: litrpc.DebitAccountRequest
(*DebitAccountResponse)(nil), // 9: litrpc.DebitAccountResponse
(*ListAccountsRequest)(nil), // 10: litrpc.ListAccountsRequest
(*ListAccountsResponse)(nil), // 11: litrpc.ListAccountsResponse
(*AccountInfoRequest)(nil), // 12: litrpc.AccountInfoRequest
(*RemoveAccountRequest)(nil), // 13: litrpc.RemoveAccountRequest
(*RemoveAccountResponse)(nil), // 14: litrpc.RemoveAccountResponse
(*AccountIdentifier)(nil), // 15: litrpc.AccountIdentifier
(*CreateAccountRequest)(nil), // 0: litrpc.CreateAccountRequest
(*CreateAccountResponse)(nil), // 1: litrpc.CreateAccountResponse
(*Account)(nil), // 2: litrpc.Account
(*AccountInvoice)(nil), // 3: litrpc.AccountInvoice
(*AccountPayment)(nil), // 4: litrpc.AccountPayment
(*UpdateAccountRequest)(nil), // 5: litrpc.UpdateAccountRequest
(*CreditAccountRequest)(nil), // 6: litrpc.CreditAccountRequest
(*CreditAccountResponse)(nil), // 7: litrpc.CreditAccountResponse
(*DebitAccountRequest)(nil), // 8: litrpc.DebitAccountRequest
(*DebitAccountResponse)(nil), // 9: litrpc.DebitAccountResponse
(*ListAccountsRequest)(nil), // 10: litrpc.ListAccountsRequest
(*ListAccountsResponse)(nil), // 11: litrpc.ListAccountsResponse
(*AccountInfoRequest)(nil), // 12: litrpc.AccountInfoRequest
(*RemoveAccountRequest)(nil), // 13: litrpc.RemoveAccountRequest
(*RemoveAccountResponse)(nil), // 14: litrpc.RemoveAccountResponse
(*AccountIdentifier)(nil), // 15: litrpc.AccountIdentifier
(*AccountPaymentsRequest)(nil), // 16: litrpc.AccountPaymentsRequest
(*AccountPaymentsResponse)(nil), // 17: litrpc.AccountPaymentsResponse
(*lnrpc.Payment)(nil), // 18: lnrpc.Payment
}
var file_lit_accounts_proto_depIdxs = []int32{
2, // 0: litrpc.CreateAccountResponse.account:type_name -> litrpc.Account
@ -1069,25 +1235,29 @@ var file_lit_accounts_proto_depIdxs = []int32{
15, // 5: litrpc.DebitAccountRequest.account:type_name -> litrpc.AccountIdentifier
2, // 6: litrpc.DebitAccountResponse.account:type_name -> litrpc.Account
2, // 7: litrpc.ListAccountsResponse.accounts:type_name -> litrpc.Account
0, // 8: litrpc.Accounts.CreateAccount:input_type -> litrpc.CreateAccountRequest
5, // 9: litrpc.Accounts.UpdateAccount:input_type -> litrpc.UpdateAccountRequest
6, // 10: litrpc.Accounts.CreditAccount:input_type -> litrpc.CreditAccountRequest
8, // 11: litrpc.Accounts.DebitAccount:input_type -> litrpc.DebitAccountRequest
10, // 12: litrpc.Accounts.ListAccounts:input_type -> litrpc.ListAccountsRequest
12, // 13: litrpc.Accounts.AccountInfo:input_type -> litrpc.AccountInfoRequest
13, // 14: litrpc.Accounts.RemoveAccount:input_type -> litrpc.RemoveAccountRequest
1, // 15: litrpc.Accounts.CreateAccount:output_type -> litrpc.CreateAccountResponse
2, // 16: litrpc.Accounts.UpdateAccount:output_type -> litrpc.Account
7, // 17: litrpc.Accounts.CreditAccount:output_type -> litrpc.CreditAccountResponse
9, // 18: litrpc.Accounts.DebitAccount:output_type -> litrpc.DebitAccountResponse
11, // 19: litrpc.Accounts.ListAccounts:output_type -> litrpc.ListAccountsResponse
2, // 20: litrpc.Accounts.AccountInfo:output_type -> litrpc.Account
14, // 21: litrpc.Accounts.RemoveAccount:output_type -> litrpc.RemoveAccountResponse
15, // [15:22] is the sub-list for method output_type
8, // [8:15] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
15, // 8: litrpc.AccountPaymentsRequest.account:type_name -> litrpc.AccountIdentifier
18, // 9: litrpc.AccountPaymentsResponse.payments:type_name -> lnrpc.Payment
0, // 10: litrpc.Accounts.CreateAccount:input_type -> litrpc.CreateAccountRequest
5, // 11: litrpc.Accounts.UpdateAccount:input_type -> litrpc.UpdateAccountRequest
6, // 12: litrpc.Accounts.CreditAccount:input_type -> litrpc.CreditAccountRequest
8, // 13: litrpc.Accounts.DebitAccount:input_type -> litrpc.DebitAccountRequest
10, // 14: litrpc.Accounts.ListAccounts:input_type -> litrpc.ListAccountsRequest
12, // 15: litrpc.Accounts.AccountInfo:input_type -> litrpc.AccountInfoRequest
13, // 16: litrpc.Accounts.RemoveAccount:input_type -> litrpc.RemoveAccountRequest
16, // 17: litrpc.Accounts.AccountPayments:input_type -> litrpc.AccountPaymentsRequest
1, // 18: litrpc.Accounts.CreateAccount:output_type -> litrpc.CreateAccountResponse
2, // 19: litrpc.Accounts.UpdateAccount:output_type -> litrpc.Account
7, // 20: litrpc.Accounts.CreditAccount:output_type -> litrpc.CreditAccountResponse
9, // 21: litrpc.Accounts.DebitAccount:output_type -> litrpc.DebitAccountResponse
11, // 22: litrpc.Accounts.ListAccounts:output_type -> litrpc.ListAccountsResponse
2, // 23: litrpc.Accounts.AccountInfo:output_type -> litrpc.Account
14, // 24: litrpc.Accounts.RemoveAccount:output_type -> litrpc.RemoveAccountResponse
17, // 25: litrpc.Accounts.AccountPayments:output_type -> litrpc.AccountPaymentsResponse
18, // [18:26] is the sub-list for method output_type
10, // [10:18] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
}
func init() { file_lit_accounts_proto_init() }
@ -1105,7 +1275,7 @@ func file_lit_accounts_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_lit_accounts_proto_rawDesc), len(file_lit_accounts_proto_rawDesc)),
NumEnums: 0,
NumMessages: 16,
NumMessages: 18,
NumExtensions: 0,
NumServices: 1,
},

View file

@ -325,6 +325,42 @@ func local_request_Accounts_RemoveAccount_0(ctx context.Context, marshaler runti
}
var (
filter_Accounts_AccountPayments_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_Accounts_AccountPayments_0(ctx context.Context, marshaler runtime.Marshaler, client AccountsClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AccountPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Accounts_AccountPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.AccountPayments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Accounts_AccountPayments_0(ctx context.Context, marshaler runtime.Marshaler, server AccountsServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq AccountPaymentsRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Accounts_AccountPayments_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.AccountPayments(ctx, &protoReq)
return msg, metadata, err
}
// RegisterAccountsHandlerServer registers the http handlers for service Accounts to "mux".
// UnaryRPC :call AccountsServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -482,6 +518,31 @@ func RegisterAccountsHandlerServer(ctx context.Context, mux *runtime.ServeMux, s
})
mux.Handle("GET", pattern_Accounts_AccountPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/litrpc.Accounts/AccountPayments", runtime.WithHTTPPathPattern("/v1/accounts/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Accounts_AccountPayments_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Accounts_AccountPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -655,6 +716,28 @@ func RegisterAccountsHandlerClient(ctx context.Context, mux *runtime.ServeMux, c
})
mux.Handle("GET", pattern_Accounts_AccountPayments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/litrpc.Accounts/AccountPayments", runtime.WithHTTPPathPattern("/v1/accounts/payments"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Accounts_AccountPayments_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_Accounts_AccountPayments_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -670,6 +753,8 @@ var (
pattern_Accounts_ListAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "accounts"}, ""))
pattern_Accounts_RemoveAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "accounts", "id"}, ""))
pattern_Accounts_AccountPayments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "accounts", "payments"}, ""))
)
var (
@ -684,4 +769,6 @@ var (
forward_Accounts_ListAccounts_0 = runtime.ForwardResponseMessage
forward_Accounts_RemoveAccount_0 = runtime.ForwardResponseMessage
forward_Accounts_AccountPayments_0 = runtime.ForwardResponseMessage
)

View file

@ -2,6 +2,8 @@ syntax = "proto3";
package litrpc;
import "proto/lnd.proto";
option go_package = "github.com/lightninglabs/lightning-terminal/litrpc";
service Accounts {
@ -52,6 +54,12 @@ service Accounts {
RemoveAccount removes the given account from the account database.
*/
rpc RemoveAccount (RemoveAccountRequest) returns (RemoveAccountResponse);
/* litcli: `accounts payments`
AccountPayments returns the detailed payment history for the given account.
*/
rpc AccountPayments (AccountPaymentsRequest)
returns (AccountPaymentsResponse);
}
message CreateAccountRequest {
@ -244,4 +252,55 @@ message AccountIdentifier {
// The label of the account.
string label = 2;
}
}
message AccountPaymentsRequest {
/*
The identifier of the account to query payments for.
*/
AccountIdentifier account = 1;
/*
The maximum number of payments to return. If set to 0, it will default
to 20. Capped at 50.
*/
uint64 max_payments = 2;
/*
The row offset into the list of payments that will be used as the start of
the query. The payments are returned in ascending lexicographical order of
their payment hash.
*/
uint64 index_offset = 3;
/*
If set, the total number of payments matching the query will be returned
in the response.
*/
bool count_total_payments = 4;
}
message AccountPaymentsResponse {
/*
The detailed payments associated with the account, sorted in ascending
lexicographical order of their payment hash.
*/
repeated lnrpc.Payment payments = 1;
/*
The row offset of the first payment returned.
*/
uint64 first_index_offset = 2;
/*
The row offset of the last payment returned. This can be used as the
index_offset in a subsequent query to paginate forwards.
*/
uint64 last_index_offset = 3;
/*
The total number of payments matching the query (only set if
count_total_payments was true in the request).
*/
uint64 total_num_payments = 4;
}

View file

@ -151,6 +151,68 @@
]
}
},
"/v1/accounts/payments": {
"get": {
"summary": "litcli: `accounts payments`\nAccountPayments returns the detailed payment history for the given account.",
"operationId": "Accounts_AccountPayments",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/litrpcAccountPaymentsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "account.id",
"description": "The ID of the account.",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "account.label",
"description": "The label of the account.",
"in": "query",
"required": false,
"type": "string"
},
{
"name": "max_payments",
"description": "The maximum number of payments to return. If set to 0, it will default\nto 20. Capped at 50.",
"in": "query",
"required": false,
"type": "string",
"format": "uint64"
},
{
"name": "index_offset",
"description": "The row offset into the list of payments that will be used as the start of\nthe query. The payments are returned in ascending lexicographical order of\ntheir payment hash.",
"in": "query",
"required": false,
"type": "string",
"format": "uint64"
},
{
"name": "count_total_payments",
"description": "If set, the total number of payments matching the query will be returned\nin the response.",
"in": "query",
"required": false,
"type": "boolean"
}
],
"tags": [
"Accounts"
]
}
},
"/v1/accounts/{id}": {
"delete": {
"summary": "litcli: `accounts remove`\nRemoveAccount removes the given account from the account database.",
@ -295,6 +357,63 @@
}
}
},
"FailureFailureCode": {
"type": "string",
"enum": [
"RESERVED",
"INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS",
"INCORRECT_PAYMENT_AMOUNT",
"FINAL_INCORRECT_CLTV_EXPIRY",
"FINAL_INCORRECT_HTLC_AMOUNT",
"FINAL_EXPIRY_TOO_SOON",
"INVALID_REALM",
"EXPIRY_TOO_SOON",
"INVALID_ONION_VERSION",
"INVALID_ONION_HMAC",
"INVALID_ONION_KEY",
"AMOUNT_BELOW_MINIMUM",
"FEE_INSUFFICIENT",
"INCORRECT_CLTV_EXPIRY",
"CHANNEL_DISABLED",
"TEMPORARY_CHANNEL_FAILURE",
"REQUIRED_NODE_FEATURE_MISSING",
"REQUIRED_CHANNEL_FEATURE_MISSING",
"UNKNOWN_NEXT_PEER",
"TEMPORARY_NODE_FAILURE",
"PERMANENT_NODE_FAILURE",
"PERMANENT_CHANNEL_FAILURE",
"EXPIRY_TOO_FAR",
"MPP_TIMEOUT",
"INVALID_ONION_PAYLOAD",
"INVALID_ONION_BLINDING",
"INTERNAL_FAILURE",
"UNKNOWN_FAILURE",
"UNREADABLE_FAILURE"
],
"default": "RESERVED",
"description": " - RESERVED: The numbers assigned in this enumeration match the failure codes as\ndefined in BOLT #4. Because protobuf 3 requires enums to start with 0,\na RESERVED value is added.\n - INTERNAL_FAILURE: An internal error occurred.\n - UNKNOWN_FAILURE: The error source is known, but the failure itself couldn't be decoded.\n - UNREADABLE_FAILURE: An unreadable failure result is returned if the received failure message\ncannot be decrypted. In that case the error source is unknown."
},
"HTLCAttemptHTLCStatus": {
"type": "string",
"enum": [
"IN_FLIGHT",
"SUCCEEDED",
"FAILED"
],
"default": "IN_FLIGHT"
},
"PaymentPaymentStatus": {
"type": "string",
"enum": [
"UNKNOWN",
"IN_FLIGHT",
"SUCCEEDED",
"FAILED",
"INITIATED"
],
"default": "UNKNOWN",
"description": " - UNKNOWN: Deprecated. This status will never be returned.\n - IN_FLIGHT: Payment has inflight HTLCs.\n - SUCCEEDED: Payment is settled.\n - FAILED: Payment is failed.\n - INITIATED: Payment is created and has not attempted any HTLCs."
},
"litrpcAccount": {
"type": "object",
"properties": {
@ -386,6 +505,34 @@
}
}
},
"litrpcAccountPaymentsResponse": {
"type": "object",
"properties": {
"payments": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/lnrpcPayment"
},
"description": "The detailed payments associated with the account, sorted in ascending\nlexicographical order of their payment hash."
},
"first_index_offset": {
"type": "string",
"format": "uint64",
"description": "The row offset of the first payment returned."
},
"last_index_offset": {
"type": "string",
"format": "uint64",
"description": "The row offset of the last payment returned. This can be used as the\nindex_offset in a subsequent query to paginate forwards."
},
"total_num_payments": {
"type": "string",
"format": "uint64",
"description": "The total number of payments matching the query (only set if\ncount_total_payments was true in the request)."
}
}
},
"litrpcCreateAccountRequest": {
"type": "object",
"properties": {
@ -453,6 +600,409 @@
"litrpcRemoveAccountResponse": {
"type": "object"
},
"lnrpcAMPRecord": {
"type": "object",
"properties": {
"root_share": {
"type": "string",
"format": "byte"
},
"set_id": {
"type": "string",
"format": "byte"
},
"child_index": {
"type": "integer",
"format": "int64"
}
}
},
"lnrpcChannelUpdate": {
"type": "object",
"properties": {
"signature": {
"type": "string",
"format": "byte",
"description": "The signature that validates the announced data and proves the ownership\nof node id."
},
"chain_hash": {
"type": "string",
"format": "byte",
"description": "The target chain that this channel was opened within. This value\nshould be the genesis hash of the target chain. Along with the short\nchannel ID, this uniquely identifies the channel globally in a\nblockchain."
},
"chan_id": {
"type": "string",
"format": "uint64",
"description": "The unique description of the funding transaction."
},
"timestamp": {
"type": "integer",
"format": "int64",
"description": "A timestamp that allows ordering in the case of multiple announcements.\nWe should ignore the message if timestamp is not greater than the\nlast-received."
},
"message_flags": {
"type": "integer",
"format": "int64",
"description": "The bitfield that describes whether optional fields are present in this\nupdate. Currently, the least-significant bit must be set to 1 if the\noptional field MaxHtlc is present."
},
"channel_flags": {
"type": "integer",
"format": "int64",
"description": "The bitfield that describes additional meta-data concerning how the\nupdate is to be interpreted. Currently, the least-significant bit must be\nset to 0 if the creating node corresponds to the first node in the\npreviously sent channel announcement and 1 otherwise. If the second bit\nis set, then the channel is set to be disabled."
},
"time_lock_delta": {
"type": "integer",
"format": "int64",
"description": "The minimum number of blocks this node requires to be added to the expiry\nof HTLCs. This is a security parameter determined by the node operator.\nThis value represents the required gap between the time locks of the\nincoming and outgoing HTLC's set to this node."
},
"htlc_minimum_msat": {
"type": "string",
"format": "uint64",
"description": "The minimum HTLC value which will be accepted."
},
"base_fee": {
"type": "integer",
"format": "int64",
"description": "The base fee that must be used for incoming HTLC's to this particular\nchannel. This value will be tacked onto the required for a payment\nindependent of the size of the payment."
},
"fee_rate": {
"type": "integer",
"format": "int64",
"description": "The fee rate that will be charged per millionth of a satoshi."
},
"htlc_maximum_msat": {
"type": "string",
"format": "uint64",
"description": "The maximum HTLC value which will be accepted."
},
"extra_opaque_data": {
"type": "string",
"format": "byte",
"description": "The set of data that was appended to this message, some of which we may\nnot actually know how to iterate or parse. By holding onto this data, we\nensure that we're able to properly validate the set of signatures that\ncover these new fields, and ensure we're able to make upgrades to the\nnetwork in a forwards compatible manner."
}
}
},
"lnrpcFailure": {
"type": "object",
"properties": {
"code": {
"$ref": "#/definitions/FailureFailureCode",
"title": "Failure code as defined in the Lightning spec"
},
"channel_update": {
"$ref": "#/definitions/lnrpcChannelUpdate",
"description": "An optional channel update message."
},
"htlc_msat": {
"type": "string",
"format": "uint64",
"description": "A failure type-dependent htlc value."
},
"onion_sha_256": {
"type": "string",
"format": "byte",
"description": "The sha256 sum of the onion payload."
},
"cltv_expiry": {
"type": "integer",
"format": "int64",
"description": "A failure type-dependent cltv expiry value."
},
"flags": {
"type": "integer",
"format": "int64",
"description": "A failure type-dependent flags value."
},
"failure_source_index": {
"type": "integer",
"format": "int64",
"description": "The position in the path of the intermediate or final node that generated\nthe failure message. Position zero is the sender node."
},
"height": {
"type": "integer",
"format": "int64",
"description": "A failure type-dependent block height."
}
}
},
"lnrpcHTLCAttempt": {
"type": "object",
"properties": {
"attempt_id": {
"type": "string",
"format": "uint64",
"description": "The unique ID that is used for this attempt."
},
"status": {
"$ref": "#/definitions/HTLCAttemptHTLCStatus",
"description": "The status of the HTLC."
},
"route": {
"$ref": "#/definitions/lnrpcRoute",
"description": "The route taken by this HTLC."
},
"attempt_time_ns": {
"type": "string",
"format": "int64",
"description": "The time in UNIX nanoseconds at which this HTLC was sent."
},
"resolve_time_ns": {
"type": "string",
"format": "int64",
"description": "The time in UNIX nanoseconds at which this HTLC was settled or failed.\nThis value will not be set if the HTLC is still IN_FLIGHT."
},
"failure": {
"$ref": "#/definitions/lnrpcFailure",
"description": "Detailed htlc failure info."
},
"preimage": {
"type": "string",
"format": "byte",
"description": "The preimage that was used to settle the HTLC."
}
}
},
"lnrpcHop": {
"type": "object",
"properties": {
"chan_id": {
"type": "string",
"format": "uint64",
"description": "The unique channel ID for the channel. The first 3 bytes are the block\nheight, the next 3 the index within the block, and the last 2 bytes are the\noutput index for the channel."
},
"chan_capacity": {
"type": "string",
"format": "int64"
},
"amt_to_forward": {
"type": "string",
"format": "int64"
},
"fee": {
"type": "string",
"format": "int64"
},
"expiry": {
"type": "integer",
"format": "int64"
},
"amt_to_forward_msat": {
"type": "string",
"format": "int64"
},
"fee_msat": {
"type": "string",
"format": "int64"
},
"pub_key": {
"type": "string",
"description": "An optional public key of the hop. If the public key is given, the payment\ncan be executed without relying on a copy of the channel graph."
},
"tlv_payload": {
"type": "boolean",
"description": "If set to true, then this hop will be encoded using the new variable length\nTLV format. Note that if any custom tlv_records below are specified, then\nthis field MUST be set to true for them to be encoded properly."
},
"mpp_record": {
"$ref": "#/definitions/lnrpcMPPRecord",
"description": "An optional TLV record that signals the use of an MPP payment. If present,\nthe receiver will enforce that the same mpp_record is included in the final\nhop payload of all non-zero payments in the HTLC set. If empty, a regular\nsingle-shot payment is or was attempted."
},
"amp_record": {
"$ref": "#/definitions/lnrpcAMPRecord",
"description": "An optional TLV record that signals the use of an AMP payment. If present,\nthe receiver will treat all received payments including the same\n(payment_addr, set_id) pair as being part of one logical payment. The\npayment will be settled by XORing the root_share's together and deriving the\nchild hashes and preimages according to BOLT XX. Must be used in conjunction\nwith mpp_record."
},
"custom_records": {
"type": "object",
"additionalProperties": {
"type": "string",
"format": "byte"
},
"description": "An optional set of key-value TLV records. This is useful within the context\nof the SendToRoute call as it allows callers to specify arbitrary K-V pairs\nto drop off at each hop within the onion."
},
"metadata": {
"type": "string",
"format": "byte",
"description": "The payment metadata to send along with the payment to the payee."
},
"blinding_point": {
"type": "string",
"format": "byte",
"description": "Blinding point is an optional blinding point included for introduction\nnodes in blinded paths. This field is mandatory for hops that represents\nthe introduction point in a blinded path."
},
"encrypted_data": {
"type": "string",
"format": "byte",
"description": "Encrypted data is a receiver-produced blob of data that provides hops\nin a blinded route with forwarding data. As this data is encrypted by\nthe recipient, we will not be able to parse it - it is essentially an\narbitrary blob of data from our node's perspective. This field is\nmandatory for all hops in a blinded path, including the introduction\nnode."
},
"total_amt_msat": {
"type": "string",
"format": "uint64",
"description": "The total amount that is sent to the recipient (possibly across multiple\nHTLCs), as specified by the sender when making a payment to a blinded path.\nThis value is only set in the final hop payload of a blinded payment. This\nvalue is analogous to the MPPRecord that is used for regular (non-blinded)\nMPP payments."
}
}
},
"lnrpcMPPRecord": {
"type": "object",
"properties": {
"payment_addr": {
"type": "string",
"format": "byte",
"description": "A unique, random identifier used to authenticate the sender as the intended\npayer of a multi-path payment. The payment_addr must be the same for all\nsubpayments, and match the payment_addr provided in the receiver's invoice.\nThe same payment_addr must be used on all subpayments. This is also called\npayment secret in specifications (e.g. BOLT 11)."
},
"total_amt_msat": {
"type": "string",
"format": "int64",
"description": "The total amount in milli-satoshis being sent as part of a larger multi-path\npayment. The caller is responsible for ensuring subpayments to the same node\nand payment_hash sum exactly to total_amt_msat. The same\ntotal_amt_msat must be used on all subpayments."
}
}
},
"lnrpcPayment": {
"type": "object",
"properties": {
"payment_hash": {
"type": "string",
"title": "The payment hash"
},
"value": {
"type": "string",
"format": "int64",
"description": "Deprecated, use value_sat or value_msat."
},
"creation_date": {
"type": "string",
"format": "int64",
"title": "Deprecated, use creation_time_ns"
},
"fee": {
"type": "string",
"format": "int64",
"description": "Deprecated, use fee_sat or fee_msat."
},
"payment_preimage": {
"type": "string",
"title": "The payment preimage"
},
"value_sat": {
"type": "string",
"format": "int64",
"title": "The value of the payment in satoshis"
},
"value_msat": {
"type": "string",
"format": "int64",
"title": "The value of the payment in milli-satoshis"
},
"payment_request": {
"type": "string",
"description": "The optional payment request being fulfilled."
},
"status": {
"$ref": "#/definitions/PaymentPaymentStatus",
"description": "The status of the payment."
},
"fee_sat": {
"type": "string",
"format": "int64",
"title": "The fee paid for this payment in satoshis"
},
"fee_msat": {
"type": "string",
"format": "int64",
"title": "The fee paid for this payment in milli-satoshis"
},
"creation_time_ns": {
"type": "string",
"format": "int64",
"description": "The time in UNIX nanoseconds at which the payment was created."
},
"htlcs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/lnrpcHTLCAttempt"
},
"description": "The HTLCs made in attempt to settle the payment."
},
"payment_index": {
"type": "string",
"format": "uint64",
"description": "The creation index of this payment. Each payment can be uniquely identified\nby this index, which may not strictly increment by 1 for payments made in\nolder versions of lnd."
},
"failure_reason": {
"$ref": "#/definitions/lnrpcPaymentFailureReason"
},
"first_hop_custom_records": {
"type": "object",
"additionalProperties": {
"type": "string",
"format": "byte"
},
"description": "The custom TLV records that were sent to the first hop as part of the HTLC\nwire message for this payment."
}
}
},
"lnrpcPaymentFailureReason": {
"type": "string",
"enum": [
"FAILURE_REASON_NONE",
"FAILURE_REASON_TIMEOUT",
"FAILURE_REASON_NO_ROUTE",
"FAILURE_REASON_ERROR",
"FAILURE_REASON_INCORRECT_PAYMENT_DETAILS",
"FAILURE_REASON_INSUFFICIENT_BALANCE",
"FAILURE_REASON_CANCELED"
],
"default": "FAILURE_REASON_NONE",
"description": " - FAILURE_REASON_NONE: Payment isn't failed (yet).\n - FAILURE_REASON_TIMEOUT: There are more routes to try, but the payment timeout was exceeded.\n - FAILURE_REASON_NO_ROUTE: All possible routes were tried and failed permanently. Or were no\nroutes to the destination at all.\n - FAILURE_REASON_ERROR: A non-recoverable error has occured.\n - FAILURE_REASON_INCORRECT_PAYMENT_DETAILS: Payment details incorrect (unknown hash, invalid amt or\ninvalid final cltv delta)\n - FAILURE_REASON_INSUFFICIENT_BALANCE: Insufficient local balance.\n - FAILURE_REASON_CANCELED: The payment was canceled."
},
"lnrpcRoute": {
"type": "object",
"properties": {
"total_time_lock": {
"type": "integer",
"format": "int64",
"description": "The cumulative (final) time lock across the entire route. This is the CLTV\nvalue that should be extended to the first hop in the route. All other hops\nwill decrement the time-lock as advertised, leaving enough time for all\nhops to wait for or present the payment preimage to complete the payment."
},
"total_fees": {
"type": "string",
"format": "int64",
"description": "The sum of the fees paid at each hop within the final route. In the case\nof a one-hop payment, this value will be zero as we don't need to pay a fee\nto ourselves."
},
"total_amt": {
"type": "string",
"format": "int64",
"description": "The total amount of funds required to complete a payment over this route.\nThis value includes the cumulative fees at each hop. As a result, the HTLC\nextended to the first-hop in the route will need to have at least this many\nsatoshis, otherwise the route will fail at an intermediate node due to an\ninsufficient amount of fees."
},
"hops": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/lnrpcHop"
},
"description": "Contains details concerning the specific forwarding details at each hop."
},
"total_fees_msat": {
"type": "string",
"format": "int64",
"description": "The total fees in millisatoshis."
},
"total_amt_msat": {
"type": "string",
"format": "int64",
"description": "The total amount in millisatoshis."
},
"first_hop_amount_msat": {
"type": "string",
"format": "int64",
"description": "The actual on-chain amount that was sent out to the first hop. This value is\nonly different from the total_amt_msat field if this is a custom channel\npayment and the value transported in the HTLC is different from the BTC\namount in the HTLC. If this value is zero, then this is an old payment that\ndidn't have this value yet and can be ignored."
},
"custom_channel_data": {
"type": "string",
"format": "byte",
"description": "Custom channel data that might be populated in custom channels."
}
},
"description": "A path through the channel graph which runs over one or more channels in\nsuccession. This struct carries all the information required to craft the\nSphinx onion packet, and send the payment along the first hop in the path. A\nroute is only selected as valid if all the channels have sufficient capacity to\ncarry the initial payment amount after fees are accounted for."
},
"protobufAny": {
"type": "object",
"properties": {

View file

@ -21,3 +21,5 @@ http:
- selector: litrpc.Accounts.DebitAccount
post: "/v1/accounts/debit/{account.id}"
body: "*"
- selector: litrpc.Accounts.AccountPayments
get: "/v1/accounts/payments"

View file

@ -52,6 +52,9 @@ type AccountsClient interface {
// litcli: `accounts remove`
// RemoveAccount removes the given account from the account database.
RemoveAccount(ctx context.Context, in *RemoveAccountRequest, opts ...grpc.CallOption) (*RemoveAccountResponse, error)
// litcli: `accounts payments`
// AccountPayments returns the detailed payment history for the given account.
AccountPayments(ctx context.Context, in *AccountPaymentsRequest, opts ...grpc.CallOption) (*AccountPaymentsResponse, error)
}
type accountsClient struct {
@ -125,6 +128,15 @@ func (c *accountsClient) RemoveAccount(ctx context.Context, in *RemoveAccountReq
return out, nil
}
func (c *accountsClient) AccountPayments(ctx context.Context, in *AccountPaymentsRequest, opts ...grpc.CallOption) (*AccountPaymentsResponse, error) {
out := new(AccountPaymentsResponse)
err := c.cc.Invoke(ctx, "/litrpc.Accounts/AccountPayments", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AccountsServer is the server API for Accounts service.
// All implementations must embed UnimplementedAccountsServer
// for forward compatibility
@ -163,6 +175,9 @@ type AccountsServer interface {
// litcli: `accounts remove`
// RemoveAccount removes the given account from the account database.
RemoveAccount(context.Context, *RemoveAccountRequest) (*RemoveAccountResponse, error)
// litcli: `accounts payments`
// AccountPayments returns the detailed payment history for the given account.
AccountPayments(context.Context, *AccountPaymentsRequest) (*AccountPaymentsResponse, error)
mustEmbedUnimplementedAccountsServer()
}
@ -191,6 +206,9 @@ func (UnimplementedAccountsServer) AccountInfo(context.Context, *AccountInfoRequ
func (UnimplementedAccountsServer) RemoveAccount(context.Context, *RemoveAccountRequest) (*RemoveAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveAccount not implemented")
}
func (UnimplementedAccountsServer) AccountPayments(context.Context, *AccountPaymentsRequest) (*AccountPaymentsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AccountPayments not implemented")
}
func (UnimplementedAccountsServer) mustEmbedUnimplementedAccountsServer() {}
// UnsafeAccountsServer may be embedded to opt out of forward compatibility for this service.
@ -330,6 +348,24 @@ func _Accounts_RemoveAccount_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _Accounts_AccountPayments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AccountPaymentsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountsServer).AccountPayments(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/litrpc.Accounts/AccountPayments",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountsServer).AccountPayments(ctx, req.(*AccountPaymentsRequest))
}
return interceptor(ctx, in, info, handler)
}
// Accounts_ServiceDesc is the grpc.ServiceDesc for Accounts service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -365,6 +401,10 @@ var Accounts_ServiceDesc = grpc.ServiceDesc{
MethodName: "RemoveAccount",
Handler: _Accounts_RemoveAccount_Handler,
},
{
MethodName: "AccountPayments",
Handler: _Accounts_AccountPayments_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "lit-accounts.proto",

54
lnd_ready.go Normal file
View file

@ -0,0 +1,54 @@
package terminal
import (
"context"
"fmt"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
)
// waitForLndRPCReady polls lnd's StateService until lnd's RPC interceptor has
// left the WAITING_TO_START state, or timeout elapses. Unlike every other
// lnd RPC, the StateService is exempt from both lnd's macaroon check and its
// RPC-readiness check (see lnd's rpcperms.InterceptorChain), so it can be
// queried on a fresh connection before the wallet is unlocked and before any
// macaroon exists. This lets us distinguish lnd's gRPC listener merely being
// bound (which is all readyChan/unlockChan guarantee) from lnd actually being
// able to service non-State RPCs.
func waitForLndRPCReady(ctx context.Context, stateClient lnrpc.StateClient,
timeout time.Duration) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
var lastErr error
for {
resp, err := stateClient.GetState(ctx, &lnrpc.GetStateRequest{})
switch {
case err != nil:
lastErr = err
case resp.State != lnrpc.WalletState_WAITING_TO_START:
return nil
}
select {
case <-time.After(stateServicePollInterval):
case <-timer.C:
if lastErr != nil {
return fmt.Errorf("lnd's RPC interceptor "+
"did not leave WAITING_TO_START "+
"within %v: %w", timeout, lastErr)
}
return fmt.Errorf("lnd's RPC interceptor did not "+
"leave WAITING_TO_START within %v", timeout)
case <-ctx.Done():
return ctx.Err()
}
}
}

117
lnd_ready_test.go Normal file
View file

@ -0,0 +1,117 @@
package terminal
import (
"context"
"errors"
"testing"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
// fakeStateClient is a test-only lnrpc.StateClient that replays a canned
// sequence of GetState responses/errors, repeating the last entry once the
// sequence is exhausted.
type fakeStateClient struct {
lnrpc.StateClient
responses []*lnrpc.GetStateResponse
errs []error
calls int
}
func (f *fakeStateClient) GetState(_ context.Context,
_ *lnrpc.GetStateRequest, _ ...grpc.CallOption) (
*lnrpc.GetStateResponse, error) {
idx := f.calls
if idx >= len(f.responses) {
idx = len(f.responses) - 1
}
f.calls++
return f.responses[idx], f.errs[idx]
}
// TestWaitForLndRPCReady asserts that waitForLndRPCReady correctly polls
// lnd's StateService until it reports a state other than WAITING_TO_START,
// and that it times out with an error if that never happens.
func TestWaitForLndRPCReady(t *testing.T) {
t.Parallel()
activeResp := &lnrpc.GetStateResponse{
State: lnrpc.WalletState_RPC_ACTIVE,
}
waitingResp := &lnrpc.GetStateResponse{
State: lnrpc.WalletState_WAITING_TO_START,
}
testCases := []struct {
name string
client *fakeStateClient
timeout time.Duration
expectError bool
}{
{
name: "ready immediately",
client: &fakeStateClient{
responses: []*lnrpc.GetStateResponse{
activeResp,
},
errs: []error{nil},
},
timeout: time.Second,
},
{
name: "ready after N polls",
client: &fakeStateClient{
responses: []*lnrpc.GetStateResponse{
waitingResp, waitingResp, activeResp,
},
errs: []error{nil, nil, nil},
},
timeout: time.Second,
},
{
name: "timeout without transition",
client: &fakeStateClient{
responses: []*lnrpc.GetStateResponse{
waitingResp,
},
errs: []error{nil},
},
timeout: 300 * time.Millisecond,
expectError: true,
},
{
name: "timeout while erroring",
client: &fakeStateClient{
responses: []*lnrpc.GetStateResponse{nil},
errs: []error{
errors.New("connection refused"),
},
},
timeout: 300 * time.Millisecond,
expectError: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := waitForLndRPCReady(
context.Background(), tc.client, tc.timeout,
)
if tc.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

View file

@ -6,8 +6,12 @@ import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"strings"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon-bakery.v2/bakery"
"gopkg.in/macaroon.v2"
)
@ -118,3 +122,127 @@ func BakeSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient,
return hex.EncodeToString(macBytes), err
}
// SuperMacaroonExists determines whether a macaroon file exists at the given
// path.
func SuperMacaroonExists(path string) bool {
if _, err := os.Stat(path); err != nil {
return false
}
return true
}
// MacaroonMatchesPermissions checks if the macaroon at the given path contains
// exactly the expected permissions (no more and no less).
func MacaroonMatchesPermissions(path string,
expectedPerms []bakery.Op) (bool, error) {
macBytes, err := os.ReadFile(path)
if err != nil {
return false, err
}
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return false, err
}
rawID := mac.Id()
if len(rawID) == 0 || rawID[0] != byte(bakery.LatestVersion) {
return false, errors.New("invalid macaroon version")
}
decodedID := &lnrpc.MacaroonId{}
if err := proto.Unmarshal(rawID[1:], decodedID); err != nil {
return false, err
}
// Map expected permissions for easy lookup: entity -> action -> true.
expectedMap := make(map[string]map[string]bool)
for _, op := range expectedPerms {
if expectedMap[op.Entity] == nil {
expectedMap[op.Entity] = make(map[string]bool)
}
expectedMap[op.Entity][op.Action] = true
}
// Map actual permissions from decoded macaroon ID:
// entity -> action -> true.
actualMap := make(map[string]map[string]bool)
for _, op := range decodedID.Ops {
if op == nil {
continue
}
if actualMap[op.Entity] == nil {
actualMap[op.Entity] = make(map[string]bool)
}
for _, action := range op.Actions {
actualMap[op.Entity][action] = true
}
}
// Compare the mapped sets for exact equality.
if len(expectedMap) != len(actualMap) {
return false, nil
}
for entity, actions := range expectedMap {
actualActions, ok := actualMap[entity]
if !ok || len(actions) != len(actualActions) {
return false, nil
}
for action := range actions {
if !actualActions[action] {
return false, nil
}
}
}
return true, nil
}
// BakeAndWriteSuperMacaroon bakes a super macaroon and writes it to disk.
func BakeAndWriteSuperMacaroon(ctx context.Context, lnd lnrpc.LightningClient,
path string, perms []bakery.Op) error {
var suffixBytes [4]byte
rootKeyID := NewSuperMacaroonRootKeyID(suffixBytes)
superMacHex, err := BakeSuperMacaroon(
ctx, lnd, rootKeyID, perms, nil,
)
if err != nil {
return fmt.Errorf("unable to bake super macaroon: %w", err)
}
superMacBytes, err := hex.DecodeString(superMacHex)
if err != nil {
return fmt.Errorf("unable to decode baked "+
"super macaroon: %w", err)
}
if err := os.WriteFile(path, superMacBytes, 0600); err != nil {
return fmt.Errorf("unable to write super macaroon to %v: %w",
path, err)
}
return nil
}
// HasMacaroonSuffix checks that the super macaroon path is not empty and ends
// with the expected suffix.
func HasMacaroonSuffix(path string) error {
if path == "" {
return fmt.Errorf("super-macaroon-path cannot be empty")
}
if !strings.HasSuffix(path, ".macaroon") {
return fmt.Errorf("super-macaroon-path must end with the " +
".macaroon suffix")
}
return nil
}

View file

@ -1,9 +1,13 @@
package macaroons
import (
"encoding/hex"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/macaroon-bakery.v2/bakery"
)
var (
@ -43,3 +47,121 @@ func TestIsSuperMacaroon(t *testing.T) {
require.True(t, IsSuperMacaroon(testMacHex))
}
// TestSuperMacaroonHelpers tests that SuperMacaroonExists and
// MacaroonMatchesPermissions behave correctly.
func TestSuperMacaroonHelpers(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
path := filepath.Join(tempDir, "test.macaroon")
// Verify that it doesn't exist yet.
require.False(t, SuperMacaroonExists(path))
// Write the test macaroon.
macBytes, err := hex.DecodeString(testMacHex)
require.NoError(t, err)
err = os.WriteFile(path, macBytes, 0600)
require.NoError(t, err)
// Now it should exist.
require.True(t, SuperMacaroonExists(path))
// The macaroon matches this expected list of permissions.
expectedPerms := []bakery.Op{
{Entity: "account", Action: "read"},
{Entity: "auction", Action: "read"},
{Entity: "audit", Action: "read"},
{Entity: "auth", Action: "read"},
{Entity: "info", Action: "read"},
{Entity: "insights", Action: "read"},
{Entity: "invoices", Action: "read"},
{Entity: "loop", Action: "in"},
{Entity: "loop", Action: "out"},
{Entity: "macaroon", Action: "read"},
{Entity: "message", Action: "read"},
{Entity: "offchain", Action: "read"},
{Entity: "onchain", Action: "read"},
{Entity: "order", Action: "read"},
{Entity: "peers", Action: "read"},
{Entity: "rates", Action: "read"},
{Entity: "recommendation", Action: "read"},
{Entity: "report", Action: "read"},
{Entity: "suggestions", Action: "read"},
{Entity: "swap", Action: "read"},
{Entity: "terms", Action: "read"},
}
matches, err := MacaroonMatchesPermissions(path, expectedPerms)
require.NoError(t, err)
require.True(t, matches)
// A subset of permissions should NOT match exactly.
matches, err = MacaroonMatchesPermissions(path, expectedPerms[:5])
require.NoError(t, err)
require.False(t, matches)
// Extra/different permissions should NOT match exactly.
differentPerms := append(
expectedPerms,
bakery.Op{Entity: "invalid", Action: "write"},
)
matches, err = MacaroonMatchesPermissions(path, differentPerms)
require.NoError(t, err)
require.False(t, matches)
}
// TestHasMacaroonSuffix tests that HasMacaroonSuffix correctly
// checks the suffix of the super macaroon path.
func TestHasMacaroonSuffix(t *testing.T) {
t.Parallel()
tests := []struct {
name string
path string
wantErr bool
errStr string
}{
{
name: "empty path",
path: "",
wantErr: true,
errStr: "super-macaroon-path cannot be empty",
},
{
name: "valid path",
path: "/tmp/test.macaroon",
wantErr: false,
},
{
name: "invalid path - missing suffix",
path: "/tmp/test.mac",
wantErr: true,
errStr: "super-macaroon-path must end " +
"with the .macaroon suffix",
},
{
name: "invalid path - wrong suffix",
path: "/tmp/test.macaroon.tmp",
wantErr: true,
errStr: "super-macaroon-path must end " +
"with the .macaroon suffix",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := HasMacaroonSuffix(tt.path)
if tt.wantErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errStr)
} else {
require.NoError(t, err)
}
})
}
}

View file

@ -1,4 +1,4 @@
FROM golang:1.25.10-bookworm@sha256:154bd7001b6eb339e88c964442c0ad6ed5e53f09844cc818a41ce4ecb3ce3b43
FROM golang:1.25.11-bookworm@sha256:b96f24a8d7d010ea0acb9c3ba99064740f02b6b984612b28bd3c9c5ab9453e38
MAINTAINER Olaoluwa Osuntokun <laolu@lightning.engineering>

View file

@ -17,6 +17,10 @@ import (
const autoMigrateKVDBEnvVar = "LIT_AUTO_MIGRATE_TO_SQL"
var errKVDBToSQLMigrationDeclined = errors.New(
"manual confirmation declined",
)
var kvdbToSQLMigrationPromptLines = []string{
"",
"CAUTION: litd is about to migrate your existing data to a new SQL " +
@ -175,8 +179,8 @@ func promptForKVDBToSQLMigrationConfirmation(input io.Reader,
}
if strings.TrimSpace(answer) != "yes" {
return errors.New("manual confirmation declined; refusing to " +
"continue kvdb-to-SQL migration")
return fmt.Errorf("%w; refusing to continue kvdb-to-SQL "+
"migration", errKVDBToSQLMigrationDeclined)
}
return nil

View file

@ -58,7 +58,7 @@ func TestConfirmPendingKVDBToSQLMigration(t *testing.T) {
createActiveAccountsKVDB(t, dbDir)
},
input: "no\n",
expectErr: "manual confirmation declined",
expectErr: errKVDBToSQLMigrationDeclined.Error(),
},
{
name: "skips prompt on auto migration config",

View file

@ -1,10 +1,10 @@
module github.com/lightninglabs/lightning-terminal/perms
go 1.25.10
go 1.25.11
require (
github.com/btcsuite/btcd v0.25.1-0.20260310163610-1c55c7c18179
github.com/lightningnetwork/lnd v0.21.0-beta
github.com/lightningnetwork/lnd v0.21.1-beta
github.com/stretchr/testify v1.11.1
gopkg.in/macaroon-bakery.v2 v2.3.0
)
@ -12,7 +12,7 @@ require (
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 // indirect
@ -111,7 +111,7 @@ require (
github.com/lightningnetwork/lnd/sqldb v1.0.13 // indirect
github.com/lightningnetwork/lnd/ticker v1.1.1 // indirect
github.com/lightningnetwork/lnd/tlv v1.3.2 // indirect
github.com/lightningnetwork/lnd/tor v1.1.6 // indirect
github.com/lightningnetwork/lnd/tor v1.1.7 // indirect
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@ -126,7 +126,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/opencontainers/runc v1.3.6 // indirect
github.com/ory/dockertest/v3 v3.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.1 // indirect
@ -170,19 +170,19 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.10 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/macaroon.v2 v2.1.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect

View file

@ -9,8 +9,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@ -97,8 +97,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA=
@ -151,8 +151,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
@ -372,8 +372,8 @@ github.com/lightninglabs/neutrino/cache v1.1.3 h1:rgnabC41W+XaPuBTQrdeFjFCCAVKh1
github.com/lightninglabs/neutrino/cache v1.1.3/go.mod h1:qxkJb+pUxR5p84jl5uIGFCR4dGdFkhNUwMSxw3EUWls=
github.com/lightningnetwork/lightning-onion v1.3.0 h1:FqILgHjD6euc/Muo1VOzZ4+XDPuFnw6EYROBq0rR/5c=
github.com/lightningnetwork/lightning-onion v1.3.0/go.mod h1:nP85zMHG7c0si/eHBbSQpuDCtnIXfSvFrK3tW6YWzmU=
github.com/lightningnetwork/lnd v0.21.0-beta h1:bDP5UH15E7DVGTztsmBPQLqgyilq5EXDrglvQFmRc3U=
github.com/lightningnetwork/lnd v0.21.0-beta/go.mod h1:HcKq9DyxbVEZXuR28TIyGbIIgAjANCxI+N6dqOnRBAA=
github.com/lightningnetwork/lnd v0.21.1-beta h1:XYr8CRY3EB5bQ8I43/6QbOZP1dRi/XI6dwzTHlS2jxQ=
github.com/lightningnetwork/lnd v0.21.1-beta/go.mod h1:iqqf8Cyq0F7ssUftb17+Y644OWVg2OMOpvWSyzympXs=
github.com/lightningnetwork/lnd/actor v0.0.6 h1:Ge8N2wivARG+27qJBwTlB0vwsypStZYZy8vk4Zl38sU=
github.com/lightningnetwork/lnd/actor v0.0.6/go.mod h1:YAsoniSbY/cAM9HTVNfZLvt7RI6swDxy6wzPspTcMZg=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
@ -394,8 +394,8 @@ github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6
github.com/lightningnetwork/lnd/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
github.com/lightningnetwork/lnd/tlv v1.3.2 h1:MO4FCk7F4k5xPMqVZF6Nb/kOpxlwPrUQpYjmyKny5s0=
github.com/lightningnetwork/lnd/tlv v1.3.2/go.mod h1:pJuiBj1ecr1WWLOtcZ+2+hu9Ey25aJWFIsjmAoPPnmc=
github.com/lightningnetwork/lnd/tor v1.1.6 h1:WHUumk7WgU6BUFsqHuqszI9P6nfhMeIG+rjJBlVE6OE=
github.com/lightningnetwork/lnd/tor v1.1.6/go.mod h1:qSRB8llhAK+a6kaTPWOLLXSZc6Hg8ZC0mq1sUQ/8JfI=
github.com/lightningnetwork/lnd/tor v1.1.7 h1:BfY1KQGz3LVaeaAGI4XYGYCwDQR1ADFD1P/uXsS2dQc=
github.com/lightningnetwork/lnd/tor v1.1.7/go.mod h1:DANQ6QCQBiR+CqrM+mKudgkMy6CR/RS4ALDbjb2W4FU=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
@ -451,8 +451,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/opencontainers/runc v1.3.6 h1:SLGIymCtsk80iNPWgbc8dtjI30r+5mTVV+4dN8/17Sk=
github.com/opencontainers/runc v1.3.6/go.mod h1:o1wyv76EDlTkcf0KTFgN8bMWLPvgF/HfX709lDv+rr4=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
@ -621,8 +621,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4=
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
@ -636,8 +636,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -663,13 +663,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -680,8 +680,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -719,16 +719,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -738,8 +738,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -759,16 +759,16 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -778,18 +778,18 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -800,8 +800,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View file

@ -44,6 +44,10 @@ var (
Entity: "account",
Action: "read",
}},
"/litrpc.Accounts/AccountPayments": {{
Entity: "account",
Action: "read",
}},
"/litrpc.Accounts/RemoveAccount": {{
Entity: "account",
Action: "write",

View file

@ -2,6 +2,8 @@ syntax = "proto3";
package litrpc;
import "lnd.proto";
option go_package = "github.com/lightninglabs/lightning-terminal/litrpc";
service Accounts {
@ -52,6 +54,12 @@ service Accounts {
RemoveAccount removes the given account from the account database.
*/
rpc RemoveAccount (RemoveAccountRequest) returns (RemoveAccountResponse);
/* litcli: `accounts payments`
AccountPayments returns the detailed payment history for the given account.
*/
rpc AccountPayments (AccountPaymentsRequest)
returns (AccountPaymentsResponse);
}
message CreateAccountRequest {
@ -244,4 +252,55 @@ message AccountIdentifier {
// The label of the account.
string label = 2;
}
}
message AccountPaymentsRequest {
/*
The identifier of the account to query payments for.
*/
AccountIdentifier account = 1;
/*
The maximum number of payments to return. If set to 0, it will default
to 20. Capped at 50.
*/
uint64 max_payments = 2 [jstype = JS_STRING];
/*
The row offset into the list of payments that will be used as the start of
the query. The payments are returned in ascending lexicographical order of
their payment hash.
*/
uint64 index_offset = 3 [jstype = JS_STRING];
/*
If set, the total number of payments matching the query will be returned
in the response.
*/
bool count_total_payments = 4;
}
message AccountPaymentsResponse {
/*
The detailed payments associated with the account, sorted in ascending
lexicographical order of their payment hash.
*/
repeated lnrpc.Payment payments = 1;
/*
The row offset of the first payment returned.
*/
uint64 first_index_offset = 2 [jstype = JS_STRING];
/*
The row offset of the last payment returned. This can be used as the
index_offset in a subsequent query to paginate forwards.
*/
uint64 last_index_offset = 3 [jstype = JS_STRING];
/*
The total number of payments matching the query (only set if
count_total_payments was true in the request).
*/
uint64 total_num_payments = 4 [jstype = JS_STRING];
}

View file

@ -1,6 +1,7 @@
syntax = "proto3";
import "swapserverrpc/common.proto";
import "lnd.proto";
package looprpc;
@ -206,13 +207,34 @@ service SwapClient {
rpc GetStaticAddressSummary (StaticAddressSummaryRequest)
returns (StaticAddressSummaryResponse);
/* loop:`static`
/* loop:`static in`
StaticAddressLoopIn initiates a static address loop-in swap.
*/
rpc StaticAddressLoopIn (StaticAddressLoopInRequest)
returns (StaticAddressLoopInResponse);
/* loop:`static openchannel`
StaticOpenChannel opens a channel funded by selected static address
deposits.
*/
rpc StaticOpenChannel (StaticOpenChannelRequest)
returns (StaticOpenChannelResponse);
}
message StaticOpenChannelRequest {
// Wrap lnd's request so Loop can extend this RPC with Loop-specific fields
// without diverging from the upstream API surface.
lnrpc.OpenChannelRequest open_channel_request = 1;
}
message StaticOpenChannelResponse {
/*
The outpoint of the channel opening transaction in the format
"txid:output_index".
*/
string channel_open_outpoint = 1;
};
message StopDaemonRequest {
}
@ -1170,6 +1192,18 @@ message GetInfoResponse {
message GetLiquidityParamsRequest {
}
enum LoopInSource {
/*
Use the legacy wallet-funded loop-in flow.
*/
LOOP_IN_SOURCE_WALLET = 0;
/*
Use deposited static-address funds for loop-in autoloops.
*/
LOOP_IN_SOURCE_STATIC_ADDRESS = 1;
}
message LiquidityParameters {
/*
A set of liquidity rules that describe the desired liquidity balance.
@ -1348,6 +1382,11 @@ message LiquidityParameters {
considered for easy autoloop swaps.
*/
repeated bytes easy_autoloop_excluded_peers = 27;
/*
Selects which source autoloop uses for loop-in rules.
*/
LoopInSource loop_in_source = 28;
}
message EasyAssetAutoloopParams {
@ -1504,6 +1543,18 @@ enum AutoReason {
the portion of total swap amount that we allow fees to consume.
*/
AUTO_REASON_FEE_INSUFFICIENT = 13;
/*
No static loop-in candidate indicates that static loop-in autoloop was
selected, but no full-deposit static candidate fit the rule.
*/
AUTO_REASON_STATIC_LOOP_IN_NO_CANDIDATE = 14;
/*
Custom channel data indicates that the target channel carries custom
channel data and is excluded from the standard autoloop planner.
*/
AUTO_REASON_CUSTOM_CHANNEL_DATA = 15;
}
message Disqualified {
@ -1534,6 +1585,11 @@ message SuggestSwapsResponse {
*/
repeated LoopInRequest loop_in = 3;
/*
The set of recommended static-address loop in swaps.
*/
repeated StaticAddressLoopInRequest static_loop_in = 4;
/*
Disqualified contains the set of channels that swaps are not recommended
for.
@ -1771,7 +1827,7 @@ message WithdrawDepositsRequest {
/*
The outpoints of the deposits to withdraw.
*/
repeated OutPoint outpoints = 1;
repeated lnrpc.OutPoint outpoints = 1;
/*
If set to true, all deposits will be withdrawn.
@ -1810,23 +1866,6 @@ message WithdrawDepositsResponse {
string address = 2;
}
message OutPoint {
/*
Raw bytes representing the transaction id.
*/
bytes txid_bytes = 1;
/*
Reversed, hex-encoded string representing the transaction id.
*/
string txid_str = 2;
/*
The index of the output on the transaction.
*/
uint32 output_index = 3;
}
message ListStaticAddressDepositsRequest {
/*
Filters the list of all stored deposits by deposit state.
@ -1914,6 +1953,11 @@ message StaticAddressSummaryResponse {
The total value of all htlc timeout sweeps that the client swept.
*/
int64 value_htlc_timeout_sweeps_satoshis = 9 [jstype = JS_STRING];
/*
The total value of all deposits that have been used for channel openings.
*/
int64 value_channels_opened = 10 [jstype = JS_STRING];
}
enum DepositState {
@ -1982,6 +2026,18 @@ enum DepositState {
has been sufficiently confirmed.
*/
EXPIRED = 10;
/*
OPENING_CHANNEL indicates that the channel open in which the deposit was
used is in progress.
*/
OPENING_CHANNEL = 11;
/*
CHANNEL_PUBLISHED indicates that the channel open was finalized and
published and that it should be managed from lnd from now on.
*/
CHANNEL_PUBLISHED = 12;
}
message Deposit {
@ -2087,6 +2143,31 @@ message StaticAddressLoopInSwap {
The deposits that were used for this swap.
*/
repeated Deposit deposits = 6;
/*
Initiation time of the swap.
*/
int64 initiation_time = 7 [jstype = JS_STRING];
/*
Last update time of the swap.
*/
int64 last_update_time = 8 [jstype = JS_STRING];
/*
Swap server cost.
*/
int64 cost_server = 9 [jstype = JS_STRING];
/*
On-chain transaction cost.
*/
int64 cost_onchain = 10 [jstype = JS_STRING];
/*
Off-chain routing fees.
*/
int64 cost_offchain = 11 [jstype = JS_STRING];
}
enum StaticAddressLoopInSwapState {

View file

@ -679,6 +679,11 @@ message SubscribeNotificationsResponse {
ServerReservationNotification reservation_notification = 1;
ServerStaticLoopInSweepNotification static_loop_in_sweep = 2;
ServerUnfinishedSwapNotification unfinished_swap = 3;
ServerStaticLoopInRiskAcceptedNotification
static_loop_in_risk_accepted = 4;
ServerStaticLoopInRiskRejectedNotification
static_loop_in_risk_rejected = 5;
ServerHtlcConfirmedNotification htlc_confirmed = 6;
}
}
@ -699,6 +704,22 @@ message ServerStaticLoopInSweepNotification {
repeated PrevoutInfo prevout_info = 4;
}
// ServerStaticLoopInRiskAcceptedNotification tells the client that the server
// has accepted confirmation risk for a static loop-in and will start attempting
// the off-chain swap payment.
message ServerStaticLoopInRiskAcceptedNotification {
// The swap hash the accepted risk belongs to.
bytes swap_hash = 1;
}
// ServerStaticLoopInRiskRejectedNotification tells the client that the server
// will not continue waiting for confirmation risk acceptance for a static
// loop-in.
message ServerStaticLoopInRiskRejectedNotification {
// The swap hash the rejected risk wait belongs to.
bytes swap_hash = 1;
}
// ServerUnfinishedSwapNotification notifies the client about an unfinished
// swap that needs attention. This is useful in client data loss scenarios to
// help the client recover the swap.
@ -709,3 +730,20 @@ message ServerUnfinishedSwapNotification {
// Whether the swap is a loop in or loop out.
bool is_loop_in = 2;
}
// ServerHtlcConfirmedNotification is a notification from the server to the
// client that the on-chain HTLC for a loop out swap has been confirmed.
message ServerHtlcConfirmedNotification {
// The swap hash of the loop out swap.
bytes swap_hash = 1;
// htlc_outpoint is the outpoint of the confirmed HTLC in the format
// "txid:output_index".
string htlc_outpoint = 2;
// htlc_address is the address of the confirmed HTLC output.
string htlc_address = 3;
// sat_per_vbyte is the requested recovery sweep fee rate.
uint32 sat_per_vbyte = 4;
}

View file

@ -24,8 +24,6 @@ import (
//
// nolint:ll
type SQLQueries interface {
sqldb.BaseQuerier
GetAliasBySessionID(ctx context.Context, id int64) ([]byte, error)
GetSessionByID(ctx context.Context, id int64) (sqlc.Session, error)
GetSessionsInGroup(ctx context.Context, groupID sql.NullInt64) ([]sqlc.Session, error)
@ -78,7 +76,7 @@ type SQLStore struct {
clock clock.Clock
}
type sqlQueriesExecutor[T sqldb.BaseQuerier] struct {
type sqlQueriesExecutor[T any] struct {
*sqldb.TransactionExecutor[T]
SQLQueries

View file

@ -58,6 +58,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/rpcperms"
@ -87,6 +88,10 @@ const (
defaultRPCTimeout = 3 * time.Minute
minimumRPCTimeout = 30 * time.Second
defaultStartupTimeout = 5 * time.Second
// stateServicePollInterval is how often we poll lnd's StateService
// while waiting for its RPC interceptor to leave WAITING_TO_START.
stateServicePollInterval = 200 * time.Millisecond
)
// restRegistration is a function type that represents a REST proxy
@ -289,7 +294,7 @@ func (g *LightningTerminal) Run(ctx context.Context) error {
}
}()
cfg, err := loadAndValidateConfig(shutdownInterceptor)
cfg, err := loadAndValidateConfig(ctx, shutdownInterceptor)
if err != nil {
return fmt.Errorf("could not load config: %w", err)
}
@ -405,6 +410,19 @@ func (g *LightningTerminal) Run(ctx context.Context) error {
g.statusMgr.SetErrored(
subservers.LIT, "could not start Lit: %v", startErr,
)
// If the user has declined the migration prompt, we shut down
// Lit fully, and do not keep the status server up and running.
// The motivation for this is that the error occurs prior to
// Lit being able to accept a `litcli stop` call. Users running
// in an env that can't easily kill the daemon therefore need to
// be able to shut it down by just declining the migration.
// Note that no sub-servers, including `lnd`, have been
// started/connected to when the migration prompt is shown. We
// can therefore safely shut Lit down without affecting them.
if errors.Is(startErr, errKVDBToSQLMigrationDeclined) {
shutdownInterceptor.RequestShutdown()
}
}
// Now block until we receive an error or the main shutdown
@ -660,6 +678,22 @@ func (g *LightningTerminal) start(ctx context.Context) error {
err)
}
// The unlockChan/readyChan signal we waited on above only guarantees
// that lnd's gRPC listener socket is bound, not that lnd's RPC
// interceptor has advanced far enough to service non-State RPCs. Wait
// for that here so that the "Wallet Ready" status set below is not
// observed before it's actually true.
lndStateClient := lnrpc.NewStateClient(g.lndConn)
if err := waitForLndRPCReady(
ctx, lndStateClient, defaultConnectTimeout,
); err != nil {
g.statusMgr.SetErrored(
subservers.LND, "lnd RPC not ready: %v", err,
)
return fmt.Errorf("lnd RPC not ready: %v", err)
}
// We now set a custom status for the LND sub-server to indicate that
// the wallet is ready.
// This is done _before_ we have set up the lnd clients so that the
@ -749,8 +783,25 @@ func (g *LightningTerminal) start(ctx context.Context) error {
return fmt.Errorf("could not start firewall DB: %v", err)
}
var accountsOpts []accounts.ServiceOption
if g.cfg.Accounts.MaxPaymentSizeMsat > 0 {
accountsOpts = append(
accountsOpts, accounts.WithMaxPaymentSize(
lnwire.MilliSatoshi(
g.cfg.Accounts.MaxPaymentSizeMsat,
),
),
)
}
if g.cfg.Accounts.CheckChannelBalance {
accountsOpts = append(
accountsOpts, accounts.WithChannelBalanceCheck(),
)
}
g.accountService, err = accounts.NewService(
g.stores.accounts, accountServiceErrCallback,
g.stores.accounts, accountServiceErrCallback, accountsOpts...,
)
if err != nil {
return fmt.Errorf("error creating account service: %v", err)
@ -789,6 +840,13 @@ func (g *LightningTerminal) start(ctx context.Context) error {
return fmt.Errorf("could not start litd sub-servers: %v", err)
}
// Bake the super macaroon on startup if configured, now that all local
// and remote sub-servers have been started and active permissions are
// fully known.
if err := g.setupSuperMacaroon(ctx); err != nil {
return fmt.Errorf("could not setup super macaroon: %w", err)
}
// We can now set the status of LiT as running.
g.statusMgr.SetRunning(subservers.LIT)
@ -2159,3 +2217,64 @@ func randId(n int) string {
return string(b)
}
// setupSuperMacaroon bakes a super macaroon and writes it to disk if needed.
func (g *LightningTerminal) setupSuperMacaroon(ctx context.Context) error {
// If the bake-super-macaroon option is set to none, we don't bake a
// macaroon.
if g.cfg.BakeSuperMacaroon == noneChoice {
return nil
}
// If the super macaroon baking option is enabled, we cannot run in
// stateless initialization mode as it won't write any macaroons to the
// filesystem.
if g.cfg.statelessInitMode {
return fmt.Errorf("cannot use bake-super-macaroon " +
"with stateless-init mode")
}
path := g.cfg.SuperMacaroonPath
readOnly := g.cfg.BakeSuperMacaroon == readOnlyChoice
activePerms := g.permsMgr.ActivePermissions(readOnly)
if litmac.SuperMacaroonExists(path) {
matches, err := litmac.MacaroonMatchesPermissions(
path, activePerms,
)
if err == nil && matches {
log.Debugf("Super macaroon already exists at "+
"%v and matches configuration, "+
"skipping bake", path)
return nil
}
if err != nil {
return fmt.Errorf(
"unable to verify super macaroon "+
"permissions at %v, please delete "+
"it if the issue persists: %w",
path, err,
)
}
log.Infof("Super macaroon permissions " +
"differ from configuration, " +
"regenerating...")
}
log.Infof("Baking super macaroon on startup...")
// Bake the super macaroon and write it to disk.
if err := litmac.BakeAndWriteSuperMacaroon(
ctx, g.basicClient, path, activePerms,
); err != nil {
return err
}
log.Infof("Successfully baked and wrote super macaroon to %v", path)
return nil
}

View file

@ -1,4 +1,4 @@
FROM golang:1.25.10-bookworm@sha256:154bd7001b6eb339e88c964442c0ad6ed5e53f09844cc818a41ce4ecb3ce3b43
FROM golang:1.25.11-bookworm@sha256:b96f24a8d7d010ea0acb9c3ba99064740f02b6b984612b28bd3c9c5ab9453e38
RUN apt-get update && apt-get install -y git
ENV GOCACHE=/tmp/build/.cache

View file

@ -1,6 +1,6 @@
module github.com/lightninglabs/lightning-terminal/tools
go 1.25.10
go 1.25.11
require (
github.com/btcsuite/btcd v0.24.2
@ -202,13 +202,13 @@ require (
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto v0.52.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.38.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/tools v0.44.0 // indirect
golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect
google.golang.org/protobuf v1.36.4 // indirect

View file

@ -754,8 +754,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -802,8 +802,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -850,8 +850,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -873,8 +873,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -937,8 +937,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@ -959,8 +959,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1023,8 +1023,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=

View file

@ -1,6 +1,6 @@
module github.com/lightninglabs/lightning-terminal/tools/linters
go 1.25.10
go 1.25.11
require (
github.com/golangci/plugin-module-register v0.1.1

View file

@ -39,7 +39,7 @@ const (
// appPreRelease MUST only contain characters from semanticAlphabet per
// the semantic versioning spec.
appPreRelease = "alpha.rc1"
appPreRelease = "alpha"
)
// Version returns the application version as a properly formed string per the