mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
Compare commits
104 commits
v0.17.0-al
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fba30046eb | ||
|
|
a04ecb750d | ||
|
|
5ed07dbf71 | ||
|
|
b6646d7868 | ||
|
|
23433a05ed | ||
|
|
6bca24793c | ||
|
|
a493b6a3d6 | ||
|
|
ae74392433 | ||
|
|
9907dabc9f | ||
|
|
970a3fb55d | ||
|
|
fb5863e38c | ||
|
|
83df345294 | ||
|
|
4ead197fe3 | ||
|
|
dc72c6225c | ||
|
|
0f5ebe7061 | ||
|
|
475c8609a0 | ||
|
|
519ce8a154 | ||
|
|
810cbf78c8 | ||
|
|
728e310c52 | ||
|
|
0d2cbdcf49 | ||
|
|
875c48806f | ||
|
|
f162f44a0d | ||
|
|
0bd5af814c | ||
|
|
0c06130ea1 | ||
|
|
7cde9c50ae | ||
|
|
c5fbbd4d77 | ||
|
|
d3e51fac17 | ||
|
|
3975e83006 | ||
|
|
e093948a35 | ||
|
|
e631ccf0e5 | ||
|
|
11526d0388 | ||
|
|
47e1f4099b | ||
|
|
f82bd5719b | ||
|
|
3a2acb6c31 | ||
|
|
bb4c85d427 | ||
|
|
5c5a4130f0 | ||
|
|
6a7cc15c45 | ||
|
|
c6d81a8713 | ||
|
|
0977e375d6 | ||
|
|
1abbbe649c | ||
|
|
6754acc17b | ||
|
|
d553cb28d7 | ||
|
|
009a5c28bc | ||
|
|
857fb900fd | ||
|
|
5632998030 | ||
|
|
a132fca1ba | ||
|
|
f5a1a020eb | ||
|
|
ec6814e940 | ||
|
|
cf7e53842a | ||
|
|
6d89f07037 | ||
|
|
73569014f2 | ||
|
|
745f70f194 | ||
|
|
7604bd297b | ||
|
|
403efb3b46 | ||
|
|
19f09f6350 | ||
|
|
9370a6aef3 | ||
|
|
6f8159e003 | ||
|
|
c478f8227a | ||
|
|
8efa0e0656 | ||
|
|
324f87d7da | ||
|
|
767250e982 | ||
|
|
95f1ba71e9 | ||
|
|
2a4a57b55b | ||
|
|
41a6c8b3f7 | ||
|
|
9bfcdbe9ae | ||
|
|
3c035fe862 | ||
|
|
1093de57b4 | ||
|
|
b3e7b2fc57 | ||
|
|
4baf098cdf | ||
|
|
053498caba | ||
|
|
512617bfa2 | ||
|
|
8f6c8a968e | ||
|
|
4614ade31c | ||
|
|
e3091c9045 | ||
|
|
58568b5afc | ||
|
|
4e72e798e6 | ||
|
|
08934deeef | ||
|
|
7a40162f84 | ||
|
|
bcdbe68fa1 | ||
|
|
b4133b1a12 | ||
|
|
8cd75e44d9 | ||
|
|
569fdee4f4 | ||
|
|
901d60cc0e | ||
|
|
503201ffa7 | ||
|
|
5032e33d35 | ||
|
|
5c34be31f4 | ||
|
|
69eb14cc63 | ||
|
|
163cccba89 | ||
|
|
916ca71d70 | ||
|
|
02a1b31303 | ||
|
|
05d0566986 | ||
|
|
484d5d789e | ||
|
|
04f06144bd | ||
|
|
0b8fd17457 | ||
|
|
f78be1d0bd | ||
|
|
edbd4f51b4 | ||
|
|
51c56982fb | ||
|
|
42c3105537 | ||
|
|
6d2bf233e8 | ||
|
|
059554474e | ||
|
|
27eba1e5c1 | ||
|
|
d9b8d6d4b8 | ||
|
|
650fd92259 | ||
|
|
409cba4f63 |
97 changed files with 9886 additions and 3276 deletions
48
.github/actions/cleanup-space/action.yml
vendored
Normal file
48
.github/actions/cleanup-space/action.yml
vendored
Normal 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
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
48
.github/workflows/main.yml
vendored
48
.github/workflows/main.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -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) | \
|
||||
|
|
|
|||
|
|
@ -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 "+
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
69
app/src/types/generated/lit-accounts_pb.d.ts
generated
vendored
69
app/src/types/generated/lit-accounts_pb.d.ts
generated
vendored
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
537
app/src/types/generated/lit-accounts_pb.js
generated
537
app/src/types/generated/lit-accounts_pb.js
generated
|
|
@ -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);
|
||||
|
|
|
|||
19
app/src/types/generated/lit-accounts_pb_service.d.ts
generated
vendored
19
app/src/types/generated/lit-accounts_pb_service.d.ts
generated
vendored
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
40
app/src/types/generated/lit-accounts_pb_service.js
generated
40
app/src/types/generated/lit-accounts_pb_service.js
generated
|
|
@ -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
126
app/src/types/generated/loop_pb.d.ts
generated
vendored
|
|
@ -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;
|
||||
|
|
|
|||
875
app/src/types/generated/loop_pb.js
generated
875
app/src/types/generated/loop_pb.js
generated
File diff suppressed because it is too large
Load diff
19
app/src/types/generated/loop_pb_service.d.ts
generated
vendored
19
app/src/types/generated/loop_pb_service.d.ts
generated
vendored
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
40
app/src/types/generated/loop_pb_service.js
generated
40
app/src/types/generated/loop_pb_service.js
generated
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
99
app/src/types/generated/swapserverrpc/server_pb.d.ts
generated
vendored
99
app/src/types/generated/swapserverrpc/server_pb.d.ts
generated
vendored
|
|
@ -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;
|
||||
|
|
|
|||
780
app/src/types/generated/swapserverrpc/server_pb.js
generated
780
app/src/types/generated/swapserverrpc/server_pb.js
generated
|
|
@ -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}
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
4574
app/yarn.lock
4574
app/yarn.lock
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
173
cmd/litcli/sessions_test.go
Normal 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
228
config.go
|
|
@ -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
151
config_test.go
Normal 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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
215
db/migsets/programmatic_migrations_test.go
Normal file
215
db/migsets/programmatic_migrations_test.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
120
docs/release-notes/release-notes-0.17.1.md
Normal file
120
docs/release-notes/release-notes-0.17.1.md
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
44
go.mod
|
|
@ -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
88
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,4 +31,8 @@ var allTestCases = []*testCase{
|
|||
name: "kvdb to sql migration",
|
||||
test: testKvdbSQLMigration,
|
||||
},
|
||||
{
|
||||
name: "terminal super macaroon on startup",
|
||||
test: testSuperMacaroonOnStartup,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -21,3 +21,5 @@ http:
|
|||
- selector: litrpc.Accounts.DebitAccount
|
||||
post: "/v1/accounts/debit/{account.id}"
|
||||
body: "*"
|
||||
- selector: litrpc.Accounts.AccountPayments
|
||||
get: "/v1/accounts/payments"
|
||||
|
|
|
|||
|
|
@ -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
54
lnd_ready.go
Normal 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
117
lnd_ready_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
30
perms/go.mod
30
perms/go.mod
|
|
@ -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
|
||||
|
|
|
|||
80
perms/go.sum
80
perms/go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ var (
|
|||
Entity: "account",
|
||||
Action: "read",
|
||||
}},
|
||||
"/litrpc.Accounts/AccountPayments": {{
|
||||
Entity: "account",
|
||||
Action: "read",
|
||||
}},
|
||||
"/litrpc.Accounts/RemoveAccount": {{
|
||||
Entity: "account",
|
||||
Action: "write",
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
119
proto/loop.proto
119
proto/loop.proto
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
123
terminal.go
123
terminal.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
14
tools/go.mod
14
tools/go.mod
|
|
@ -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
|
||||
|
|
|
|||
28
tools/go.sum
28
tools/go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue