Compare commits

...

143 Commits

Author SHA1 Message Date
Boris Nagaev bff1fd8004
Merge pull request #782 from starius/sweep-GetSweepFeeDetails
sweep: factor out method GetSweepFeeDetails
22 hours ago
Boris Nagaev 84ae3667d5
sweep: factor out method GetSweepFeeDetails
It was factored out from GetSweepFee method. It provides fee rate
and weight in addition to absolute fee.
4 days ago
Boris Nagaev 0ec63a61d4
Merge pull request #778 from starius/sweepbatcher-sql-test
sweepbatcher: unit tests with SQL loopdb as well
4 days ago
Boris Nagaev 029d0870b5
sweepbatcher: unit tests with SQL loopdb as well
This is needed to cover the code of SQLStore with tests.

To achieve compatibility with loopdb (SQLite), the following changes were done:

 - DestAddr is filled to avoid crash in SQL layer
 - Preimage is filled to avoid uniqueness checks by the DB
 - the code working with batch IDs was changed to work correctly
   when batch_id starts with 1 instead of 0
 - SQL swap store has to have a swap with swap_hash from sweep
   to satisfy foreign key constraint of the DB
4 days ago
Boris Nagaev 137dd80d39
sweepbatcher: use %w for wrapped errors
Fix errors.Is(err, context.Canceled) check in tests.
6 days ago
Boris Nagaev a4e01027ea
Merge pull request #781 from starius/sweepbatcher-exectx
loopdb: parameterize ExecTx with subset of methods used by a package
6 days ago
Boris Nagaev d6aa4fe8a9
loopdb: all BaseDB's methods use receiver name db 6 days ago
Boris Nagaev 4c2d874b8d
loopdb: use passed transaction in ExecTx calls
Some calls used external queries instead of the passed one,
bypassing the transaction.
7 days ago
Boris Nagaev 1947b90842
multi: typed callback in ExecTx
Provide a wrapped store type, exposing ExecTx method with a subset
interface in the callback argument. BaseDB interfaces in instantout,
reservation and sweepbatcher use ExecTx with their subset Querier
instead of whole sqlc.Querier (*sqlc.Queries).

This is needed to make the packages more reusable, so they don't
depend on methods of *sqlc.Queries they don't use.
7 days ago
Boris Nagaev d341448568
loopdb: fix description of BaseDB.ExecTx 7 days ago
Boris Nagaev 2847e83fcf
multi: separate Querier from BaseDB interfaces
A Querier holds the methods of sqlc.Querier interface relevant for a package.
BaseDB has Querier + ExecTx method.

This change is needed to simplify further rework of ExecTx.
7 days ago
Boris Nagaev 8d8cd9129f
reservation: add missing BaseDB interface method
Method InsertReservationUpdate was missing in the interface.
7 days ago
Boris Nagaev 91507d8633
reservation: fix description of UpdateReservation
The description was from another method (InsertReservationUpdate).
7 days ago
Boris Nagaev 4ad86b615a
Merge pull request #779 from starius/sweepbatcher-fix-race
sweepbatcher: fix race condition when stopping
7 days ago
Boris Nagaev c34d04e2f7
sweepbatcher: fix race condition when stopping
The race was detected in CI and locally when running with -race.
It happened between the following calls:

WARNING: DATA RACE
Write at 0x00c0003e6638 by goroutine 1374:
  runtime.racewrite()
      <autogenerated>:1 +0x1e
  github.com/lightninglabs/loop/sweepbatcher.(*batch).Wait()
      sweepbatcher/sweep_batch.go:463 +0x6e
  github.com/lightninglabs/loop/sweepbatcher.(*Batcher).Run.func1()
      sweepbatcher/sweep_batcher.go:272 +0x10e

Previous read at 0x00c0003e6638 by goroutine 1388:
  runtime.raceread()
      <autogenerated>:1 +0x1e
  github.com/lightninglabs/loop/sweepbatcher.(*batch).monitorConfirmations()
      sweepbatcher/sweep_batch.go:1144 +0x285
  github.com/lightninglabs/loop/sweepbatcher.(*batch).handleSpend()
      sweepbatcher/sweep_batch.go:1309 +0x10e4
  github.com/lightninglabs/loop/sweepbatcher.(*batch).Run()
      sweepbatcher/sweep_batch.go:526 +0xb04
  github.com/lightninglabs/loop/sweepbatcher.(*Batcher).spinUpBatch.func1()
      sweepbatcher/sweep_batcher.go:455 +0xbd

The race was caused because wg.Add(1) and wg.Wait() were running from different
goroutines (one goroutine was running batch.Run() and another - batcher.Run()).

To avoid this scenario, wg.Wait() call was moved into batch.Run() call, so it
waits itself for its children goroutines, after which the channel b.finished
is closed, and it serves a signal for external waiters (the batcher, calling
batch.Wait()).

Also the channel batch.stopped was renamed to batch.stopping to better reflect
its nature.

Added TestSweepBatcherCloseDuringAdding to make sure adding a sweep during
shutting down does not cause a crash. The test did not catch the original
race condition.
1 week ago
Boris Nagaev 8f985143e2
Merge pull request #780 from starius/sweepbatcher-dropbatch-writetx
sweepbatcher: use write tx in DropBatch
1 week ago
Boris Nagaev b311649ce4
sweepbatcher: use write tx in DropBatch 1 week ago
Boris Nagaev 3fd3fab8d1
loopdb: provide function NewSqlWriteOpts
It returns transaction options resulting in full (read+write) tx.

Use it in loopdb and instantout stores.
1 week ago
Slyghtning 174a6b888b
Merge pull request #777 from hieblmi/refactor-select-hop-hints
utils: refactor SelectHopHints to use narrower interface
2 weeks ago
Slyghtning 5b797055f8
utils: refactor SelectHopHints to use narrower interface 2 weeks ago
Slyghtning 3b3d3ac64f
Merge pull request #776 from chengehe/master
chore: make function comments match function names
3 weeks ago
chengehe 0aff202988 chore: make function comments match function names
Signed-off-by: chengehe <hechenge@yeah.net>
3 weeks ago
Alex Bosworth 88c7194e2e
Merge pull request #775 from lightninglabs/alexbosworth-patch-3
version: bump version to v0.28.5-beta
3 weeks ago
Alex Bosworth 2efe224b9a
version: bump version to v0.28.5-beta 3 weeks ago
Konstantin Nick 557ac34959
Merge pull request #774 from jinjingroad/master
chore: fix some comments
3 weeks ago
jinjingroad 12bc3b2e9e chore: fix some comments
Signed-off-by: jinjingroad <jinjingroad@sina.com>
3 weeks ago
András Bánki-Horváth d47158d58e
Merge pull request #773 from bhandras/cost-migraton-pagination
loop: paginate fetching payments when running the cost migration
3 weeks ago
Andras Banki-Horvath 3754730525
loop: paginate fetching payments when running the cost migration 3 weeks ago
Alex Bosworth 4652417b25
Merge pull request #772 from lightninglabs/alexbosworth-patch-2
version: bump version to v0.28.4-beta
3 weeks ago
Alex Bosworth 6fe1686cc6
version: bump version to v0.28.4-beta 3 weeks ago
András Bánki-Horváth 18b0de3c5e
Merge pull request #771 from bhandras/cost-migration-timeout
loopd: make the LND RPC timeout configurable and fix cost migration for pending swaps
3 weeks ago
Andras Banki-Horvath 9cacd8eb75
loop: fix cost migration for pending swaps 3 weeks ago
Andras Banki-Horvath 28c09bec68
loopd: make the LND RPC timeout configurable 3 weeks ago
Boris Nagaev 3d1d3eb4aa
Merge pull request #766 from starius/sweepbatcher-sweep-fetcher
sweepbatcher: factor out loopdb-less version
3 weeks ago
Boris Nagaev 0c2ba74dba
sweepbatcher: factor out loopdb-less version
Changed argument of function NewBatcher from LoopOutFetcher to SweepFetcher
(returning new public type SweepInfo).
This change is backwards-incompatible on the package layer, but nobody seems
to use the package outside of Loop.

To use NewBatcher inside Loop, turn loopdb into SweepFetcher using
function NewSweepFetcherFromSwapStore.
3 weeks ago
Boris Nagaev 295198a902
Merge pull request #770 from starius/lnd-v0.18.0-beta.1
go.mod: update LND to v0.18.0-beta.1
3 weeks ago
Boris Nagaev 99a0f831b3
go.mod: update LND to v0.18.0-beta.1
This is the same as v0.18.0-beta.
3 weeks ago
Konstantin Nick c5245e1009
Merge pull request #769 from lightninglabs/fix_docker_build
build: fix dockerfile
3 weeks ago
sputn1ck 890718d0ee build: fix dockerfile 3 weeks ago
Alex Bosworth 67ab876879
Merge pull request #768 from lightninglabs/alexbosworth-patch-2
version: bump version to v0.28.3-beta
3 weeks ago
Alex Bosworth 9b1218a752
version: bump version to v0.28.3-beta 3 weeks ago
András Bánki-Horváth 55241ffe04
Merge pull request #764 from bhandras/costs-cleanup-migration
loop: add migration to fix stored loop out costs
3 weeks ago
Andras Banki-Horvath c7fa1e4109
loopd: run the cost migration on start 3 weeks ago
Andras Banki-Horvath f40ef193e9
loop: add migration for loopout swaps to fix negative stored costs
Previously we may have stored negative costs for some loop out swaps
which this commit attempts to correct by fetching all completed swap,
calculating the correct costs and overriding them in the database.
3 weeks ago
Andras Banki-Horvath c650cdc8a8
loopdb: add ability to track manual migrations 3 weeks ago
Andras Banki-Horvath 4f5c806ba5
loopdb: add helper methods to update swap costs
This commit adds the necessary sqlc code and SwapStore function to
update swap costs for all swaps in one transaction.
3 weeks ago
Andras Banki-Horvath 08aa4db35d
loopout: correctly account for the prepay amount when calculating costs
Previously we'd not account for the prepay amount which resulted in the
total swap cost reported being lower than what it actually is.
3 weeks ago
Boris Nagaev 37194d31cf
Merge pull request #761 from starius/sweepbatcher-load-swaps-from-loopdb-only
sweepbatcher/Store: use only own tables (separating from loopdb)
4 weeks ago
Boris Nagaev 7a7ea05e52
sweepbatcher/Store: do not provide LoopOut field
This data is not used by Batcher since commit
"sweepbatcher: load swap from loopdb, not own store".

Now sweepbatcher.Store depends only on tables sweeps and sweep_batches
and does not depend on swaps, loopout_swaps and htlc_keys, making it
easier to reuse.
4 weeks ago
Boris Nagaev 4be69e186e
Revert "sweepbatcher/StoreMock: load LoopOut from loopdb"
This reverts commit d38b7c55a7.

Batcher does not use this data anymore, since previous commit.
4 weeks ago
Boris Nagaev 16132d1593
sweepbatcher: load swap from loopdb, not own store
Method Store.GetBatchSweeps provides data from tables outside of sweepbatcher:
swaps, loopout_swaps, htlc_keys. It makes it harder to reuse. Batcher already
has a straightforward way to get swap data: LoopOutFetcher interface (loopdb).

In this commit I switch the source of data from the field returned by Store
(LoopOut) to loading independently by calling LoopOutFetcher.FetchLoopOutSwap.
4 weeks ago
Boris Nagaev c5862898a8
sqlc: remove trailing spaces in queries 4 weeks ago
Slyghtning 6b09aaaca4
Merge pull request #763 from hieblmi/bump-lnd-0.18.0-beta
gomod: bump lnd to version 0.18.0-beta
4 weeks ago
Slyghtning 402d9a84df
gomod: bump lnd to version 0.18.0-beta 4 weeks ago
Boris Nagaev 60c149f885
Merge pull request #762 from starius/fix-flaky-autoloop-test
liquidity: fix flaky autoloop test
4 weeks ago
Boris Nagaev 3be5a37cd3
liquidity: fix flaky autoloop test
This failure became normal recently:

=== RUN   TestAutoLoopInEnabled
    autoloop_testcontext_test.go:318:
        	Error Trace:	/home/runner/work/loop/loop/liquidity/autoloop_testcontext_test.go:318
        	            				/home/runner/work/loop/loop/liquidity/autoloop_test.go:804
        	Error:      	Not equal:
        	            	expected: 80000
        	            	actual  : 160000
        	Test:       	TestAutoLoopInEnabled
    autoloop_testcontext_test.go:318:
        	Error Trace:	/home/runner/work/loop/loop/liquidity/autoloop_testcontext_test.go:318
        	            				/home/runner/work/loop/loop/liquidity/autoloop_test.go:804
        	Error:      	Not equal:
        	            	expected: 160000
        	            	actual  : 80000
        	Test:       	TestAutoLoopInEnabled
    autoloop_testcontext_test.go:343:
        	Error Trace:	/home/runner/work/loop/loop/liquidity/autoloop_testcontext_test.go:343
        	            				/home/runner/work/loop/loop/liquidity/autoloop_test.go:804
        	Error:      	Should be true
        	Test:       	TestAutoLoopInEnabled

The root cause is them the order of items in c.quoteRequestIn depends on the
order of loopInBuilder.buildSwap calls, which depends on the order of channel
handling in Manager.SuggestSwaps, which depends on the order of map traversal,
which is not determitistic.

Since in the test all the amounts are different, I used amount as a key and
put the expected calls into a map using amount as a key. When I extract an
item from c.quoteRequestIn channel, I find the corresponding item in the map
and remove it. All other logic is preserved.
4 weeks ago
Boris Nagaev 5c88cf86b9
Merge pull request #760 from starius/sweepbatcher-rm-defaultBatchConfTarget
sweepbatcher: load from DB preserves confTarget
4 weeks ago
Boris Nagaev 87fb185a9b
sweepbatcher: remove const defaultBatchConfTarget
It is always overwritten with primary sweep's confTarget.

Print a warning if batchConfTarget is 0 in updateRbfRate.

See https://github.com/lightninglabs/loop/pull/754#discussion_r1613514363
4 weeks ago
Boris Nagaev 40ad1ce609
sweepbatcher: load from DB preserves confTarget
It used to be set to default (defaultBatchConfTarget = 12) which
could in theory affect fee rate if updateRbfRate() and publish()
were not called before the batch was saved. (Unlikely scenario.)
4 weeks ago
Boris Nagaev d38b7c55a7
sweepbatcher/StoreMock: load LoopOut from loopdb
Method sweepbatcher.Store.FetchBatchSweeps (implementation using real DB) runs
JOIN query to load LoopOut from swaps table. Now the mock does the same.

It is needed to test store and load scenarios in tests.
4 weeks ago
Boris Nagaev 22dd2e8bd1
Merge pull request #759 from starius/sweepbatcher-avoid-adding-to-two-batches
sweepbatcher: exit early in handleSweep
4 weeks ago
Boris Nagaev 4258b95dd2
sweepbatcher: fix too long lines 4 weeks ago
Boris Nagaev 0b2c177445
sweepbatcher: exit early in handleSweep
If the sweep was successfully updated in the batch, no need to
try to add it to all other batches.

Added a test reproducing adding a sweep to both batches without this change.
4 weeks ago
Boris Nagaev a135eb81f0
Merge pull request #755 from starius/sweepbatcher-changes
sweepbatcher: small refactorings
4 weeks ago
Boris Nagaev 870b60fada
sweepbatcher: fix docstring 4 weeks ago
Boris Nagaev 6def712dfe
sweepbatcher: fix typos in annotations of methods 4 weeks ago
Boris Nagaev dc5d0fe30c
sweepbatcher: narrow down interface of swapStore
Only one method of loopdb.SwapStore is used (FetchLoopOutSwap).
Local interface LoopOutFetcher was defined to reflect this.
4 weeks ago
Boris Nagaev b5b17991a5
sweepbatcher: use method AddSweep in test 4 weeks ago
Alex Bosworth 951f98147d
Merge pull request #756 from lightninglabs/update-to-v0.28.2
version: bump version to v0.28.2-beta
1 month ago
Alex Bosworth c44018dc42
version: bump version to v0.28.2-beta 1 month ago
András Bánki-Horváth 563e7be6ae
Merge pull request #754 from bhandras/sweepbatcher-empty-batch-fix
sweepbatcher: do not fail on restoring empty batches
1 month ago
Andras Banki-Horvath 14de8f1f5d
sweepbatcher: test that empty batches won't prevent startup 1 month ago
Andras Banki-Horvath e5ade6a0b1
sweepbatcher: close the quit channel when the batcher is shutting down 1 month ago
Andras Banki-Horvath c01e8014e1
sweepbatcher: do not fail on restoring empty batches
Previously storing an empty batch would make the batcher fail to start
as spinning up a restored batch assumes that there's a primary sweep
added already. As there's no point in spinning up such batch we can just
skip over it.
Furthermore we'll ensure that we won't try to ever publish an empty
batch to avoid setting the fee rate too early.
1 month ago
Andras Banki-Horvath 939c9b4ccf
loopdb+sweepbatcher: add the DropBatch call 1 month ago
Slyghtning 38f0e3a1f5
Merge pull request #753 from hieblmi/static-addr-protocol-version
staticaddr: protocol version package
1 month ago
András Bánki-Horváth e30afba364
Merge pull request #743 from bhandras/loop-out-timeout
loopout: configurable payment timeout for off-chain payments
1 month ago
Andras Banki-Horvath 01c017d913
cli: add payment_timeout option to the CLI as well 1 month ago
Andras Banki-Horvath 0d0af5bf24
loopout: use the request defined payment timeout 1 month ago
Andras Banki-Horvath 4749c029bb
loopdb: add migraton and DB code to handle payment timeout 1 month ago
Andras Banki-Horvath 461ceeeb28
looprpc: add payment_timeout to the LoopOutRequest 1 month ago
András Bánki-Horváth f26a00dd98
Merge pull request #751 from bhandras/negative-fees-fixup
loopout: fix negative reported fees
1 month ago
Andras Banki-Horvath 56902352cd
loopout: fix negative reported fees 1 month ago
Slyghtning 314feb9760
staticaddr: protocol version package 1 month ago
András Bánki-Horváth edbbc3f02f
Merge pull request #752 from bhandras/lnd-18 1 month ago
Andras Banki-Horvath 1ca2542a30
build: bump lnd to v0.18.0-beta.rc3 1 month ago
Andras Banki-Horvath 3b35ddba95
github: bump go version 1 month ago
András Bánki-Horváth 2a3c70fa62
Merge pull request #748 from bhandras/regtest-fixup
regtest: fix loopserver address
1 month ago
Andras Banki-Horvath 7f40042424
regtest: fix loopserver address 1 month ago
András Bánki-Horváth 3843c3906d
Merge pull request #741 from bhandras/fsm-observer-fixup
fsm: add WaitForStateAsync to the cached observer
2 months ago
Andras Banki-Horvath 811e9dff99
fsm: add WaitForStateAsync to the cached observer
By adding WaitForStateAsync to the observer we can always observe state
changes in an atomic way without relying on the observer's internal
cache.
2 months ago
Slyghtning 7a8c052e8c
Merge pull request #736 from hieblmi/daemon-chore
daemon: fix wrapped errors and typos
2 months ago
Slyghtning 636f8b611b
daemon: fix wrapped errors and typos 2 months ago
András Bánki-Horváth e5dd7add8a
Merge pull request #738 from lightninglabs/sweep-logging
sweepbatcher: add more debug logging
2 months ago
Andras Banki-Horvath 75d7641d74
sweepbatcher: add more debug logging 2 months ago
András Bánki-Horváth 13e8c29520
Merge pull request #730 from starius/s-lsat-l402
multi: replace occurrences of "LSAT" to "L402"
2 months ago
Boris Nagaev 5a1f79557d
loopd: re-add GetLsatTokens method in gRPC
This is needed not to break existing client binaries, e.g. `loop listauth`,
Terminal Web, RTL.

The API should be removed in a couple of releases. For now, GetLsatTokens
just prints a warning message about the API being deprecated and that the
client binary should be updated, and calls GetL402Tokens API, as a wrapper.

Type LsatToken used by GetLsatTokens in the past was renamed to L402Token,
but this does not affect binary encoding, so type L402Token can be used
(as part of TokensResponse) without breaking backward compatibility.

Updated release_notes.md.

See https://github.com/lightninglabs/loop/pull/730#discussion_r1579251294
2 months ago
Boris Nagaev 14dc8e165d
looprpc: additional_bindings for /v1/lsat/tokens
Provide backward compatibility for clients using this API endpoint.
2 months ago
Boris Nagaev 7bb04ae6ac
loopd: recorgnize maxlsatcost and maxlsatfee flags
The flags were re-added in hidden mode so that users who specified them
could start the daemon after upgrading. If a flag is used, a deprecation
warning is printed.

Updated release_notes.md.
2 months ago
Boris Nagaev 0e7927ac96
multi: replace LSAT with L402
git mv ./cmd/loop/lsat.go ./cmd/loop/l402.go
sed 's@lsat@l402@g' -i `git grep -l lsat`
sed 's@Lsat@L402@g' -i `git grep -l Lsat`
sed 's@LSAT@L402@g' -i `git grep -l LSAT`
make rpc

Updated release_notes.md.
2 months ago
Boris Nagaev bfc3f44aa1
update aperture to include lsat to l402 renaming
Fix the build:
go mod tidy
sed 's@\<lsat\>@l402@g' -i `git grep -l -w aperture/lsat`
make rpc
2 months ago
Boris Nagaev b99cde172a
go: bump protobuf to v1.33.0-hex-display 2 months ago
Boris Nagaev 368432ebb4
looprpc,swapserverrpc: update Go image to 1.21.9-bookworm
This is needed to update protobuf. Version 1.33 breaks in Go 1.16 with
the following error: "//go:build comment without // +build comment".

Distribution was updated from buster (10) to bookworm (12).
protoc was updated from v3.6.1 to v3.21.12.
2 months ago
Boris Nagaev e30cb5f2a8
multi: apply make fmt 2 months ago
Konstantin Nick cd5dc903f9
Merge pull request #731 from lightninglabs/dependabot/go_modules/golang.org/x/net-0.23.0
build(deps): bump golang.org/x/net from 0.21.0 to 0.23.0
2 months ago
dependabot[bot] b363118fed
build(deps): bump golang.org/x/net from 0.21.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2 months ago
Alex Bosworth e08d9da644
Merge pull request #728 from lightninglabs/alexbosworth-patch-2
version: bump version to v0.28.1-beta
2 months ago
Alex Bosworth 99e0c04f5f
version: bump version to v0.28.1-beta 2 months ago
Slyghtning f8ff35c0f5
Merge pull request #727 from hieblmi/send-prepay-to-selected-channel
Send loop out prepay over selected outgoing chan set
2 months ago
Slyghtning 1cea76bf01
loopout: send prepay over outgoing chan set 2 months ago
András Bánki-Horváth 54a6f157f1
Merge pull request #715 from lightninglabs/dependabot/go_modules/github.com/jackc/pgx/v4-4.18.2
build(deps): bump github.com/jackc/pgx/v4 from 4.18.1 to 4.18.2
3 months ago
András Bánki-Horváth 6ac6ee0549
Merge pull request #725 from bhandras/swap-info-fixup
loop: fill the correct HTLC in loopout update
3 months ago
Andras Banki-Horvath 06fd21fd05
loop: fill the correct HTLC in loopout update 3 months ago
Konstantin Nick c3371fead1
Merge pull request #724 from lightninglabs/dependabot/go_modules/github.com/docker/docker-24.0.9incompatible
build(deps): bump github.com/docker/docker from 24.0.7+incompatible to 24.0.9+incompatible
3 months ago
dependabot[bot] e7d0b3a472
build(deps): bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
dependabot[bot] 2326c15e45
build(deps): bump github.com/jackc/pgx/v4 from 4.18.1 to 4.18.2
Bumps [github.com/jackc/pgx/v4](https://github.com/jackc/pgx) from 4.18.1 to 4.18.2.
- [Changelog](https://github.com/jackc/pgx/blob/v4.18.2/CHANGELOG.md)
- [Commits](https://github.com/jackc/pgx/compare/v4.18.1...v4.18.2)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgx/v4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
3 months ago
András Bánki-Horváth 664ab04b57
Merge pull request #723 from bhandras/sqlite-sqlc-bump
mod+loopdb: bump sqlite and sqlc dependencies
3 months ago
Andras Banki-Horvath fb0c7f504e
loopdb: bump sqlc to 1.25.0 3 months ago
Andras Banki-Horvath d0847cf2d5
mod: bump modernc.org/sqlite to 1.29.5 3 months ago
Slyghtning bc09440c61
Merge pull request #714 from hieblmi/speed-up-docker
Speed up docker runs
4 months ago
Slyghtning 0babeee084
make: cache docker builds 4 months ago
Alex Bosworth 0d268750ac
Merge pull request #713 from lightninglabs/alexbosworth-patch-1
version: bump version to v0.28.0-beta
4 months ago
Alex Bosworth 67c59fe043
version: bump version to v0.28.0-beta 4 months ago
Konstantin Nick 5d75e99d6d
Merge pull request #708 from sputn1ck/listinstantouts
Add `listinstantouts` command
4 months ago
András Bánki-Horváth 65b8cb6036
Merge pull request #694 from bhandras/cost-cleanup
loop: fix the loopout per sweep onchain cost and cleanup server cost calculation (both loopin and loopout)
4 months ago
András Bánki-Horváth 1cd42078af
Merge pull request #712 from bhandras/db-durability
loopdb: make sqlite sync for extra durability
4 months ago
Andras Banki-Horvath 7fe4ee2f3b
loopdb: make sqlite sync for extra durability 4 months ago
Andras Banki-Horvath 5294b4ff07
loop: clean up server cost calculation for slightly better UX
Previously we'd calculate the server costs (swap fees) by accounting for
both the on-chain HTLC and the off-chain payment which was confusing as
server cost fluctuated by the amount of the swap itself. Now we'll only
add the actual cost when the swap happened, so the server cost will go
form zero to the actual fee value paid.
4 months ago
Andras Banki-Horvath e1ddb50dfe
loopout+sweepbatcher: calculate the per sweep onchain fees correctly
Previously we'd report the fees per sweep as the total sweep cost of a
batch. With this change the reported cost will be the proportional fee
which should be equal for all sweeps except if there's any rounding
difference in which case that is paid by the sweep belonging to the
first input of the batch tx.
4 months ago
Andras Banki-Horvath b4ebb19a77
loopdb+sweepbatcher: add GetParentBatch and TotalSwept calls 4 months ago
Andras Banki-Horvath c094ad4a85
sweepbatcher: refactor monitorSpendAndNotify to return an error 4 months ago
sputn1ck 034bc246ca
cmd: add listinstantouts cmd 4 months ago
sputn1ck 1f211e5647
swapclientserver: add listinstantouts 4 months ago
sputn1ck 1f96a61d21
looprpc: add listinstantouts 4 months ago
sputn1ck b3805b7cad
instantout: add listinstantout func 4 months ago
sputn1ck f3919d976c
instantout: export value and expiry 4 months ago
Konstantin Nick 010b63d75e
Merge pull request #709 from sputn1ck/instantout_custom_addr
instantout: add custom address to send funds to
4 months ago
sputn1ck 6a62be0d09
instantout: add addr to send funds to 4 months ago
Konstantin Nick c19781bd13
Merge pull request #710 from sputn1ck/instantout_ux
instantout: improve ux
4 months ago
sputn1ck 194d021824
cmd: improve instantout ux 4 months ago
sputn1ck 1be6d39677
instantout: log correct expiry 4 months ago
Slyghtning ddb52b6be4
Merge pull request #707 from hieblmi/minor-fixes
Minor clean up in utils
4 months ago
Slyghtning 5214da8822
loopd: cleanup db utils func 4 months ago

@ -20,7 +20,7 @@ env:
# If you change this value, please change it in the following files as well: # If you change this value, please change it in the following files as well:
# /Dockerfile # /Dockerfile
GO_VERSION: 1.20.4 GO_VERSION: 1.21.10
jobs: jobs:
######################## ########################

@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} golang:1.20.4-alpine as builder FROM --platform=${BUILDPLATFORM} golang:1.22-alpine as builder
# Copy in the local repository to build from. # Copy in the local repository to build from.
COPY . /go/src/github.com/lightningnetwork/loop COPY . /go/src/github.com/lightningnetwork/loop

@ -34,7 +34,11 @@ ifneq ($(workers),)
LINT_WORKERS = --concurrency=$(workers) LINT_WORKERS = --concurrency=$(workers)
endif endif
DOCKER_TOOLS = docker run -v $$(pwd):/build loop-tools DOCKER_TOOLS = docker run \
--rm \
-v $(shell bash -c "go env GOCACHE || (mkdir -p /tmp/go-cache; echo /tmp/go-cache)"):/tmp/build/.cache \
-v $(shell bash -c "go env GOMODCACHE || (mkdir -p /tmp/go-modcache; echo /tmp/go-modcache)"):/tmp/build/.modcache \
-v $$(pwd):/build loop-tools
GREEN := "\\033[0;32m" GREEN := "\\033[0;32m"
NC := "\\033[0m" NC := "\\033[0m"

@ -12,7 +12,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/swap"
@ -56,8 +56,8 @@ var (
// globalCallTimeout is the maximum time any call of the client to the // globalCallTimeout is the maximum time any call of the client to the
// server is allowed to take, including the time it may take to get // server is allowed to take, including the time it may take to get
// and pay for an LSAT token. // and pay for an L402 token.
globalCallTimeout = serverRPCTimeout + lsat.PaymentTimeout globalCallTimeout = serverRPCTimeout + l402.PaymentTimeout
// probeTimeout is the maximum time until a probe is allowed to take. // probeTimeout is the maximum time until a probe is allowed to take.
probeTimeout = 3 * time.Minute probeTimeout = 3 * time.Minute
@ -111,13 +111,13 @@ type ClientConfig struct {
// Lnd is an instance of the lnd proxy. // Lnd is an instance of the lnd proxy.
Lnd *lndclient.LndServices Lnd *lndclient.LndServices
// MaxLsatCost is the maximum price we are willing to pay to the server // MaxL402Cost is the maximum price we are willing to pay to the server
// for the token. // for the token.
MaxLsatCost btcutil.Amount MaxL402Cost btcutil.Amount
// MaxLsatFee is the maximum that we are willing to pay in routing fees // MaxL402Fee is the maximum that we are willing to pay in routing fees
// to obtain the token. // to obtain the token.
MaxLsatFee btcutil.Amount MaxL402Fee btcutil.Amount
// LoopOutMaxParts defines the maximum number of parts that may be used // LoopOutMaxParts defines the maximum number of parts that may be used
// for a loop out swap. When greater than one, a multi-part payment may // for a loop out swap. When greater than one, a multi-part payment may
@ -138,12 +138,12 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
sweeperDb sweepbatcher.BatcherStore, cfg *ClientConfig) ( sweeperDb sweepbatcher.BatcherStore, cfg *ClientConfig) (
*Client, func(), error) { *Client, func(), error) {
lsatStore, err := lsat.NewFileStore(dbDir) l402Store, err := l402.NewFileStore(dbDir)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
swapServerClient, err := newSwapServerClient(cfg, lsatStore) swapServerClient, err := newSwapServerClient(cfg, l402Store)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -153,7 +153,7 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
Server: swapServerClient, Server: swapServerClient,
Store: loopDB, Store: loopDB,
Conn: swapServerClient.conn, Conn: swapServerClient.conn,
LsatStore: lsatStore, L402Store: l402Store,
CreateExpiryTimer: func(d time.Duration) <-chan time.Time { CreateExpiryTimer: func(d time.Duration) <-chan time.Time {
return time.NewTimer(d).C return time.NewTimer(d).C
}, },
@ -177,10 +177,18 @@ func NewClient(dbDir string, loopDB loopdb.SwapStore,
return nil return nil
} }
sweepStore, err := sweepbatcher.NewSweepFetcherFromSwapStore(
loopDB, cfg.Lnd.ChainParams,
)
if err != nil {
return nil, nil, fmt.Errorf("sweepbatcher."+
"NewSweepFetcherFromSwapStore failed: %w", err)
}
batcher := sweepbatcher.NewBatcher( batcher := sweepbatcher.NewBatcher(
cfg.Lnd.WalletKit, cfg.Lnd.ChainNotifier, cfg.Lnd.Signer, cfg.Lnd.WalletKit, cfg.Lnd.ChainNotifier, cfg.Lnd.Signer,
swapServerClient.MultiMuSig2SignSweep, verifySchnorrSig, swapServerClient.MultiMuSig2SignSweep, verifySchnorrSig,
cfg.Lnd.ChainParams, sweeperDb, loopDB, cfg.Lnd.ChainParams, sweeperDb, sweepStore,
) )
executor := newExecutor(&executorConfig{ executor := newExecutor(&executorConfig{
@ -675,7 +683,8 @@ func (s *Client) LoopInQuote(ctx context.Context,
// Because the Private flag is set, we'll generate our own // Because the Private flag is set, we'll generate our own
// set of hop hints and use that // set of hop hints and use that
request.RouteHints, err = SelectHopHints( request.RouteHints, err = SelectHopHints(
ctx, s.lndServices, request.Amount, DefaultMaxHopHints, includeNodes, ctx, s.lndServices.Client, request.Amount,
DefaultMaxHopHints, includeNodes,
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -106,7 +106,7 @@ func TestLoopOutFailOffchain(t *testing.T) {
ctx.finish() ctx.finish()
} }
// TestLoopOutWrongAmount asserts that the client checks the server invoice // TestLoopOutFailWrongAmount asserts that the client checks the server invoice
// amounts. // amounts.
func TestLoopOutFailWrongAmount(t *testing.T) { func TestLoopOutFailWrongAmount(t *testing.T) {
defer test.Guard(t)() defer test.Guard(t)()

@ -25,6 +25,12 @@ var instantOutCommand = cli.Command{
Usage: "the comma-separated list of short " + Usage: "the comma-separated list of short " +
"channel IDs of the channels to loop out", "channel IDs of the channels to loop out",
}, },
cli.StringFlag{
Name: "addr",
Usage: "the optional address that the looped out funds " +
"should be sent to, if let blank the funds " +
"will go to lnd's wallet",
},
}, },
Action: instantOut, Action: instantOut,
} }
@ -83,7 +89,10 @@ func instantOut(ctx *cli.Context) error {
fmt.Printf("Available reservations: \n\n") fmt.Printf("Available reservations: \n\n")
for _, res := range confirmedReservations { for _, res := range confirmedReservations {
idx++ idx++
fmt.Printf("Reservation %v: %v \n", idx, res.Amount) fmt.Printf("Reservation %v: shortid %x, amt %v, expiry "+
"height %v \n", idx, res.ReservationId[:3], res.Amount,
res.Expiry)
totalAmt += int64(res.Amount) totalAmt += int64(res.Amount)
} }
@ -175,6 +184,7 @@ func instantOut(ctx *cli.Context) error {
&looprpc.InstantOutRequest{ &looprpc.InstantOutRequest{
ReservationIds: selectedReservations, ReservationIds: selectedReservations,
OutgoingChanSet: outgoingChanSet, OutgoingChanSet: outgoingChanSet,
DestAddr: ctx.String("addr"),
}, },
) )
@ -192,3 +202,31 @@ func instantOut(ctx *cli.Context) error {
return nil return nil
} }
var listInstantOutsCommand = cli.Command{
Name: "listinstantouts",
Usage: "list all instant out swaps",
Description: `
List all instant out swaps.
`,
Action: listInstantOuts,
}
func listInstantOuts(ctx *cli.Context) error {
// First set up the swap client itself.
client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()
resp, err := client.ListInstantOuts(
context.Background(), &looprpc.ListInstantOutsRequest{},
)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}

@ -26,8 +26,8 @@ type printableToken struct {
var listAuthCommand = cli.Command{ var listAuthCommand = cli.Command{
Name: "listauth", Name: "listauth",
Usage: "list all LSAT tokens", Usage: "list all L402 tokens",
Description: "Shows a list of all LSAT tokens that loopd has paid for", Description: "Shows a list of all L402 tokens that loopd has paid for",
Action: listAuth, Action: listAuth,
} }
@ -38,7 +38,7 @@ func listAuth(ctx *cli.Context) error {
} }
defer cleanup() defer cleanup()
resp, err := client.GetLsatTokens( resp, err := client.GetL402Tokens(
context.Background(), &looprpc.TokensRequest{}, context.Background(), &looprpc.TokensRequest{},
) )
if err != nil { if err != nil {

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -92,6 +93,14 @@ var loopOutCommand = cli.Command{
"Not setting this flag therefore might " + "Not setting this flag therefore might " +
"result in a lower swap fee", "result in a lower swap fee",
}, },
cli.DurationFlag{
Name: "payment_timeout",
Usage: "the timeout for each individual off-chain " +
"payment attempt. If not set, the default " +
"timeout of 1 hour will be used. As the " +
"payment might be retried, the actual total " +
"time may be longer",
},
forceFlag, forceFlag,
labelFlag, labelFlag,
verboseFlag, verboseFlag,
@ -235,6 +244,25 @@ func loopOut(ctx *cli.Context) error {
} }
} }
var paymentTimeout int64
if ctx.IsSet("payment_timeout") {
parsedTimeout := ctx.Duration("payment_timeout")
if parsedTimeout.Truncate(time.Second) != parsedTimeout {
return fmt.Errorf("payment timeout must be a " +
"whole number of seconds")
}
paymentTimeout = int64(parsedTimeout.Seconds())
if paymentTimeout <= 0 {
return fmt.Errorf("payment timeout must be a " +
"positive value")
}
if paymentTimeout > math.MaxUint32 {
return fmt.Errorf("payment timeout is too large")
}
}
resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{ resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{
Amt: int64(amt), Amt: int64(amt),
Dest: destAddr, Dest: destAddr,
@ -252,6 +280,7 @@ func loopOut(ctx *cli.Context) error {
SwapPublicationDeadline: uint64(swapDeadline.Unix()), SwapPublicationDeadline: uint64(swapDeadline.Unix()),
Label: label, Label: label,
Initiator: defaultInitiator, Initiator: defaultInitiator,
PaymentTimeout: uint32(paymentTimeout),
}) })
if err != nil { if err != nil {
return err return err

@ -148,7 +148,7 @@ func main() {
listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand,
setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand,
getInfoCommand, abandonSwapCommand, reservationsCommands, getInfoCommand, abandonSwapCommand, reservationsCommands,
instantOutCommand, instantOutCommand, listInstantOutsCommand,
} }
err := app.Run(os.Args) err := app.Run(os.Args)

@ -3,7 +3,7 @@ package loop
import ( import (
"time" "time"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -15,7 +15,7 @@ type clientConfig struct {
Server swapServerClient Server swapServerClient
Conn *grpc.ClientConn Conn *grpc.ClientConn
Store loopdb.SwapStore Store loopdb.SwapStore
LsatStore lsat.Store L402Store l402.Store
CreateExpiryTimer func(expiry time.Duration) <-chan time.Time CreateExpiryTimer func(expiry time.Duration) <-chan time.Time
LoopOutMaxParts uint32 LoopOutMaxParts uint32
} }

@ -0,0 +1,204 @@
package loop
import (
"context"
"fmt"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// costMigrationID is the identifier for the cost migration.
costMigrationID = "cost_migration"
// paymentBatchSize is the maximum number of payments we'll fetch in
// one go.
paymentBatchSize = 1000
)
// CalculateLoopOutCost calculates the total cost of a loop out swap. It will
// correctly account for the on-chain and off-chain fees that were paid and
// make sure that all costs are positive.
func CalculateLoopOutCost(params *chaincfg.Params, loopOutSwap *loopdb.LoopOut,
paymentFees map[lntypes.Hash]lnwire.MilliSatoshi) (loopdb.SwapCost,
error) {
// First make sure that this swap is actually finished.
if loopOutSwap.State().State.IsPending() {
return loopdb.SwapCost{}, fmt.Errorf("swap is not yet finished")
}
// We first need to decode the prepay invoice to get the prepay hash and
// the prepay amount.
_, _, hash, prepayAmount, err := swap.DecodeInvoice(
params, loopOutSwap.Contract.PrepayInvoice,
)
if err != nil {
return loopdb.SwapCost{}, fmt.Errorf("unable to decode the "+
"prepay invoice: %v", err)
}
// The swap hash is given and we don't need to get it from the
// swap invoice, however we'll decode it anyway to get the invoice amount
// that was paid in case we don't have the payment anymore.
_, _, swapHash, swapPaymentAmount, err := swap.DecodeInvoice(
params, loopOutSwap.Contract.SwapInvoice,
)
if err != nil {
return loopdb.SwapCost{}, fmt.Errorf("unable to decode the "+
"swap invoice: %v", err)
}
var (
cost loopdb.SwapCost
swapPaid, prepayPaid bool
)
// Now that we have the prepay and swap amount, we can calculate the
// total cost of the swap. Note that we only need to account for the
// server cost in case the swap was successful or if the sweep timed
// out. Otherwise the server didn't pull the off-chain htlc nor the
// prepay.
switch loopOutSwap.State().State {
case loopdb.StateSuccess:
cost.Server = swapPaymentAmount + prepayAmount -
loopOutSwap.Contract.AmountRequested
swapPaid = true
prepayPaid = true
case loopdb.StateFailSweepTimeout:
cost.Server = prepayAmount
prepayPaid = true
default:
cost.Server = 0
}
// Now attempt to look up the actual payments so we can calculate the
// total routing costs.
prepayPaymentFee, ok := paymentFees[hash]
if prepayPaid && ok {
cost.Offchain += prepayPaymentFee.ToSatoshis()
} else {
log.Debugf("Prepay payment %s is missing, won't account for "+
"routing fees", hash)
}
swapPaymentFee, ok := paymentFees[swapHash]
if swapPaid && ok {
cost.Offchain += swapPaymentFee.ToSatoshis()
} else {
log.Debugf("Swap payment %s is missing, won't account for "+
"routing fees", swapHash)
}
// For the on-chain cost, just make sure that the cost is positive.
cost.Onchain = loopOutSwap.State().Cost.Onchain
if cost.Onchain < 0 {
cost.Onchain *= -1
}
return cost, nil
}
// MigrateLoopOutCosts will calculate the correct cost for all loop out swaps
// and override the cost values of the last update in the database.
func MigrateLoopOutCosts(ctx context.Context, lnd lndclient.LndServices,
db loopdb.SwapStore) error {
migrationDone, err := db.HasMigration(ctx, costMigrationID)
if err != nil {
return err
}
if migrationDone {
log.Infof("Cost cleanup migration already done, skipping")
return nil
}
log.Infof("Starting cost cleanup migration")
startTs := time.Now()
defer func() {
log.Infof("Finished cost cleanup migration in %v",
time.Since(startTs))
}()
// First we'll fetch all loop out swaps from the database.
loopOutSwaps, err := db.FetchLoopOutSwaps(ctx)
if err != nil {
return err
}
// Gather payment fees to a map for easier lookup.
paymentFees := make(map[lntypes.Hash]lnwire.MilliSatoshi)
offset := uint64(0)
for {
payments, err := lnd.Client.ListPayments(
ctx, lndclient.ListPaymentsRequest{
Offset: offset,
MaxPayments: paymentBatchSize,
},
)
if err != nil {
return err
}
if len(payments.Payments) == 0 {
break
}
for _, payment := range payments.Payments {
paymentFees[payment.Hash] = payment.Fee
}
offset = payments.LastIndexOffset + 1
}
// Now we'll calculate the cost for each swap and finally update the
// costs in the database.
updatedCosts := make(map[lntypes.Hash]loopdb.SwapCost)
for _, loopOutSwap := range loopOutSwaps {
if loopOutSwap.State().State.IsPending() {
continue
}
cost, err := CalculateLoopOutCost(
lnd.ChainParams, loopOutSwap, paymentFees,
)
if err != nil {
// We don't want to fail loopd because of any old swap
// that we're unable to calculate the cost for. We'll
// warn though so that we can investigate further.
log.Warnf("Unable to calculate cost for swap %v: %v",
loopOutSwap.Hash, err)
continue
}
_, ok := updatedCosts[loopOutSwap.Hash]
if ok {
return fmt.Errorf("found a duplicate swap %v while "+
"updating costs", loopOutSwap.Hash)
}
updatedCosts[loopOutSwap.Hash] = cost
}
log.Infof("Updating costs for %d loop out swaps", len(updatedCosts))
err = db.BatchUpdateLoopOutSwapCosts(ctx, updatedCosts)
if err != nil {
return err
}
// Finally mark the migration as done.
return db.SetMigration(ctx, costMigrationID)
}

@ -0,0 +1,184 @@
package loop
import (
"context"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// TestCalculateLoopOutCost tests the CalculateLoopOutCost function.
func TestCalculateLoopOutCost(t *testing.T) {
// Set up test context objects.
lnd := test.NewMockLnd()
server := newServerMock(lnd)
store := loopdb.NewStoreMock(t)
cfg := &swapConfig{
lnd: &lnd.LndServices,
store: store,
server: server,
}
height := int32(600)
req := *testRequest
initResult, err := newLoopOutSwap(
context.Background(), cfg, height, &req,
)
require.NoError(t, err)
swap, err := store.FetchLoopOutSwap(
context.Background(), initResult.swap.hash,
)
require.NoError(t, err)
// Override the chain cost so it's negative.
const expectedChainCost = btcutil.Amount(1000)
// Now we have the swap and prepay invoices so let's calculate the
// costs without providing the payments first, so we don't account for
// any routing fees.
paymentFees := make(map[lntypes.Hash]lnwire.MilliSatoshi)
_, err = CalculateLoopOutCost(lnd.ChainParams, swap, paymentFees)
// We expect that the call fails as the swap isn't finished yet.
require.Error(t, err)
// Override the swap state to make it look like the swap is finished
// and make the chain cost negative too, so we can test that it'll be
// corrected to be positive in the cost calculation.
swap.Events = append(
swap.Events, &loopdb.LoopEvent{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateSuccess,
Cost: loopdb.SwapCost{
Onchain: -expectedChainCost,
},
},
},
)
costs, err := CalculateLoopOutCost(lnd.ChainParams, swap, paymentFees)
require.NoError(t, err)
expectedServerCost := server.swapInvoiceAmt + server.prepayInvoiceAmt -
swap.Contract.AmountRequested
require.Equal(t, expectedServerCost, costs.Server)
require.Equal(t, btcutil.Amount(0), costs.Offchain)
require.Equal(t, expectedChainCost, costs.Onchain)
// Now add the two payments to the payments map and calculate the costs
// again. We expect that the routng fees are now accounted for.
paymentFees[server.swapHash] = lnwire.NewMSatFromSatoshis(44)
paymentFees[server.prepayHash] = lnwire.NewMSatFromSatoshis(11)
costs, err = CalculateLoopOutCost(lnd.ChainParams, swap, paymentFees)
require.NoError(t, err)
expectedOffchainCost := btcutil.Amount(44 + 11)
require.Equal(t, expectedServerCost, costs.Server)
require.Equal(t, expectedOffchainCost, costs.Offchain)
require.Equal(t, expectedChainCost, costs.Onchain)
// Now override the last update to make the swap timed out at the HTLC
// sweep. We expect that the chain cost won't change, and only the
// prepay will be accounted for.
swap.Events[0] = &loopdb.LoopEvent{
SwapStateData: loopdb.SwapStateData{
State: loopdb.StateFailSweepTimeout,
Cost: loopdb.SwapCost{
Onchain: 0,
},
},
}
costs, err = CalculateLoopOutCost(lnd.ChainParams, swap, paymentFees)
require.NoError(t, err)
expectedServerCost = server.prepayInvoiceAmt
expectedOffchainCost = btcutil.Amount(11)
require.Equal(t, expectedServerCost, costs.Server)
require.Equal(t, expectedOffchainCost, costs.Offchain)
require.Equal(t, btcutil.Amount(0), costs.Onchain)
}
// TestCostMigration tests the cost migration for loop out swaps.
func TestCostMigration(t *testing.T) {
// Set up test context objects.
lnd := test.NewMockLnd()
server := newServerMock(lnd)
store := loopdb.NewStoreMock(t)
cfg := &swapConfig{
lnd: &lnd.LndServices,
store: store,
server: server,
}
height := int32(600)
req := *testRequest
initResult, err := newLoopOutSwap(
context.Background(), cfg, height, &req,
)
require.NoError(t, err)
// Override the chain cost so it's negative.
const expectedChainCost = btcutil.Amount(1000)
// Override the swap state to make it look like the swap is finished
// and make the chain cost negative too, so we can test that it'll be
// corrected to be positive in the cost calculation.
err = store.UpdateLoopOut(
context.Background(), initResult.swap.hash, time.Now(),
loopdb.SwapStateData{
State: loopdb.StateSuccess,
Cost: loopdb.SwapCost{
Onchain: -expectedChainCost,
},
},
)
require.NoError(t, err)
// Add the two mocked payment to LND. Note that we only care about the
// fees here, so we don't need to provide the full payment details.
lnd.Payments = []lndclient.Payment{
{
Hash: server.swapHash,
Fee: lnwire.NewMSatFromSatoshis(44),
},
{
Hash: server.prepayHash,
Fee: lnwire.NewMSatFromSatoshis(11),
},
}
// Now we can run the migration.
err = MigrateLoopOutCosts(context.Background(), lnd.LndServices, store)
require.NoError(t, err)
// Finally check that the swap cost has been updated correctly.
swap, err := store.FetchLoopOutSwap(
context.Background(), initResult.swap.hash,
)
require.NoError(t, err)
expectedServerCost := server.swapInvoiceAmt + server.prepayInvoiceAmt -
swap.Contract.AmountRequested
costs := swap.Events[0].Cost
expectedOffchainCost := btcutil.Amount(44 + 11)
require.Equal(t, expectedServerCost, costs.Server)
require.Equal(t, expectedOffchainCost, costs.Offchain)
require.Equal(t, expectedChainCost, costs.Onchain)
// Now run the migration again to make sure it doesn't fail. This also
// indicates that the migration did not run the second time as
// otherwise the store mocks SetMigration function would fail.
err = MigrateLoopOutCosts(context.Background(), lnd.LndServices, store)
require.NoError(t, err)
}

@ -243,3 +243,82 @@ func TestExampleFSMFlow(t *testing.T) {
}) })
} }
} }
// TestObserverAsyncWait tests the observer's WaitForStateAsync function.
func TestObserverAsyncWait(t *testing.T) {
testCases := []struct {
name string
waitTime time.Duration
blockTime time.Duration
expectTimeout bool
}{
{
name: "success",
waitTime: time.Second,
blockTime: time.Millisecond,
expectTimeout: false,
},
{
name: "timeout",
waitTime: time.Millisecond,
blockTime: time.Second,
expectTimeout: true,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
service := &mockService{
respondChan: make(chan bool),
}
store := &mockStore{}
exampleContext := NewExampleFSMContext(service, store)
cachedObserver := NewCachedObserver(100)
exampleContext.RegisterObserver(cachedObserver)
t0 := time.Now()
timeoutCtx, cancel := context.WithTimeout(
context.Background(), tc.waitTime,
)
defer cancel()
// Wait for the final state.
errChan := cachedObserver.WaitForStateAsync(
timeoutCtx, StuffSuccess, true,
)
go func() {
err := exampleContext.SendEvent(
OnRequestStuff,
newInitStuffRequest(),
)
require.NoError(t, err)
time.Sleep(tc.blockTime)
service.respondChan <- true
}()
timeout := false
select {
case <-timeoutCtx.Done():
timeout = true
case <-errChan:
}
require.Equal(t, tc.expectTimeout, timeout)
t1 := time.Now()
diff := t1.Sub(t0)
if tc.expectTimeout {
require.Less(t, diff, tc.blockTime)
} else {
require.Less(t, diff, tc.waitTime)
}
})
}
}

@ -306,23 +306,36 @@ func NoOpAction(_ EventContext) EventType {
} }
// ErrConfigError is an error returned when the state machine is misconfigured. // ErrConfigError is an error returned when the state machine is misconfigured.
type ErrConfigError error type ErrConfigError struct {
msg string
}
// Error returns the error message.
func (e ErrConfigError) Error() string {
return fmt.Sprintf("config error: %s", e.msg)
}
// NewErrConfigError creates a new ErrConfigError. // NewErrConfigError creates a new ErrConfigError.
func NewErrConfigError(msg string) ErrConfigError { func NewErrConfigError(msg string) ErrConfigError {
return (ErrConfigError)(fmt.Errorf("config error: %s", msg)) return ErrConfigError{
msg: msg,
}
} }
// ErrWaitingForStateTimeout is an error returned when the state machine times // ErrWaitingForStateTimeout is an error returned when the state machine times
// out while waiting for a state. // out while waiting for a state.
type ErrWaitingForStateTimeout error type ErrWaitingForStateTimeout struct {
expected StateType
}
// NewErrWaitingForStateTimeout creates a new ErrWaitingForStateTimeout. // Error returns the error message.
func NewErrWaitingForStateTimeout(expected, func (e ErrWaitingForStateTimeout) Error() string {
actual StateType) ErrWaitingForStateTimeout { return fmt.Sprintf("waiting for state timed out: %s", e.expected)
}
return (ErrWaitingForStateTimeout)(fmt.Errorf( // NewErrWaitingForStateTimeout creates a new ErrWaitingForStateTimeout.
"waiting for state timeout: expected %s, actual: %s", func NewErrWaitingForStateTimeout(expected StateType) ErrWaitingForStateTimeout {
expected, actual, return ErrWaitingForStateTimeout{
)) expected: expected,
}
} }

@ -100,12 +100,11 @@ func WithAbortEarlyOnErrorOption() WaitForStateOption {
// the given duration before checking the state. This is useful if the // the given duration before checking the state. This is useful if the
// function is called immediately after sending an event to the state machine // function is called immediately after sending an event to the state machine
// and the state machine needs some time to process the event. // and the state machine needs some time to process the event.
func (s *CachedObserver) WaitForState(ctx context.Context, func (c *CachedObserver) WaitForState(ctx context.Context,
timeout time.Duration, state StateType, timeout time.Duration, state StateType,
opts ...WaitForStateOption) error { opts ...WaitForStateOption) error {
var options fsmOptions var options fsmOptions
for _, opt := range opts { for _, opt := range opts {
opt.apply(&options) opt.apply(&options)
} }
@ -120,61 +119,77 @@ func (s *CachedObserver) WaitForState(ctx context.Context,
} }
} }
// Create a new context with a timeout.
timeoutCtx, cancel := context.WithTimeout(ctx, timeout) timeoutCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
// Channel to notify when the desired state is reached ch := c.WaitForStateAsync(timeoutCtx, state, options.abortEarlyOnError)
// or an error occurred.
ch := make(chan error)
// Goroutine to wait on condition variable // Wait for either the condition to be met or for a timeout.
go func() { select {
s.notificationMx.Lock() case <-timeoutCtx.Done():
defer s.notificationMx.Unlock() return NewErrWaitingForStateTimeout(state)
for { case err := <-ch:
// Check if the last state is the desired state return err
if s.lastNotification.NextState == state { }
select { }
case <-timeoutCtx.Done():
return
case ch <- nil: // WaitForStateAsync waits asynchronously until the passed context is canceled
return // or the expected state is reached. The function returns a channel that will
} // receive an error if the expected state is reached or an error occurred. If
// the context is canceled before the expected state is reached, the channel
// will receive an ErrWaitingForStateTimeout error.
func (c *CachedObserver) WaitForStateAsync(ctx context.Context, state StateType,
abortOnEarlyError bool) chan error {
// Channel to notify when the desired state is reached or an error
// occurred.
ch := make(chan error, 1)
// Wait on the notification condition variable asynchronously to avoid
// blocking the caller.
go func() {
c.notificationMx.Lock()
defer c.notificationMx.Unlock()
// writeResult writes the result to the channel. If the context
// is canceled, an ErrWaitingForStateTimeout error is written
// to the channel.
writeResult := func(err error) {
select {
case <-ctx.Done():
ch <- NewErrWaitingForStateTimeout(
state,
)
case ch <- err:
} }
}
// Check if an error occurred for {
if s.lastNotification.Event == OnError { // Check if the last state is the desired state.
if options.abortEarlyOnError { if c.lastNotification.NextState == state {
select { writeResult(nil)
case <-timeoutCtx.Done(): return
return }
case ch <- s.lastNotification.LastActionError: // Check if an error has occurred.
return if c.lastNotification.Event == OnError {
} lastErr := c.lastNotification.LastActionError
if abortOnEarlyError {
writeResult(lastErr)
return
} }
} }
// Otherwise, wait for the next notification // Otherwise use the conditional variable to wait for
s.notificationCond.Wait() // the next notification.
c.notificationCond.Wait()
} }
}() }()
// Wait for either the condition to be met or for a timeout return ch
select {
case <-timeoutCtx.Done():
return NewErrWaitingForStateTimeout(
state, s.lastNotification.NextState,
)
case lastActionErr := <-ch:
if lastActionErr != nil {
return lastActionErr
}
return nil
}
} }
// FixedSizeSlice is a slice with a fixed size. // FixedSizeSlice is a slice with a fixed size.

136
go.mod

@ -1,42 +1,42 @@
module github.com/lightninglabs/loop module github.com/lightninglabs/loop
require ( require (
github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5 github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240403021926-ae5533602c46
github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcec/v2 v2.3.3
github.com/btcsuite/btcd/btcutil v1.1.5 github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/btcutil/psbt v1.1.8 github.com/btcsuite/btcd/btcutil/psbt v1.1.8
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf github.com/btcsuite/btcwallet v0.16.10-0.20240404104514-b2f31f9045fb
github.com/btcsuite/btcwallet/wtxmgr v1.5.0 github.com/btcsuite/btcwallet/wtxmgr v1.5.3
github.com/coreos/bbolt v1.3.3 github.com/coreos/bbolt v1.3.3
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/fortytw2/leaktest v1.3.0 github.com/fortytw2/leaktest v1.3.0
github.com/golang-migrate/migrate/v4 v4.16.1 github.com/golang-migrate/migrate/v4 v4.17.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3 github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3
github.com/jackc/pgconn v1.14.0 github.com/jackc/pgconn v1.14.3
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
github.com/jessevdk/go-flags v1.4.0 github.com/jessevdk/go-flags v1.4.0
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.9
github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030 github.com/lightninglabs/aperture v0.3.2-beta
github.com/lightninglabs/lndclient v0.17.4-1 github.com/lightninglabs/lndclient v0.18.0-1
github.com/lightninglabs/loop/swapserverrpc v1.0.5 github.com/lightninglabs/loop/swapserverrpc v1.0.5
github.com/lightningnetwork/lnd v0.17.4-beta github.com/lightningnetwork/lnd v0.18.0-beta.1
github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1 github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/queue v1.1.1 github.com/lightningnetwork/lnd/queue v1.1.1
github.com/lightningnetwork/lnd/ticker v1.1.1 github.com/lightningnetwork/lnd/ticker v1.1.1
github.com/lightningnetwork/lnd/tor v1.1.2 github.com/lightningnetwork/lnd/tor v1.1.2
github.com/ory/dockertest/v3 v3.10.0 github.com/ory/dockertest/v3 v3.10.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
github.com/urfave/cli v1.22.9 github.com/urfave/cli v1.22.9
golang.org/x/net v0.17.0 golang.org/x/net v0.23.0
google.golang.org/grpc v1.59.0 google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.33.0
gopkg.in/macaroon-bakery.v2 v2.1.0 gopkg.in/macaroon-bakery.v2 v2.1.0
gopkg.in/macaroon.v2 v2.1.0 gopkg.in/macaroon.v2 v2.1.0
modernc.org/sqlite v1.20.3 modernc.org/sqlite v1.29.8
) )
require ( require (
@ -48,12 +48,11 @@ require (
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aead/siphash v1.0.1 // indirect github.com/aead/siphash v1.0.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 // indirect
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 // indirect
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 // indirect
github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect github.com/btcsuite/btcwallet/walletdb v1.4.2 // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/btcsuite/winsvc v1.0.0 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect
@ -64,88 +63,84 @@ require (
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/lru v1.0.0 // indirect github.com/decred/dcrd/lru v1.1.2 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/fergusstrange/embedded-postgres v1.25.0 // indirect
github.com/fergusstrange/embedded-postgres v1.10.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.1 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/imdario/mergo v0.3.13 // indirect github.com/imdario/mergo v0.3.13 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.1 // indirect github.com/jackc/pgx/v4 v4.18.2 // indirect
github.com/jackpal/gateway v1.0.5 // indirect github.com/jackpal/gateway v1.0.5 // indirect
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad // indirect github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/jrick/logrotate v1.0.0 // indirect github.com/jrick/logrotate v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kkdai/bstream v1.0.0 // indirect github.com/kkdai/bstream v1.0.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
github.com/lightninglabs/neutrino v0.16.0 // indirect github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd // indirect
github.com/lightninglabs/neutrino/cache v1.1.1 // indirect github.com/lightninglabs/neutrino/cache v1.1.2 // indirect
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f // indirect
github.com/lightningnetwork/lnd/healthcheck v1.2.3 // indirect github.com/lightningnetwork/lnd/fn v1.0.5 // indirect
github.com/lightningnetwork/lnd/kvdb v1.4.4 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.4 // indirect
github.com/lightningnetwork/lnd/tlv v1.1.1 // indirect github.com/lightningnetwork/lnd/kvdb v1.4.8 // indirect
github.com/lightningnetwork/lnd/sqldb v1.0.2 // indirect
github.com/lightningnetwork/lnd/tlv v1.2.3 // indirect
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mholt/archiver/v3 v3.5.0 // indirect
github.com/miekg/dns v1.1.43 // indirect github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/moby/term v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nwaples/rardecode v1.1.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.12 // indirect github.com/opencontainers/runc v1.1.12 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_golang v1.11.1 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.2 // indirect github.com/sirupsen/logrus v1.9.2 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 // indirect github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
@ -160,50 +155,47 @@ require (
go.etcd.io/etcd/pkg/v3 v3.5.7 // indirect go.etcd.io/etcd/pkg/v3 v3.5.7 // indirect
go.etcd.io/etcd/raft/v3 v3.5.7 // indirect go.etcd.io/etcd/raft/v3 v3.5.7 // indirect
go.etcd.io/etcd/server/v3 v3.5.7 // indirect go.etcd.io/etcd/server/v3 v3.5.7 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
go.opentelemetry.io/otel v1.20.0 // indirect go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 // indirect
go.opentelemetry.io/otel/metric v1.20.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/sdk v1.3.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.16.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.15.0 // indirect golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.9.1 // indirect golang.org/x/tools v0.19.0 // indirect
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/libc v1.49.3 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/libc v1.22.2 // indirect modernc.org/memory v1.8.0 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/memory v1.4.0 // indirect modernc.org/token v1.1.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect sigs.k8s.io/yaml v1.2.0 // indirect
) )
// We want to format raw bytes as hex instead of base64. The forked version // We want to format raw bytes as hex instead of base64. The forked version
// allows us to specify that as an option. // allows us to specify that as an option.
replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display
replace github.com/lightninglabs/loop/swapserverrpc => ./swapserverrpc replace github.com/lightninglabs/loop/swapserverrpc => ./swapserverrpc
go 1.19 go 1.22.3

334
go.sum

@ -36,7 +36,7 @@ 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.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= 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.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= 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.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@ -173,7 +173,8 @@ 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.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.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.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= 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.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.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@ -606,6 +607,7 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
@ -630,8 +632,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
@ -645,19 +645,15 @@ github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g=
github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5 h1:8BHBWvtP6kkzvmCpyWEznq4eS0gfLOSVuXLesv413Xs= github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240403021926-ae5533602c46 h1:tjpNTdZNQqE14menwDGAxWfzN0DFHVTXFEyEL8yvA/4=
github.com/btcsuite/btcd v0.24.1-0.20240123000108-62e6af035ec5/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240403021926-ae5533602c46/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0=
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34=
github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8=
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg=
@ -669,20 +665,18 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf h1:eNjj5R0tKP48NQxDkuKr+C9frZsdzTAemEwu75ZDQg0= github.com/btcsuite/btcwallet v0.16.10-0.20240404104514-b2f31f9045fb h1:qoIOlBPRZWtfpcbQlNFf67Wz8ZlXo+mxQc9Pnbm/iqU=
github.com/btcsuite/btcwallet v0.16.10-0.20240127010340-16b422a2e8bf/go.mod h1:LzcW/LYkQLgDufv6Ouw4cOIW0YsY+A60MTtc61/OZTU= github.com/btcsuite/btcwallet v0.16.10-0.20240404104514-b2f31f9045fb/go.mod h1:2C3Q/MhYAKmk7F+Tey6LfKtKRTdQsrCf8AAAzzDPmH4=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 h1:poyHFf7+5+RdxNp5r2T6IBRD7RyraUsYARYbp/7t4D8=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0= github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4/go.mod h1:GETGDQuyq+VFfH1S/+/7slLM/9aNa4l7P4ejX6dJfb0=
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 h1:UZo7YRzdHbwhK7Rhv3PO9bXgTxiOH45edK5qdsdiatk=
github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= github.com/btcsuite/btcwallet/wallet/txrules v1.2.1/go.mod h1:MVSqRkju/IGxImXYPfBkG65FgEZYA4fXchheILMVl8g=
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.2/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4 h1:nmcKAVTv/cmYrs0A4hbiC6Qw+WTLYy/14SmTt3mLnCo=
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 h1:PszOub7iXVYbtGybym5TGCp9Dv1h1iX4rIC3HICZGLg= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.4/go.mod h1:YqJR8WAAHiKIPesZTr9Cx9Az4fRhRLcJ6GcxzRUZCAc=
github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= github.com/btcsuite/btcwallet/walletdb v1.4.2 h1:zwZZ+zaHo4mK+FAN6KeK85S3oOm+92x2avsHvFAhVBE=
github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/walletdb v1.4.2/go.mod h1:7ZQ+BvOEre90YT7eSq8bLoxTsgXidUzA/mqbRS114CQ=
github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= github.com/btcsuite/btcwallet/wtxmgr v1.5.3 h1:QrWCio9Leh3DwkWfp+A1SURj8pYn3JuTLv3waP5uEro=
github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= github.com/btcsuite/btcwallet/wtxmgr v1.5.3/go.mod h1:M4nQpxGTXiDlSOODKXboXX7NFthmiBNjzAKKNS7Fhjg=
github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2cL8vl6av4SU=
github.com/btcsuite/btcwallet/wtxmgr v1.5.0/go.mod h1:TQVDhFxseiGtZwEPvLgtfyxuNUDsIdaJdshvWzR0HJ4=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
@ -701,6 +695,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
@ -721,13 +716,17 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/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-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY=
github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 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/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5 h1:xD/lrqdvwsc+O2bjSSi3YqY73Ke3LAiSCx49aCesA0E= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5 h1:xD/lrqdvwsc+O2bjSSi3YqY73Ke3LAiSCx49aCesA0E=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs= github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcKDqs=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
@ -745,32 +744,36 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY=
github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8=
github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78=
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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.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/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -787,8 +790,9 @@ github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= 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 v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c= github.com/fergusstrange/embedded-postgres v1.25.0 h1:sa+k2Ycrtz40eCRPOzI7Ry7TtkWXXJ+YRsxpKMDhxK0=
github.com/fergusstrange/embedded-postgres v1.25.0/go.mod h1:t/MLs0h9ukYM6FSt99R7InCHs1nW0ordoVCcnzmpTYw=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -796,10 +800,13 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@ -830,23 +837,26 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-migrate/migrate/v4 v4.16.1 h1:O+0C55RbMN66pWm5MjO6mw0px6usGpY0+bkSGW9zCo0= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
github.com/golang-migrate/migrate/v4 v4.16.1/go.mod h1:qXiwa/3Zeqaltm1MxOCZDYysW/F6folYiBgBG03l9hc= github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -872,9 +882,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -900,6 +910,7 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -921,14 +932,15 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
@ -965,6 +977,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -981,10 +995,10 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
@ -1000,8 +1014,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
@ -1015,12 +1029,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc= github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc=
@ -1042,20 +1055,27 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk= github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk=
github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a/go.mod h1:GZ/FY8Cqw3KHG6DwRVPUKbSPTAwyrU28xFi5cqZnLsc=
github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a h1:d7eZO8OS/ZXxdP0uq3E8CdoA1qNFaecAv90UxrxaY2k= github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a h1:d7eZO8OS/ZXxdP0uq3E8CdoA1qNFaecAv90UxrxaY2k=
github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a/go.mod h1:JWeZdyttIEbkR51z2S13+J+aCuHVe0F6meRy+P0YGDo=
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY= github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY=
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls=
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY= github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY=
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg= github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg=
github.com/juju/mgo/v2 v2.0.0-20220111072304-f200228f1090 h1:zX5GoH3Jp8k1EjUFkApu/YZAYEn0PYQfg/U6IDyNyYs= github.com/juju/mgo/v2 v2.0.0-20220111072304-f200228f1090 h1:zX5GoH3Jp8k1EjUFkApu/YZAYEn0PYQfg/U6IDyNyYs=
github.com/juju/mgo/v2 v2.0.0-20220111072304-f200228f1090/go.mod h1:N614SE0a4e+ih2rg96Vi2PeC3cTpUOWgCTv3Cgk974c=
github.com/juju/retry v0.0.0-20220204093819-62423bf33287 h1:U+7oMWEglXfiikIppNexButZRwKPlzLBGKYSNCXzXf8= github.com/juju/retry v0.0.0-20220204093819-62423bf33287 h1:U+7oMWEglXfiikIppNexButZRwKPlzLBGKYSNCXzXf8=
github.com/juju/retry v0.0.0-20220204093819-62423bf33287/go.mod h1:SssN1eYeK3A2qjnFGTiVMbdzGJ2BfluaJblJXvuvgqA=
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM= github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM=
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494/go.mod h1:rUquetT0ALL48LHZhyRGvjjBH8xZaZ8dFClulKK5wK4=
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0 h1:bn+2Adl1yWqYjm3KSFlFqsvfLg2eq+XNL7GGMYApdVw= github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0 h1:bn+2Adl1yWqYjm3KSFlFqsvfLg2eq+XNL7GGMYApdVw=
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0/go.mod h1:8csUcj1VRkfjNIRzBFWzLFCMLwLqsRWvkmhfVAUwbC4=
github.com/juju/version/v2 v2.0.0-20220204124744-fc9915e3d935 h1:6YoyzXVW1XkqN86y2s/rz365Jm7EiAy39v2G5ikzvHU= github.com/juju/version/v2 v2.0.0-20220204124744-fc9915e3d935 h1:6YoyzXVW1XkqN86y2s/rz365Jm7EiAy39v2G5ikzvHU=
github.com/juju/version/v2 v2.0.0-20220204124744-fc9915e3d935/go.mod h1:ZeFjNy+UFEWJDDPdzW7Cm9NeU6dsViGaFYhXzycLQrw=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -1063,15 +1083,8 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -1081,6 +1094,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -1089,41 +1103,43 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030 h1:q/BBO2awQdy/dCILXXZbBsstQ+1DpSQ/c8B8EgKqg+g= github.com/lightninglabs/aperture v0.3.2-beta h1:J2GQwBmSHxpr5VOatXbgrTogF/qN2l6UWLPHfIowq10=
github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030/go.mod h1:Jvoen+fgoaGQZIHdchiGigu0Lwuwz8S5u5wad9IhVDU= github.com/lightninglabs/aperture v0.3.2-beta/go.mod h1:M/5dPzHjHvuYXQuxzicqaGiCclHUvKW6N0ay1t/HGiM=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= 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/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/lndclient v0.17.4-1 h1:uCLBYf1f1nOoagHuiPK9anERA86dNSlYK9/QGb410RQ= github.com/lightninglabs/lndclient v0.18.0-1 h1:b9ur24NTbNRUOfotkhio6SAlkvXADLz9k7QLIlLYpSk=
github.com/lightninglabs/lndclient v0.17.4-1/go.mod h1:2krqTDgp3W3DLSDx9bYaT0MDrMVslGMXETViKE8J1pk= github.com/lightninglabs/lndclient v0.18.0-1/go.mod h1:GBIttLpj+W82XrZrFvQ1gpQH074aTcwisP/zvdGbqE4=
github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s=
github.com/lightninglabs/neutrino v0.16.0/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk=
github.com/lightninglabs/neutrino/cache v1.1.1 h1:TllWOSlkABhpgbWJfzsrdUaDH2fBy/54VSIB4vVqV8M= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
github.com/lightninglabs/neutrino/cache v1.1.1/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY= github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display h1:Y2WiPkBS/00EiEg0qp0FhehxnQfk3vv8U6Xt3nN+rTY=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= github.com/lightninglabs/protobuf-go-hex-display v1.33.0-hex-display/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lnd v0.17.4-beta h1:BXYbETYZWtcNrYcAosGGXnWsq4Nr5R9PRqlRuEA9AUs= github.com/lightningnetwork/lnd v0.18.0-beta.1 h1:7DpRre4rtUmLim4JC5oPd3KEd1Q3QpWTH6jQgSOGNYM=
github.com/lightningnetwork/lnd v0.17.4-beta/go.mod h1:S5hugoB/FWyF9Up9sjEnOsA/ohmhXzIqRHHMLlrtyFk= github.com/lightningnetwork/lnd v0.18.0-beta.1/go.mod h1:1SA9iv9rZddNAcfP38SN9lNSVT1zf5aqmukLUoomjDU=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI= github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U= github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0= github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ= github.com/lightningnetwork/lnd/clock v1.1.1/go.mod h1:mGnAhPyjYZQJmebS7aevElXKTFDuO+uNFFfMXK1W8xQ=
github.com/lightningnetwork/lnd/healthcheck v1.2.3 h1:oqhOOy8WmIEa6RBkYKC0mmYZkhl8T2kGD97n9jpML8o= github.com/lightningnetwork/lnd/fn v1.0.5 h1:ffDgMSn83avw6rNzxhbt6w5/2oIrwQKTPGfyaLupZtE=
github.com/lightningnetwork/lnd/healthcheck v1.2.3/go.mod h1:eDxH3dEwV9DeBW/6inrmlVh1qBOFV0AI14EEPnGt9gc= github.com/lightningnetwork/lnd/fn v1.0.5/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U=
github.com/lightningnetwork/lnd/kvdb v1.4.4 h1:bCv63rVCvzqj1BkagN/EWTov6NDDgYEG/t0z2HepRMk= github.com/lightningnetwork/lnd/healthcheck v1.2.4 h1:lLPLac+p/TllByxGSlkCwkJlkddqMP5UCoawCj3mgFQ=
github.com/lightningnetwork/lnd/kvdb v1.4.4/go.mod h1:9SuaIqMA9ugrVkdvgQkYXa8CAKYNYd4vsEYORP4V698= github.com/lightningnetwork/lnd/healthcheck v1.2.4/go.mod h1:G7Tst2tVvWo7cx6mSBEToQC5L1XOGxzZTPB29g9Rv2I=
github.com/lightningnetwork/lnd/kvdb v1.4.8 h1:xH0a5Vi1yrcZ5BEeF2ba3vlKBRxrL9uYXlWTjOjbNTY=
github.com/lightningnetwork/lnd/kvdb v1.4.8/go.mod h1:J2diNABOoII9UrMnxXS5w7vZwP7CA1CStrl8MnIrb3A=
github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI= github.com/lightningnetwork/lnd/queue v1.1.1 h1:99ovBlpM9B0FRCGYJo6RSFDlt8/vOkQQZznVb18iNMI=
github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4= github.com/lightningnetwork/lnd/queue v1.1.1/go.mod h1:7A6nC1Qrm32FHuhx/mi1cieAiBZo5O6l8IBIoQxvkz4=
github.com/lightningnetwork/lnd/sqldb v1.0.2 h1:PfuYzScYMD9/QonKo/QvgsbXfTnH5DfldIimkfdW4Bk=
github.com/lightningnetwork/lnd/sqldb v1.0.2/go.mod h1:V2Xl6JNWLTKE97WJnwfs0d0TYJdIQTqK8/3aAwkd3qI=
github.com/lightningnetwork/lnd/ticker v1.1.1 h1:J/b6N2hibFtC7JLV77ULQp++QLtCwT6ijJlbdiZFbSM= 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/ticker v1.1.1/go.mod h1:waPTRAAcwtu7Ji3+3k+u/xH5GHovTsCoSVpho0KDvdA=
github.com/lightningnetwork/lnd/tlv v1.1.1 h1:BW1u9+uHLRA9sm+8FBkAg1H9rPjrj3S9KvXYiCYjQWk= github.com/lightningnetwork/lnd/tlv v1.2.3 h1:If5ibokA/UoCBGuCKaY6Vn2SJU0l9uAbehCnhTZjEP8=
github.com/lightningnetwork/lnd/tlv v1.1.1/go.mod h1:292dSXpZ+BNnSJFjS1qvHden9LEbulmECglSgfg+4lw= github.com/lightningnetwork/lnd/tlv v1.2.3/go.mod h1:zDkmqxOczP6LaLTvSFDQ1SJUfHcQRCMKFj93dn3eMB8=
github.com/lightningnetwork/lnd/tor v1.1.2 h1:3zv9z/EivNFaMF89v3ciBjCS7kvCj4ZFG7XvD2Qq0/k= github.com/lightningnetwork/lnd/tor v1.1.2 h1:3zv9z/EivNFaMF89v3ciBjCS7kvCj4ZFG7XvD2Qq0/k=
github.com/lightningnetwork/lnd/tor v1.1.2/go.mod h1:j7T9uJ2NLMaHwE7GiBGnpYLn4f7NRoTM6qj+ul6/ycA= github.com/lightningnetwork/lnd/tor v1.1.2/go.mod h1:j7T9uJ2NLMaHwE7GiBGnpYLn4f7NRoTM6qj+ul6/ycA=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
@ -1141,15 +1157,14 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
@ -1166,23 +1181,26 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@ -1195,8 +1213,6 @@ github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnz
github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -1230,8 +1246,9 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -1249,6 +1266,7 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -1269,8 +1287,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -1281,21 +1300,18 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 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 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw= github.com/urfave/cli v1.22.9 h1:cv3/KhXGBGjEXLC4bH0sLuJ9BewaAbpk5oyMOveu4pw=
github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.9/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@ -1318,7 +1334,6 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec h1:FpfFs4EhNehiVfzQttTuxanPIT43FtkkCFypIod8LHo=
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ= gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
@ -1343,24 +1358,25 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= 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.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE=
go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs=
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0 h1:R/OBkMoGgfy2fLhs2QhkCI1w4HLEQX92GCcJB6SSdNk=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0 h1:giGm8w67Ja7amYNfYMdme7xSp2pIxThWopw8+QP51Yk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 h1:VQbUHoJqytHHSJ1OZodPH9tvZZSVzUHjPHpkO85sT6k= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0 h1:VQbUHoJqytHHSJ1OZodPH9tvZZSVzUHjPHpkO85sT6k=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY=
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI=
go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ=
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
@ -1401,9 +1417,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1419,8 +1434,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -1463,8 +1478,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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.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.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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= 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= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1531,8 +1546,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 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.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1562,7 +1577,8 @@ 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.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= 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.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0=
golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1579,8 +1595,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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1679,8 +1695,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1690,8 +1706,8 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1785,8 +1801,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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2003,12 +2019,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -2088,6 +2104,7 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@ -2097,24 +2114,27 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws=
modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
@ -2123,33 +2143,35 @@ modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0=
modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4=
modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs= modernc.org/sqlite v1.29.8 h1:nGKglNx9K5v0As+zF0/Gcl1kMkmaU1XynYyq92PbsC8=
modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=

@ -19,6 +19,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
) )
const ( const (
@ -59,6 +60,7 @@ type InitInstantOutCtx struct {
initationHeight int32 initationHeight int32
outgoingChanSet loopdb.ChannelSet outgoingChanSet loopdb.ChannelSet
protocolVersion ProtocolVersion protocolVersion ProtocolVersion
sweepAddress btcutil.Address
} }
// InitInstantOutAction is the first action that is executed when the instant // InitInstantOutAction is the first action that is executed when the instant
@ -107,7 +109,7 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
if int32(res.Expiry) < initCtx.cltvExpiry+htlcExpiryDelta { if int32(res.Expiry) < initCtx.cltvExpiry+htlcExpiryDelta {
return f.HandleError(fmt.Errorf("reservation %x has "+ return f.HandleError(fmt.Errorf("reservation %x has "+
"expiry %v which is less than the swap expiry %v", "expiry %v which is less than the swap expiry %v",
resId, res.Expiry, initCtx.cltvExpiry)) resId, res.Expiry, initCtx.cltvExpiry+htlcExpiryDelta))
} }
} }
@ -165,11 +167,15 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
} }
// Create the address that we'll send the funds to. // Create the address that we'll send the funds to.
sweepAddress, err := f.cfg.Wallet.NextAddr( sweepAddress := initCtx.sweepAddress
f.ctx, "", walletrpc.AddressType_TAPROOT_PUBKEY, false, if sweepAddress == nil {
) sweepAddress, err = f.cfg.Wallet.NextAddr(
if err != nil { f.ctx, lnwallet.DefaultAccountName,
return f.HandleError(err) walletrpc.AddressType_TAPROOT_PUBKEY, false,
)
if err != nil {
return f.HandleError(err)
}
} }
// Now we can create the instant out. // Now we can create the instant out.
@ -179,13 +185,13 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
protocolVersion: ProtocolVersionFullReservation, protocolVersion: ProtocolVersionFullReservation,
initiationHeight: initCtx.initationHeight, initiationHeight: initCtx.initationHeight,
outgoingChanSet: initCtx.outgoingChanSet, outgoingChanSet: initCtx.outgoingChanSet,
cltvExpiry: initCtx.cltvExpiry, CltvExpiry: initCtx.cltvExpiry,
clientPubkey: keyRes.PubKey, clientPubkey: keyRes.PubKey,
serverPubkey: serverPubkey, serverPubkey: serverPubkey,
value: btcutil.Amount(reservationAmt), Value: btcutil.Amount(reservationAmt),
htlcFeeRate: feeRate, htlcFeeRate: feeRate,
swapInvoice: instantOutResponse.SwapInvoice, swapInvoice: instantOutResponse.SwapInvoice,
reservations: reservations, Reservations: reservations,
keyLocator: keyRes.KeyLocator, keyLocator: keyRes.KeyLocator,
sweepAddress: sweepAddress, sweepAddress: sweepAddress,
} }
@ -205,7 +211,7 @@ func (f *FSM) InitInstantOutAction(eventCtx fsm.EventContext) fsm.EventType {
func (f *FSM) PollPaymentAcceptedAction(_ fsm.EventContext) fsm.EventType { func (f *FSM) PollPaymentAcceptedAction(_ fsm.EventContext) fsm.EventType {
// Now that we're doing the swap, we first lock the reservations // Now that we're doing the swap, we first lock the reservations
// so that they can't be used for other swaps. // so that they can't be used for other swaps.
for _, reservation := range f.InstantOut.reservations { for _, reservation := range f.InstantOut.Reservations {
err := f.cfg.ReservationManager.LockReservation( err := f.cfg.ReservationManager.LockReservation(
f.ctx, reservation.ID, f.ctx, reservation.ID,
) )
@ -221,7 +227,7 @@ func (f *FSM) PollPaymentAcceptedAction(_ fsm.EventContext) fsm.EventType {
Invoice: f.InstantOut.swapInvoice, Invoice: f.InstantOut.swapInvoice,
Timeout: defaultSendpaymentTimeout, Timeout: defaultSendpaymentTimeout,
MaxParts: defaultMaxParts, MaxParts: defaultMaxParts,
MaxFee: getMaxRoutingFee(f.InstantOut.value), MaxFee: getMaxRoutingFee(f.InstantOut.Value),
}, },
) )
if err != nil { if err != nil {
@ -295,7 +301,7 @@ func (f *FSM) BuildHTLCAction(eventCtx fsm.EventContext) fsm.EventType {
return f.handleErrorAndUnlockReservations(err) return f.handleErrorAndUnlockReservations(err)
} }
if len(htlcInitRes.HtlcServerNonces) != len(f.InstantOut.reservations) { if len(htlcInitRes.HtlcServerNonces) != len(f.InstantOut.Reservations) {
return f.handleErrorAndUnlockReservations( return f.handleErrorAndUnlockReservations(
errors.New("invalid number of server nonces"), errors.New("invalid number of server nonces"),
) )
@ -429,8 +435,8 @@ func (f *FSM) PushPreimageAction(eventCtx fsm.EventContext) fsm.EventType {
return OnErrorPublishHtlc return OnErrorPublishHtlc
} }
f.InstantOut.finalizedSweeplessSweepTx = sweepTx f.InstantOut.FinalizedSweeplessSweepTx = sweepTx
txHash := f.InstantOut.finalizedSweeplessSweepTx.TxHash() txHash := f.InstantOut.FinalizedSweeplessSweepTx.TxHash()
f.InstantOut.SweepTxHash = &txHash f.InstantOut.SweepTxHash = &txHash
@ -592,7 +598,7 @@ func (f *FSM) handleErrorAndUnlockReservations(err error) fsm.EventType {
defer cancel() defer cancel()
// Unlock the reservations. // Unlock the reservations.
for _, reservation := range f.InstantOut.reservations { for _, reservation := range f.InstantOut.Reservations {
err := f.cfg.ReservationManager.UnlockReservation( err := f.cfg.ReservationManager.UnlockReservation(
ctx, reservation.ID, ctx, reservation.ID,
) )

@ -35,16 +35,16 @@ type InstantOut struct {
// State is the current state of the swap. // State is the current state of the swap.
State fsm.StateType State fsm.StateType
// cltvExpiry is the expiry of the swap. // CltvExpiry is the expiry of the swap.
cltvExpiry int32 CltvExpiry int32
// outgoingChanSet optionally specifies the short channel ids of the // outgoingChanSet optionally specifies the short channel ids of the
// channels that may be used to loop out. // channels that may be used to loop out.
outgoingChanSet loopdb.ChannelSet outgoingChanSet loopdb.ChannelSet
// reservations are the reservations that are used in as inputs for the // Reservations are the Reservations that are used in as inputs for the
// instant out swap. // instant out swap.
reservations []*reservation.Reservation Reservations []*reservation.Reservation
// protocolVersion is the version of the protocol that is used for the // protocolVersion is the version of the protocol that is used for the
// swap. // swap.
@ -53,8 +53,8 @@ type InstantOut struct {
// initiationHeight is the height at which the swap was initiated. // initiationHeight is the height at which the swap was initiated.
initiationHeight int32 initiationHeight int32
// value is the amount that is swapped. // Value is the amount that is swapped.
value btcutil.Amount Value btcutil.Amount
// keyLocator is the key locator that is used for the swap. // keyLocator is the key locator that is used for the swap.
keyLocator keychain.KeyLocator keyLocator keychain.KeyLocator
@ -81,9 +81,9 @@ type InstantOut struct {
// SweepTxHash is the hash of the sweep transaction. // SweepTxHash is the hash of the sweep transaction.
SweepTxHash *chainhash.Hash SweepTxHash *chainhash.Hash
// finalizedSweeplessSweepTx is the transaction that is used to sweep // FinalizedSweeplessSweepTx is the transaction that is used to sweep
// the funds in the cooperative path. // the funds in the cooperative path.
finalizedSweeplessSweepTx *wire.MsgTx FinalizedSweeplessSweepTx *wire.MsgTx
// sweepConfirmationHeight is the height at which the sweep // sweepConfirmationHeight is the height at which the sweep
// transaction was confirmed. // transaction was confirmed.
@ -93,7 +93,7 @@ type InstantOut struct {
// getHtlc returns the swap.htlc for the instant out. // getHtlc returns the swap.htlc for the instant out.
func (i *InstantOut) getHtlc(chainParams *chaincfg.Params) (*swap.Htlc, error) { func (i *InstantOut) getHtlc(chainParams *chaincfg.Params) (*swap.Htlc, error) {
return swap.NewHtlcV2( return swap.NewHtlcV2(
i.cltvExpiry, pubkeyTo33ByteSlice(i.serverPubkey), i.CltvExpiry, pubkeyTo33ByteSlice(i.serverPubkey),
pubkeyTo33ByteSlice(i.clientPubkey), i.SwapHash, chainParams, pubkeyTo33ByteSlice(i.clientPubkey), i.SwapHash, chainParams,
) )
} }
@ -104,11 +104,11 @@ func (i *InstantOut) createMusig2Session(ctx context.Context,
[][]byte, error) { [][]byte, error) {
// Create the htlc musig2 context. // Create the htlc musig2 context.
musig2Sessions := make([]*input.MuSig2SessionInfo, len(i.reservations)) musig2Sessions := make([]*input.MuSig2SessionInfo, len(i.Reservations))
clientNonces := make([][]byte, len(i.reservations)) clientNonces := make([][]byte, len(i.Reservations))
// Create the sessions and nonces from the reservations. // Create the sessions and nonces from the reservations.
for idx, reservation := range i.reservations { for idx, reservation := range i.Reservations {
session, err := reservation.Musig2CreateSession(ctx, signer) session, err := reservation.Musig2CreateSession(ctx, signer)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -123,12 +123,12 @@ func (i *InstantOut) createMusig2Session(ctx context.Context,
// getInputReservation returns the input reservation for the instant out. // getInputReservation returns the input reservation for the instant out.
func (i *InstantOut) getInputReservations() (InputReservations, error) { func (i *InstantOut) getInputReservations() (InputReservations, error) {
if len(i.reservations) == 0 { if len(i.Reservations) == 0 {
return nil, errors.New("no reservations") return nil, errors.New("no reservations")
} }
inputs := make(InputReservations, len(i.reservations)) inputs := make(InputReservations, len(i.Reservations))
for idx, reservation := range i.reservations { for idx, reservation := range i.Reservations {
pkScript, err := reservation.GetPkScript() pkScript, err := reservation.GetPkScript()
if err != nil { if err != nil {
return nil, err return nil, err
@ -170,7 +170,7 @@ func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params) (
// Estimate the fee // Estimate the fee
weight := htlcWeight(len(inputReservations)) weight := htlcWeight(len(inputReservations))
fee := i.htlcFeeRate.FeeForWeight(weight) fee := i.htlcFeeRate.FeeForWeight(weight)
if fee > i.value/5 { if fee > i.Value/5 {
return nil, errors.New("fee is higher than 20% of " + return nil, errors.New("fee is higher than 20% of " +
"sweep value") "sweep value")
} }
@ -182,7 +182,7 @@ func (i *InstantOut) createHtlcTransaction(network *chaincfg.Params) (
// Create the sweep output // Create the sweep output
sweepOutput := &wire.TxOut{ sweepOutput := &wire.TxOut{
Value: int64(i.value) - int64(fee), Value: int64(i.Value) - int64(fee),
PkScript: htlc.PkScript, PkScript: htlc.PkScript,
} }
@ -214,7 +214,7 @@ func (i *InstantOut) createSweeplessSweepTx(feerate chainfee.SatPerKWeight) (
// Estimate the fee // Estimate the fee
weight := sweeplessSweepWeight(len(inputReservations)) weight := sweeplessSweepWeight(len(inputReservations))
fee := feerate.FeeForWeight(weight) fee := feerate.FeeForWeight(weight)
if fee > i.value/5 { if fee > i.Value/5 {
return nil, errors.New("fee is higher than 20% of " + return nil, errors.New("fee is higher than 20% of " +
"sweep value") "sweep value")
} }
@ -226,7 +226,7 @@ func (i *InstantOut) createSweeplessSweepTx(feerate chainfee.SatPerKWeight) (
// Create the sweep output // Create the sweep output
sweepOutput := &wire.TxOut{ sweepOutput := &wire.TxOut{
Value: int64(i.value) - int64(fee), Value: int64(i.Value) - int64(fee),
PkScript: pkscript, PkScript: pkscript,
} }
@ -381,7 +381,7 @@ func (i *InstantOut) generateHtlcSweepTx(ctx context.Context,
return nil, err return nil, err
} }
fee := feeRate.FeeForWeight(int64(weightEstimator.Weight())) fee := feeRate.FeeForWeight(weightEstimator.Weight())
htlcOutValue := i.finalizedHtlcTx.TxOut[0].Value htlcOutValue := i.finalizedHtlcTx.TxOut[0].Value
output := &wire.TxOut{ output := &wire.TxOut{
@ -424,7 +424,7 @@ func (i *InstantOut) generateHtlcSweepTx(ctx context.Context,
} }
// htlcWeight returns the weight for the htlc transaction. // htlcWeight returns the weight for the htlc transaction.
func htlcWeight(numInputs int) int64 { func htlcWeight(numInputs int) lntypes.WeightUnit {
var weightEstimator input.TxWeightEstimator var weightEstimator input.TxWeightEstimator
for i := 0; i < numInputs; i++ { for i := 0; i < numInputs; i++ {
weightEstimator.AddTaprootKeySpendInput( weightEstimator.AddTaprootKeySpendInput(
@ -434,11 +434,11 @@ func htlcWeight(numInputs int) int64 {
weightEstimator.AddP2WSHOutput() weightEstimator.AddP2WSHOutput()
return int64(weightEstimator.Weight()) return weightEstimator.Weight()
} }
// sweeplessSweepWeight returns the weight for the sweepless sweep transaction. // sweeplessSweepWeight returns the weight for the sweepless sweep transaction.
func sweeplessSweepWeight(numInputs int) int64 { func sweeplessSweepWeight(numInputs int) lntypes.WeightUnit {
var weightEstimator input.TxWeightEstimator var weightEstimator input.TxWeightEstimator
for i := 0; i < numInputs; i++ { for i := 0; i < numInputs; i++ {
weightEstimator.AddTaprootKeySpendInput( weightEstimator.AddTaprootKeySpendInput(
@ -448,7 +448,7 @@ func sweeplessSweepWeight(numInputs int) int64 {
weightEstimator.AddP2TROutput() weightEstimator.AddP2TROutput()
return int64(weightEstimator.Weight()) return weightEstimator.Weight()
} }
// pubkeyTo33ByteSlice converts a pubkey to a 33 byte slice. // pubkeyTo33ByteSlice converts a pubkey to a 33 byte slice.

@ -137,7 +137,20 @@ func (m *Manager) recoverInstantOuts(ctx context.Context) error {
// NewInstantOut creates a new instantout. // NewInstantOut creates a new instantout.
func (m *Manager) NewInstantOut(ctx context.Context, func (m *Manager) NewInstantOut(ctx context.Context,
reservations []reservation.ID) (*FSM, error) { reservations []reservation.ID, sweepAddress string) (*FSM, error) {
var (
sweepAddr btcutil.Address
err error
)
if sweepAddress != "" {
sweepAddr, err = btcutil.DecodeAddress(
sweepAddress, m.cfg.Network,
)
if err != nil {
return nil, err
}
}
m.Lock() m.Lock()
// Create the instantout request. // Create the instantout request.
@ -146,6 +159,7 @@ func (m *Manager) NewInstantOut(ctx context.Context,
reservations: reservations, reservations: reservations,
initationHeight: m.currentHeight, initationHeight: m.currentHeight,
protocolVersion: CurrentProtocolVersion(), protocolVersion: CurrentProtocolVersion(),
sweepAddress: sweepAddr,
} }
instantOut, err := NewFSM( instantOut, err := NewFSM(
@ -244,3 +258,8 @@ func (m *Manager) GetInstantOutQuote(ctx context.Context,
OnChainFee: chainFee, OnChainFee: chainFee,
}, nil }, nil
} }
// ListInstantOuts returns all instant outs from the database.
func (m *Manager) ListInstantOuts(ctx context.Context) ([]*InstantOut, error) {
return m.cfg.Store.ListInstantLoopOuts(ctx)
}

@ -9,7 +9,6 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/fsm"
reservationrpc "github.com/lightninglabs/loop/swapserverrpc" reservationrpc "github.com/lightninglabs/loop/swapserverrpc"
) )
@ -186,7 +185,7 @@ func (m *Manager) fetchL402(ctx context.Context) {
func (m *Manager) RegisterReservationNotifications( func (m *Manager) RegisterReservationNotifications(
reservationChan chan *reservationrpc.ServerReservationNotification) error { reservationChan chan *reservationrpc.ServerReservationNotification) error {
// In order to create a valid lsat we first are going to call // In order to create a valid l402 we first are going to call
// the FetchL402 method. As a client might not have outbound capacity // the FetchL402 method. As a client might not have outbound capacity
// yet, we'll retry until we get a valid response. // yet, we'll retry until we get a valid response.
if !m.hasL402 { if !m.hasL402 {

@ -112,7 +112,7 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
dbFixture := loopdb.NewTestDB(t) dbFixture := loopdb.NewTestDB(t)
store := NewSQLStore(dbFixture) store := NewSQLStore(loopdb.NewTypedStore[Querier](dbFixture))
mockReservationClient := new(mockReservationClient) mockReservationClient := new(mockReservationClient)

@ -16,9 +16,9 @@ import (
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
) )
// BaseDB is the interface that contains all the queries generated // Querier is the interface that contains all the queries generated
// by sqlc for the reservation table. // by sqlc for the reservation table.
type BaseDB interface { type Querier interface {
// CreateReservation stores the reservation in the database. // CreateReservation stores the reservation in the database.
CreateReservation(ctx context.Context, CreateReservation(ctx context.Context,
arg sqlc.CreateReservationParams) error arg sqlc.CreateReservationParams) error
@ -35,14 +35,24 @@ type BaseDB interface {
// made. // made.
GetReservations(ctx context.Context) ([]sqlc.Reservation, error) GetReservations(ctx context.Context) ([]sqlc.Reservation, error)
// UpdateReservation inserts a new reservation update. // InsertReservationUpdate inserts a new reservation update.
InsertReservationUpdate(ctx context.Context,
arg sqlc.InsertReservationUpdateParams) error
// UpdateReservation updates a reservation.
UpdateReservation(ctx context.Context, UpdateReservation(ctx context.Context,
arg sqlc.UpdateReservationParams) error arg sqlc.UpdateReservationParams) error
}
// BaseDB is the interface that contains all the queries generated
// by sqlc for the reservation table and transaction functionality.
type BaseDB interface {
Querier
// ExecTx allows for executing a function in the context of a database // ExecTx allows for executing a function in the context of a database
// transaction. // transaction.
ExecTx(ctx context.Context, txOptions loopdb.TxOptions, ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
txBody func(*sqlc.Queries) error) error txBody func(Querier) error) error
} }
// SQLStore manages the reservations in the database. // SQLStore manages the reservations in the database.
@ -81,8 +91,8 @@ func (r *SQLStore) CreateReservation(ctx context.Context,
UpdateState: string(reservation.State), UpdateState: string(reservation.State),
} }
return r.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, return r.baseDb.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
err := q.CreateReservation(ctx, args) err := q.CreateReservation(ctx, args)
if err != nil { if err != nil {
return err return err
@ -121,8 +131,8 @@ func (r *SQLStore) UpdateReservation(ctx context.Context,
), ),
} }
return r.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, return r.baseDb.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
err := q.UpdateReservation(ctx, updateArgs) err := q.UpdateReservation(ctx, updateArgs)
if err != nil { if err != nil {
return err return err
@ -138,7 +148,7 @@ func (r *SQLStore) GetReservation(ctx context.Context,
var reservation *Reservation var reservation *Reservation
err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(), err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
var err error var err error
reservationRow, err := q.GetReservation( reservationRow, err := q.GetReservation(
ctx, reservationId[:], ctx, reservationId[:],
@ -182,7 +192,7 @@ func (r *SQLStore) ListReservations(ctx context.Context) ([]*Reservation,
var result []*Reservation var result []*Reservation
err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(), err := r.baseDb.ExecTx(ctx, loopdb.NewSqlReadOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
var err error var err error
reservations, err := q.GetReservations(ctx) reservations, err := q.GetReservations(ctx)

@ -19,7 +19,7 @@ func TestSqlStore(t *testing.T) {
testDb := loopdb.NewTestDB(t) testDb := loopdb.NewTestDB(t)
defer testDb.Close() defer testDb.Close()
store := NewSQLStore(testDb) store := NewSQLStore(loopdb.NewTypedStore[Querier](testDb))
// Create a reservation and store it. // Create a reservation and store it.
reservation := &Reservation{ reservation := &Reservation{

@ -21,9 +21,9 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
// InstantOutBaseDB is the interface that contains all the queries generated // Querier is the interface that contains all the queries generated
// by sqlc for the instantout table. // by sqlc for the instantout table.
type InstantOutBaseDB interface { type Querier interface {
// InsertSwap inserts a new base swap. // InsertSwap inserts a new base swap.
InsertSwap(ctx context.Context, arg sqlc.InsertSwapParams) error InsertSwap(ctx context.Context, arg sqlc.InsertSwapParams) error
@ -53,11 +53,17 @@ type InstantOutBaseDB interface {
// GetInstantOutSwaps retrieves all instant out swaps. // GetInstantOutSwaps retrieves all instant out swaps.
GetInstantOutSwaps(ctx context.Context) ([]sqlc.GetInstantOutSwapsRow, GetInstantOutSwaps(ctx context.Context) ([]sqlc.GetInstantOutSwapsRow,
error) error)
}
// InstantOutBaseDB is the interface that contains all the queries generated
// by sqlc for the instantout table and transaction functionality.
type InstantOutBaseDB interface {
Querier
// ExecTx allows for executing a function in the context of a database // ExecTx allows for executing a function in the context of a database
// transaction. // transaction.
ExecTx(ctx context.Context, txOptions loopdb.TxOptions, ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
txBody func(*sqlc.Queries) error) error txBody func(Querier) error) error
} }
// ReservationStore is the interface that is required to load the reservations // ReservationStore is the interface that is required to load the reservations
@ -94,8 +100,8 @@ func (s *SQLStore) CreateInstantLoopOut(ctx context.Context,
SwapHash: instantOut.SwapHash[:], SwapHash: instantOut.SwapHash[:],
Preimage: instantOut.swapPreimage[:], Preimage: instantOut.swapPreimage[:],
InitiationTime: s.clock.Now(), InitiationTime: s.clock.Now(),
AmountRequested: int64(instantOut.value), AmountRequested: int64(instantOut.Value),
CltvExpiry: instantOut.cltvExpiry, CltvExpiry: instantOut.CltvExpiry,
MaxMinerFee: 0, MaxMinerFee: 0,
MaxSwapFee: 0, MaxSwapFee: 0,
InitiationHeight: instantOut.initiationHeight, InitiationHeight: instantOut.initiationHeight,
@ -114,7 +120,7 @@ func (s *SQLStore) CreateInstantLoopOut(ctx context.Context,
} }
reservationIdByteSlice := reservationIdsToByteSlice( reservationIdByteSlice := reservationIdsToByteSlice(
instantOut.reservations, instantOut.Reservations,
) )
instantOutArgs := sqlc.InsertInstantOutParams{ instantOutArgs := sqlc.InsertInstantOutParams{
SwapHash: instantOut.SwapHash[:], SwapHash: instantOut.SwapHash[:],
@ -132,8 +138,8 @@ func (s *SQLStore) CreateInstantLoopOut(ctx context.Context,
UpdateState: string(instantOut.State), UpdateState: string(instantOut.State),
} }
return s.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, return s.baseDb.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
err := q.InsertSwap(ctx, swapArgs) err := q.InsertSwap(ctx, swapArgs)
if err != nil { if err != nil {
return err return err
@ -172,9 +178,9 @@ func (s *SQLStore) UpdateInstantLoopOut(ctx context.Context,
} }
var finalSweeplessSweepTx []byte var finalSweeplessSweepTx []byte
if instantOut.finalizedSweeplessSweepTx != nil { if instantOut.FinalizedSweeplessSweepTx != nil {
var buffer bytes.Buffer var buffer bytes.Buffer
err := instantOut.finalizedSweeplessSweepTx.Serialize( err := instantOut.FinalizedSweeplessSweepTx.Serialize(
&buffer, &buffer,
) )
if err != nil { if err != nil {
@ -204,8 +210,8 @@ func (s *SQLStore) UpdateInstantLoopOut(ctx context.Context,
UpdateState: string(instantOut.State), UpdateState: string(instantOut.State),
} }
return s.baseDb.ExecTx(ctx, &loopdb.SqliteTxOptions{}, return s.baseDb.ExecTx(ctx, loopdb.NewSqlWriteOpts(),
func(q *sqlc.Queries) error { func(q Querier) error {
err := q.UpdateInstantOut(ctx, updateParams) err := q.UpdateInstantOut(ctx, updateParams)
if err != nil { if err != nil {
return err return err
@ -355,12 +361,12 @@ func (s *SQLStore) sqlInstantOutToInstantOut(ctx context.Context,
instantOut := &InstantOut{ instantOut := &InstantOut{
SwapHash: swapHash, SwapHash: swapHash,
swapPreimage: swapPreImage, swapPreimage: swapPreImage,
cltvExpiry: row.CltvExpiry, CltvExpiry: row.CltvExpiry,
outgoingChanSet: outgoingChanSet, outgoingChanSet: outgoingChanSet,
reservations: reservations, Reservations: reservations,
protocolVersion: ProtocolVersion(row.ProtocolVersion), protocolVersion: ProtocolVersion(row.ProtocolVersion),
initiationHeight: row.InitiationHeight, initiationHeight: row.InitiationHeight,
value: btcutil.Amount(row.AmountRequested), Value: btcutil.Amount(row.AmountRequested),
keyLocator: keychain.KeyLocator{ keyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(row.ClientKeyFamily), Family: keychain.KeyFamily(row.ClientKeyFamily),
Index: uint32(row.ClientKeyIndex), Index: uint32(row.ClientKeyIndex),
@ -372,7 +378,7 @@ func (s *SQLStore) sqlInstantOutToInstantOut(ctx context.Context,
sweepAddress: sweepAddress, sweepAddress: sweepAddress,
finalizedHtlcTx: finalizedHtlcTx, finalizedHtlcTx: finalizedHtlcTx,
SweepTxHash: sweepTxHash, SweepTxHash: sweepTxHash,
finalizedSweeplessSweepTx: finalizedSweepLessSweepTx, FinalizedSweeplessSweepTx: finalizedSweepLessSweepTx,
sweepConfirmationHeight: uint32(deserializeNullInt32( sweepConfirmationHeight: uint32(deserializeNullInt32(
row.SweepConfirmationHeight, row.SweepConfirmationHeight,
)), )),

@ -92,6 +92,12 @@ type OutRequest struct {
// initiated the swap (loop CLI, autolooper, LiT UI and so on) and is // initiated the swap (loop CLI, autolooper, LiT UI and so on) and is
// appended to the user agent string. // appended to the user agent string.
Initiator string Initiator string
// PaymentTimeout specifies the payment timeout for the individual
// off-chain payments. As the swap payment may be retried (depending on
// the configured maximum payment timeout) the total time spent may be
// a multiple of this value.
PaymentTimeout time.Duration
} }
// Out contains the full details of a loop out request. This includes things // Out contains the full details of a loop out request. This includes things

@ -312,9 +312,26 @@ func (c *autoloopTestCtx) autoloop(step *autoloopStep) {
// Assert that we query the server for a quote for each of our // Assert that we query the server for a quote for each of our
// recommended swaps. Note that this differs from our set of expected // recommended swaps. Note that this differs from our set of expected
// swaps because we may get quotes for suggested swaps but then just // swaps because we may get quotes for suggested swaps but then just
// log them. // log them. The order in c.quoteRequestIn is not deterministic,
// it depends on the order of map traversal (map peerChannels in
// method Manager.SuggestSwaps). So receive from the channel an item
// and then find a corresponding expected item, using amount as a key.
amt2expected := make(map[btcutil.Amount]quoteInRequestResp)
for _, expected := range step.quotesIn { for _, expected := range step.quotesIn {
// Make sure all amounts are unique.
require.NotContains(c.t, amt2expected, expected.request.Amount)
amt2expected[expected.request.Amount] = expected
}
for i := 0; i < len(step.quotesIn); i++ {
request := <-c.quoteRequestIn request := <-c.quoteRequestIn
// Get the expected item, using amount as a key.
expected, has := amt2expected[request.Amount]
require.True(c.t, has)
delete(amt2expected, request.Amount)
assert.Equal( assert.Equal(
c.t, expected.request.Amount, request.Amount, c.t, expected.request.Amount, request.Amount,
) )

@ -46,6 +46,7 @@ import (
"github.com/lightninglabs/loop" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/labels" "github.com/lightninglabs/loop/labels"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
clientrpc "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/funding" "github.com/lightningnetwork/lnd/funding"
@ -55,8 +56,6 @@ import (
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/ticker" "github.com/lightningnetwork/lnd/ticker"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
clientrpc "github.com/lightninglabs/loop/looprpc"
) )
const ( const (

@ -77,7 +77,6 @@ func loopInSweepFee(fee chainfee.SatPerKWeight) btcutil.Amount {
maxSize := htlc.MaxTimeoutWitnessSize() maxSize := htlc.MaxTimeoutWitnessSize()
estimator.AddWitnessInput(maxSize) estimator.AddWitnessInput(maxSize)
weight := int64(estimator.Weight())
return fee.FeeForWeight(weight) return fee.FeeForWeight(estimator.Weight())
} }

@ -8,13 +8,12 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
clientrpc "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
clientrpc "github.com/lightninglabs/loop/looprpc"
) )
var ( var (

@ -177,7 +177,7 @@ func TestCalculateAmount(t *testing.T) {
} }
} }
// TestSuggestSwaps tests swap suggestions for the threshold rule. It does not // TestSuggestSwap tests swap suggestions for the threshold rule. It does not
// many different values because we have separate tests for swap amount // many different values because we have separate tests for swap amount
// calculation. // calculation.
func TestSuggestSwap(t *testing.T) { func TestSuggestSwap(t *testing.T) {

@ -10,7 +10,7 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
"github.com/lightningnetwork/lnd/cert" "github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
@ -76,6 +76,10 @@ var (
defaultLndMacaroon, defaultLndMacaroon,
) )
// DefaultLndRPCTimeout is the default timeout to use when communicating
// with lnd.
DefaultLndRPCTimeout = time.Minute
// DefaultTLSCertPath is the default full path of the autogenerated TLS // DefaultTLSCertPath is the default full path of the autogenerated TLS
// certificate. // certificate.
DefaultTLSCertPath = filepath.Join( DefaultTLSCertPath = filepath.Join(
@ -118,6 +122,9 @@ type lndConfig struct {
MacaroonPath string `long:"macaroonpath" description:"The full path to the single macaroon to use, either the admin.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur."` MacaroonPath string `long:"macaroonpath" description:"The full path to the single macaroon to use, either the admin.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur."`
TLSPath string `long:"tlspath" description:"Path to lnd tls certificate"` TLSPath string `long:"tlspath" description:"Path to lnd tls certificate"`
// RPCTimeout is the timeout to use when communicating with lnd.
RPCTimeout time.Duration `long:"rpctimeout" description:"The timeout to use when communicating with lnd"`
} }
type loopServerConfig struct { type loopServerConfig struct {
@ -160,8 +167,10 @@ type Config struct {
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB."` MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB."`
DebugLevel string `long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"` DebugLevel string `long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
MaxLSATCost uint32 `long:"maxlsatcost" description:"Maximum cost in satoshis that loopd is going to pay for an LSAT token automatically. Does not include routing fees."` MaxLSATCost uint32 `long:"maxlsatcost" hidden:"true"`
MaxLSATFee uint32 `long:"maxlsatfee" description:"Maximum routing fee in satoshis that we are willing to pay while paying for an LSAT token."` MaxLSATFee uint32 `long:"maxlsatfee" hidden:"true"`
MaxL402Cost uint32 `long:"maxl402cost" description:"Maximum cost in satoshis that loopd is going to pay for an L402 token automatically. Does not include routing fees."`
MaxL402Fee uint32 `long:"maxl402fee" description:"Maximum routing fee in satoshis that we are willing to pay while paying for an L402 token."`
LoopOutMaxParts uint32 `long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap."` LoopOutMaxParts uint32 `long:"loopoutmaxparts" description:"The maximum number of payment parts that may be used for a loop out swap."`
@ -206,8 +215,8 @@ func DefaultConfig() Config {
TLSKeyPath: DefaultTLSKeyPath, TLSKeyPath: DefaultTLSKeyPath,
TLSValidity: DefaultAutogenValidity, TLSValidity: DefaultAutogenValidity,
MacaroonPath: DefaultMacaroonPath, MacaroonPath: DefaultMacaroonPath,
MaxLSATCost: lsat.DefaultMaxCostSats, MaxL402Cost: l402.DefaultMaxCostSats,
MaxLSATFee: lsat.DefaultMaxRoutingFeeSats, MaxL402Fee: l402.DefaultMaxRoutingFeeSats,
LoopOutMaxParts: defaultLoopOutMaxParts, LoopOutMaxParts: defaultLoopOutMaxParts,
TotalPaymentTimeout: defaultTotalPaymentTimeout, TotalPaymentTimeout: defaultTotalPaymentTimeout,
MaxPaymentRetries: defaultMaxPaymentRetries, MaxPaymentRetries: defaultMaxPaymentRetries,
@ -215,6 +224,7 @@ func DefaultConfig() Config {
Lnd: &lndConfig{ Lnd: &lndConfig{
Host: "localhost:10009", Host: "localhost:10009",
MacaroonPath: DefaultLndMacaroonPath, MacaroonPath: DefaultLndMacaroonPath,
RPCTimeout: DefaultLndRPCTimeout,
}, },
} }
} }

@ -17,14 +17,12 @@ import (
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout"
"github.com/lightninglabs/loop/instantout/reservation"
"github.com/lightninglabs/loop/loopd/perms" "github.com/lightninglabs/loop/loopd/perms"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightninglabs/loop/instantout/reservation"
loop_looprpc "github.com/lightninglabs/loop/looprpc" loop_looprpc "github.com/lightninglabs/loop/looprpc"
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc" loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/macaroons"
@ -53,7 +51,7 @@ type ListenerCfg struct {
// on the passed TLS configuration. // on the passed TLS configuration.
restListener func(*tls.Config) (net.Listener, error) restListener func(*tls.Config) (net.Listener, error)
// getLnd returns a grpc connection to an lnd instance. // getLnd returns a grpc connection to a lnd instance.
getLnd func(lndclient.Network, *lndConfig) (*lndclient.GrpcLndServices, getLnd func(lndclient.Network, *lndConfig) (*lndclient.GrpcLndServices,
error) error)
} }
@ -120,9 +118,9 @@ func New(config *Config, lisCfg *ListenerCfg) *Daemon {
// Start starts loopd in daemon mode. It will listen for grpc connections, // Start starts loopd in daemon mode. It will listen for grpc connections,
// execute commands and pass back swap status information. // execute commands and pass back swap status information.
func (d *Daemon) Start() error { func (d *Daemon) Start() error {
// There should be no reason to start the daemon twice. Therefore return // There should be no reason to start the daemon twice. Therefore,
// an error if that's tried. This is mostly to guard against Start and // return an error if that's tried. This is mostly to guard against
// StartAsSubserver both being called. // Start and StartAsSubserver both being called.
if atomic.AddInt32(&d.started, 1) != 1 { if atomic.AddInt32(&d.started, 1) != 1 {
return errOnlyStartOnce return errOnlyStartOnce
} }
@ -137,7 +135,7 @@ func (d *Daemon) Start() error {
// With lnd connected, initialize everything else, such as the swap // With lnd connected, initialize everything else, such as the swap
// server client, the swap client RPC server instance and our main swap // server client, the swap client RPC server instance and our main swap
// and error handlers. If this fails, then nothing has been started yet // and error handlers. If this fails, then nothing has been started yet,
// and we can just return the error. // and we can just return the error.
err = d.initialize(true) err = d.initialize(true)
if errors.Is(err, bbolt.ErrTimeout) { if errors.Is(err, bbolt.ErrTimeout) {
@ -324,7 +322,7 @@ func (d *Daemon) startWebServers() error {
err := d.restServer.Serve(d.restListener) err := d.restServer.Serve(d.restListener)
// ErrServerClosed is always returned when the proxy is // ErrServerClosed is always returned when the proxy is
// shut down, so don't log it. // shut down, so don't log it.
if err != nil && err != http.ErrServerClosed { if err != nil && !errors.Is(err, http.ErrServerClosed) {
// Notify the main error handler goroutine that // Notify the main error handler goroutine that
// we exited unexpectedly here. We don't have to // we exited unexpectedly here. We don't have to
// worry about blocking as the internal error // worry about blocking as the internal error
@ -343,7 +341,7 @@ func (d *Daemon) startWebServers() error {
log.Infof("RPC server listening on %s", d.grpcListener.Addr()) log.Infof("RPC server listening on %s", d.grpcListener.Addr())
err = d.grpcServer.Serve(d.grpcListener) err = d.grpcServer.Serve(d.grpcListener)
if err != nil && err != grpc.ErrServerStopped { if err != nil && !errors.Is(err, grpc.ErrServerStopped) {
// Notify the main error handler goroutine that // Notify the main error handler goroutine that
// we exited unexpectedly here. We don't have to // we exited unexpectedly here. We don't have to
// worry about blocking as the internal error // worry about blocking as the internal error
@ -411,7 +409,18 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
return err return err
} }
sweeperDb := sweepbatcher.NewSQLStore(baseDb, chainParams) // Run the costs migration.
err = loop.MigrateLoopOutCosts(d.mainCtx, d.lnd.LndServices, swapDb)
if err != nil {
log.Errorf("Cost migration failed: %v", err)
return err
}
sweeperDb := sweepbatcher.NewSQLStore(
loopdb.NewTypedStore[sweepbatcher.Querier](baseDb),
chainParams,
)
// Create an instance of the loop client library. // Create an instance of the loop client library.
swapClient, clientCleanup, err := getClient( swapClient, clientCleanup, err := getClient(
@ -495,7 +504,9 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
) )
// Create the reservation and instantout managers. // Create the reservation and instantout managers.
if d.cfg.EnableExperimental { if d.cfg.EnableExperimental {
reservationStore := reservation.NewSQLStore(baseDb) reservationStore := reservation.NewSQLStore(
loopdb.NewTypedStore[reservation.Querier](baseDb),
)
reservationConfig := &reservation.Config{ reservationConfig := &reservation.Config{
Store: reservationStore, Store: reservationStore,
Wallet: d.lnd.WalletKit, Wallet: d.lnd.WalletKit,
@ -510,7 +521,8 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
// Create the instantout services. // Create the instantout services.
instantOutStore := instantout.NewSQLStore( instantOutStore := instantout.NewSQLStore(
baseDb, clock.NewDefaultClock(), reservationStore, loopdb.NewTypedStore[instantout.Querier](baseDb),
clock.NewDefaultClock(), reservationStore,
d.lnd.ChainParams, d.lnd.ChainParams,
) )
instantOutConfig := &instantout.Config{ instantOutConfig := &instantout.Config{
@ -682,9 +694,9 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
var runtimeErr error var runtimeErr error
// There are only two ways this goroutine can exit. Either there // There are only two ways this goroutine can exit. Either there
// is an internal error or the caller requests shutdown. In both // is an internal error or the caller requests a shutdown.
// cases we wait for the stop to complete before we signal the // In both cases we wait for the stop to complete before we
// caller that we're done. // signal the caller that we're done.
select { select {
case runtimeErr = <-d.internalErrChan: case runtimeErr = <-d.internalErrChan:
log.Errorf("Runtime error in daemon, shutting down: "+ log.Errorf("Runtime error in daemon, shutting down: "+
@ -693,7 +705,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
case <-d.quit: case <-d.quit:
} }
// We need to shutdown before sending the error on the channel, // We need to shut down before sending the error on the channel,
// otherwise a caller might exit the process too early. // otherwise a caller might exit the process too early.
d.stop() d.stop()
cleanupMacaroonStore() cleanupMacaroonStore()
@ -724,7 +736,7 @@ func (d *Daemon) stop() {
d.mainCtxCancel() d.mainCtxCancel()
} }
// As there is no swap activity anymore, we can forcefully shutdown the // As there is no swap activity anymore, we can forcefully shut down the
// gRPC and HTTP servers now. // gRPC and HTTP servers now.
log.Infof("Stopping gRPC server") log.Infof("Stopping gRPC server")
if d.grpcServer != nil { if d.grpcServer != nil {

@ -2,7 +2,7 @@ package loopd
import ( import (
"github.com/btcsuite/btclog" "github.com/btcsuite/btclog"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/fsm" "github.com/lightninglabs/loop/fsm"
@ -37,7 +37,7 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
lnd.AddSubLogger(root, "SWEEP", intercept, sweepbatcher.UseLogger) lnd.AddSubLogger(root, "SWEEP", intercept, sweepbatcher.UseLogger)
lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger) lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger)
lnd.AddSubLogger(root, "STORE", intercept, loopdb.UseLogger) lnd.AddSubLogger(root, "STORE", intercept, loopdb.UseLogger)
lnd.AddSubLogger(root, lsat.Subsystem, intercept, lsat.UseLogger) lnd.AddSubLogger(root, l402.Subsystem, intercept, l402.UseLogger)
lnd.AddSubLogger( lnd.AddSubLogger(
root, liquidity.Subsystem, intercept, liquidity.UseLogger, root, liquidity.Subsystem, intercept, liquidity.UseLogger,
) )

@ -69,6 +69,10 @@ var RequiredPermissions = map[string][]bakery.Op{
Entity: "loop", Entity: "loop",
Action: "in", Action: "in",
}}, }},
"/looprpc.SwapClient/GetL402Tokens": {{
Entity: "auth",
Action: "read",
}},
"/looprpc.SwapClient/GetLsatTokens": {{ "/looprpc.SwapClient/GetLsatTokens": {{
Entity: "auth", Entity: "auth",
Action: "read", Action: "read",
@ -108,4 +112,8 @@ var RequiredPermissions = map[string][]bakery.Op{
Entity: "swap", Entity: "swap",
Action: "read", Action: "read",
}}, }},
"/looprpc.SwapClient/ListInstantOuts": {{
Entity: "swap",
Action: "read",
}},
} }

@ -96,6 +96,7 @@ func NewListenerConfig(config *Config, rpcCfg RPCConfig) *ListenerCfg {
BlockUntilChainSynced: true, BlockUntilChainSynced: true,
CallerCtx: callerCtx, CallerCtx: callerCtx,
BlockUntilUnlocked: true, BlockUntilUnlocked: true,
RPCTimeout: cfg.RPCTimeout,
} }
// If a custom lnd connection is specified we use that // If a custom lnd connection is specified we use that

@ -15,7 +15,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/instantout" "github.com/lightninglabs/loop/instantout"
@ -101,6 +101,17 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
log.Infof("Loop out request received") log.Infof("Loop out request received")
// Note that LoopOutRequest.PaymentTimeout is unsigned and therefore
// cannot be negative.
paymentTimeout := time.Duration(in.PaymentTimeout) * time.Second
// Make sure we don't exceed the total allowed payment timeout.
if paymentTimeout > s.config.TotalPaymentTimeout {
return nil, fmt.Errorf("payment timeout %v exceeds maximum "+
"allowed timeout of %v", paymentTimeout,
s.config.TotalPaymentTimeout)
}
var sweepAddr btcutil.Address var sweepAddr btcutil.Address
var isExternalAddr bool var isExternalAddr bool
var err error var err error
@ -184,6 +195,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
SwapPublicationDeadline: publicationDeadline, SwapPublicationDeadline: publicationDeadline,
Label: in.Label, Label: in.Label,
Initiator: in.Initiator, Initiator: in.Initiator,
PaymentTimeout: paymentTimeout,
} }
switch { switch {
@ -920,18 +932,18 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
return response, nil return response, nil
} }
// GetLsatTokens returns all tokens that are contained in the LSAT token store. // GetL402Tokens returns all tokens that are contained in the L402 token store.
func (s *swapClientServer) GetLsatTokens(ctx context.Context, func (s *swapClientServer) GetL402Tokens(ctx context.Context,
_ *clientrpc.TokensRequest) (*clientrpc.TokensResponse, error) { _ *clientrpc.TokensRequest) (*clientrpc.TokensResponse, error) {
log.Infof("Get LSAT tokens request received") log.Infof("Get L402 tokens request received")
tokens, err := s.impl.LsatStore.AllTokens() tokens, err := s.impl.L402Store.AllTokens()
if err != nil { if err != nil {
return nil, err return nil, err
} }
rpcTokens := make([]*clientrpc.LsatToken, len(tokens)) rpcTokens := make([]*clientrpc.L402Token, len(tokens))
idx := 0 idx := 0
for key, token := range tokens { for key, token := range tokens {
macBytes, err := token.BaseMacaroon().MarshalBinary() macBytes, err := token.BaseMacaroon().MarshalBinary()
@ -939,13 +951,13 @@ func (s *swapClientServer) GetLsatTokens(ctx context.Context,
return nil, err return nil, err
} }
id, err := lsat.DecodeIdentifier( id, err := l402.DecodeIdentifier(
bytes.NewReader(token.BaseMacaroon().Id()), bytes.NewReader(token.BaseMacaroon().Id()),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
rpcTokens[idx] = &clientrpc.LsatToken{ rpcTokens[idx] = &clientrpc.L402Token{
BaseMacaroon: macBytes, BaseMacaroon: macBytes,
PaymentHash: token.PaymentHash[:], PaymentHash: token.PaymentHash[:],
PaymentPreimage: token.Preimage[:], PaymentPreimage: token.Preimage[:],
@ -964,6 +976,21 @@ func (s *swapClientServer) GetLsatTokens(ctx context.Context,
return &clientrpc.TokensResponse{Tokens: rpcTokens}, nil return &clientrpc.TokensResponse{Tokens: rpcTokens}, nil
} }
// GetLsatTokens returns all tokens that are contained in the L402 token store.
// Deprecated: use GetL402Tokens.
// This API is provided to maintain backward compatibility with gRPC clients
// (e.g. `loop listauth`, Terminal Web, RTL).
// Type LsatToken used by GetLsatTokens in the past was renamed to L402Token,
// but this does not affect binary encoding, so we can use type L402Token here.
func (s *swapClientServer) GetLsatTokens(ctx context.Context,
req *clientrpc.TokensRequest) (*clientrpc.TokensResponse, error) {
log.Warnf("Received deprecated call GetLsatTokens. Please update the " +
"client software. Calling GetL402Tokens now.")
return s.GetL402Tokens(ctx, req)
}
// GetInfo returns basic information about the loop daemon and details to swaps // GetInfo returns basic information about the loop daemon and details to swaps
// from the swap store. // from the swap store.
func (s *swapClientServer) GetInfo(ctx context.Context, func (s *swapClientServer) GetInfo(ctx context.Context,
@ -1194,7 +1221,7 @@ func (s *swapClientServer) InstantOut(ctx context.Context,
} }
instantOutFsm, err := s.instantOutManager.NewInstantOut( instantOutFsm, err := s.instantOutManager.NewInstantOut(
ctx, reservationIds, ctx, reservationIds, req.DestAddr,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1231,6 +1258,47 @@ func (s *swapClientServer) InstantOutQuote(ctx context.Context,
}, nil }, nil
} }
// ListInstantOuts returns a list of all currently known instant out swaps and
// their current status.
func (s *swapClientServer) ListInstantOuts(ctx context.Context,
_ *clientrpc.ListInstantOutsRequest) (
*clientrpc.ListInstantOutsResponse, error) {
instantOuts, err := s.instantOutManager.ListInstantOuts(ctx)
if err != nil {
return nil, err
}
rpcSwaps := make([]*clientrpc.InstantOut, 0, len(instantOuts))
for _, instantOut := range instantOuts {
rpcSwaps = append(rpcSwaps, rpcInstantOut(instantOut))
}
return &clientrpc.ListInstantOutsResponse{
Swaps: rpcSwaps,
}, nil
}
func rpcInstantOut(instantOut *instantout.InstantOut) *clientrpc.InstantOut {
var sweepTxId string
if instantOut.SweepTxHash != nil {
sweepTxId = instantOut.SweepTxHash.String()
}
reservations := make([][]byte, len(instantOut.Reservations))
for i, res := range instantOut.Reservations {
reservations[i] = res.ID[:]
}
return &clientrpc.InstantOut{
SwapHash: instantOut.SwapHash[:],
State: string(instantOut.State),
Amount: uint64(instantOut.Value),
SweepTxId: sweepTxId,
ReservationIds: reservations,
}
}
func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) { func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {
switch reason { switch reason {
case liquidity.ReasonNone: case liquidity.ReasonNone:

@ -6,6 +6,7 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop" "github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/liquidity"
@ -21,19 +22,47 @@ func getClient(cfg *Config, swapDb loopdb.SwapStore,
sweeperDb sweepbatcher.BatcherStore, lnd *lndclient.LndServices) ( sweeperDb sweepbatcher.BatcherStore, lnd *lndclient.LndServices) (
*loop.Client, func(), error) { *loop.Client, func(), error) {
// Default is not set for MaxLSATCost and MaxLSATFee to distinguish
// it from user explicitly setting the option to default value.
// So if MaxL402Cost and MaxLSATFee are not set in the config file
// and command line, they are set to 0.
const (
defaultCost = l402.DefaultMaxCostSats
defaultFee = l402.DefaultMaxRoutingFeeSats
)
if cfg.MaxL402Cost != defaultCost && cfg.MaxLSATCost != 0 {
return nil, nil, fmt.Errorf("both maxl402cost and maxlsatcost" +
" were specified; they are not allowed together")
}
if cfg.MaxL402Fee != defaultFee && cfg.MaxLSATFee != 0 {
return nil, nil, fmt.Errorf("both maxl402fee and maxlsatfee" +
" were specified; they are not allowed together")
}
clientConfig := &loop.ClientConfig{ clientConfig := &loop.ClientConfig{
ServerAddress: cfg.Server.Host, ServerAddress: cfg.Server.Host,
ProxyAddress: cfg.Server.Proxy, ProxyAddress: cfg.Server.Proxy,
SwapServerNoTLS: cfg.Server.NoTLS, SwapServerNoTLS: cfg.Server.NoTLS,
TLSPathServer: cfg.Server.TLSPath, TLSPathServer: cfg.Server.TLSPath,
Lnd: lnd, Lnd: lnd,
MaxLsatCost: btcutil.Amount(cfg.MaxLSATCost), MaxL402Cost: btcutil.Amount(cfg.MaxL402Cost),
MaxLsatFee: btcutil.Amount(cfg.MaxLSATFee), MaxL402Fee: btcutil.Amount(cfg.MaxL402Fee),
LoopOutMaxParts: cfg.LoopOutMaxParts, LoopOutMaxParts: cfg.LoopOutMaxParts,
TotalPaymentTimeout: cfg.TotalPaymentTimeout, TotalPaymentTimeout: cfg.TotalPaymentTimeout,
MaxPaymentRetries: cfg.MaxPaymentRetries, MaxPaymentRetries: cfg.MaxPaymentRetries,
} }
if cfg.MaxL402Cost == defaultCost && cfg.MaxLSATCost != 0 {
log.Warnf("Option maxlsatcost is deprecated and will be " +
"removed. Switch to maxl402cost.")
clientConfig.MaxL402Cost = btcutil.Amount(cfg.MaxLSATCost)
}
if cfg.MaxL402Fee == defaultFee && cfg.MaxLSATFee != 0 {
log.Warnf("Option maxlsatfee is deprecated and will be " +
"removed. Switch to maxl402fee.")
clientConfig.MaxL402Fee = btcutil.Amount(cfg.MaxLSATFee)
}
swapClient, cleanUp, err := loop.NewClient( swapClient, cleanUp, err := loop.NewClient(
cfg.DataDir, swapDb, sweeperDb, clientConfig, cfg.DataDir, swapDb, sweeperDb, clientConfig,
) )
@ -56,9 +85,8 @@ func openDatabase(cfg *Config, chainParams *chaincfg.Params) (loopdb.SwapStore,
case DatabaseBackendSqlite: case DatabaseBackendSqlite:
log.Infof("Opening sqlite3 database at: %v", log.Infof("Opening sqlite3 database at: %v",
cfg.Sqlite.DatabaseFileName) cfg.Sqlite.DatabaseFileName)
db, err = loopdb.NewSqliteStore(
cfg.Sqlite, chainParams, db, err = loopdb.NewSqliteStore(cfg.Sqlite, chainParams)
)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -67,9 +95,8 @@ func openDatabase(cfg *Config, chainParams *chaincfg.Params) (loopdb.SwapStore,
case DatabaseBackendPostgres: case DatabaseBackendPostgres:
log.Infof("Opening postgres database at: %v", log.Infof("Opening postgres database at: %v",
cfg.Postgres.DSN(true)) cfg.Postgres.DSN(true))
db, err = loopdb.NewPostgresStore(
cfg.Postgres, chainParams, db, err = loopdb.NewPostgresStore(cfg.Postgres, chainParams)
)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -79,9 +106,6 @@ func openDatabase(cfg *Config, chainParams *chaincfg.Params) (loopdb.SwapStore,
return nil, nil, fmt.Errorf("unknown database backend: %s", return nil, nil, fmt.Errorf("unknown database backend: %s",
cfg.DatabaseBackend) cfg.DatabaseBackend)
} }
if err != nil {
return nil, nil, fmt.Errorf("unable to open database: %v", err)
}
return db, &baseDb, nil return db, &baseDb, nil
} }

@ -32,7 +32,10 @@ func view(config *Config, lisCfg *ListenerCfg) error {
return err return err
} }
sweeperDb := sweepbatcher.NewSQLStore(baseDb, chainParams) sweeperDb := sweepbatcher.NewSQLStore(
loopdb.NewTypedStore[sweepbatcher.Querier](baseDb),
chainParams,
)
swapClient, cleanup, err := getClient( swapClient, cleanup, err := getClient(
config, swapDb, sweeperDb, &lnd.LndServices, config, swapDb, sweeperDb, &lnd.LndServices,

@ -65,6 +65,18 @@ type SwapStore interface {
// it's decoding using the proto package's `Unmarshal` method. // it's decoding using the proto package's `Unmarshal` method.
FetchLiquidityParams(ctx context.Context) ([]byte, error) FetchLiquidityParams(ctx context.Context) ([]byte, error)
// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of
// loop out swaps.
BatchUpdateLoopOutSwapCosts(ctx context.Context,
swaps map[lntypes.Hash]SwapCost) error
// HasMigration returns true if the migration with the given ID has
// been done.
HasMigration(ctx context.Context, migrationID string) (bool, error)
// SetMigration marks the migration with the given ID as done.
SetMigration(ctx context.Context, migrationID string) error
// Close closes the underlying database. // Close closes the underlying database.
Close() error Close() error
} }

@ -61,6 +61,10 @@ type LoopOutContract struct {
// allow the server to delay the publication in exchange for possibly // allow the server to delay the publication in exchange for possibly
// lower fees. // lower fees.
SwapPublicationDeadline time.Time SwapPublicationDeadline time.Time
// PaymentTimeout is the timeout for any individual off-chain payment
// attempt.
PaymentTimeout time.Duration
} }
// ChannelSet stores a set of channels. // ChannelSet stores a set of channels.

@ -32,7 +32,7 @@ const (
ProtocolVersionUserExpiryLoopOut ProtocolVersion = 4 ProtocolVersionUserExpiryLoopOut ProtocolVersion = 4
// ProtocolVersionHtlcV2 indicates that the client will use the new // ProtocolVersionHtlcV2 indicates that the client will use the new
// HTLC v2 scrips for swaps. // HTLC v2 scripts for swaps.
ProtocolVersionHtlcV2 ProtocolVersion = 5 ProtocolVersionHtlcV2 ProtocolVersion = 5
// ProtocolVersionMultiLoopIn indicates that the client creates a probe // ProtocolVersionMultiLoopIn indicates that the client creates a probe

@ -18,13 +18,13 @@ import (
) )
// FetchLoopOutSwaps returns all swaps currently in the store. // FetchLoopOutSwaps returns all swaps currently in the store.
func (s *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut, func (db *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut,
error) { error) {
var loopOuts []*LoopOut var loopOuts []*LoopOut
err := s.ExecTx(ctx, NewSqlReadOpts(), func(*sqlc.Queries) error { err := db.ExecTx(ctx, NewSqlReadOpts(), func(tx *sqlc.Queries) error {
swaps, err := s.Queries.GetLoopOutSwaps(ctx) swaps, err := tx.GetLoopOutSwaps(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -32,7 +32,7 @@ func (s *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut,
loopOuts = make([]*LoopOut, len(swaps)) loopOuts = make([]*LoopOut, len(swaps))
for i, swap := range swaps { for i, swap := range swaps {
updates, err := s.Queries.GetSwapUpdates( updates, err := tx.GetSwapUpdates(
ctx, swap.SwapHash, ctx, swap.SwapHash,
) )
if err != nil { if err != nil {
@ -40,7 +40,7 @@ func (s *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut,
} }
loopOut, err := ConvertLoopOutRow( loopOut, err := ConvertLoopOutRow(
s.network, sqlc.GetLoopOutSwapRow(swap), db.network, sqlc.GetLoopOutSwapRow(swap),
updates, updates,
) )
if err != nil { if err != nil {
@ -60,24 +60,24 @@ func (s *BaseDB) FetchLoopOutSwaps(ctx context.Context) ([]*LoopOut,
} }
// FetchLoopOutSwap returns the loop out swap with the given hash. // FetchLoopOutSwap returns the loop out swap with the given hash.
func (s *BaseDB) FetchLoopOutSwap(ctx context.Context, func (db *BaseDB) FetchLoopOutSwap(ctx context.Context,
hash lntypes.Hash) (*LoopOut, error) { hash lntypes.Hash) (*LoopOut, error) {
var loopOut *LoopOut var loopOut *LoopOut
err := s.ExecTx(ctx, NewSqlReadOpts(), func(*sqlc.Queries) error { err := db.ExecTx(ctx, NewSqlReadOpts(), func(tx *sqlc.Queries) error {
swap, err := s.Queries.GetLoopOutSwap(ctx, hash[:]) swap, err := tx.GetLoopOutSwap(ctx, hash[:])
if err != nil { if err != nil {
return err return err
} }
updates, err := s.Queries.GetSwapUpdates(ctx, swap.SwapHash) updates, err := tx.GetSwapUpdates(ctx, swap.SwapHash)
if err != nil { if err != nil {
return err return err
} }
loopOut, err = ConvertLoopOutRow( loopOut, err = ConvertLoopOutRow(
s.network, swap, updates, db.network, swap, updates,
) )
if err != nil { if err != nil {
return err return err
@ -93,11 +93,11 @@ func (s *BaseDB) FetchLoopOutSwap(ctx context.Context,
} }
// CreateLoopOut adds an initiated swap to the store. // CreateLoopOut adds an initiated swap to the store.
func (s *BaseDB) CreateLoopOut(ctx context.Context, hash lntypes.Hash, func (db *BaseDB) CreateLoopOut(ctx context.Context, hash lntypes.Hash,
swap *LoopOutContract) error { swap *LoopOutContract) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
insertArgs := loopToInsertArgs( insertArgs := loopToInsertArgs(
hash, &swap.SwapContract, hash, &swap.SwapContract,
) )
@ -131,11 +131,11 @@ func (s *BaseDB) CreateLoopOut(ctx context.Context, hash lntypes.Hash,
} }
// BatchCreateLoopOut adds multiple initiated swaps to the store. // BatchCreateLoopOut adds multiple initiated swaps to the store.
func (s *BaseDB) BatchCreateLoopOut(ctx context.Context, func (db *BaseDB) BatchCreateLoopOut(ctx context.Context,
swaps map[lntypes.Hash]*LoopOutContract) error { swaps map[lntypes.Hash]*LoopOutContract) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
for swapHash, swap := range swaps { for swapHash, swap := range swaps {
swap := swap swap := swap
@ -174,20 +174,20 @@ func (s *BaseDB) BatchCreateLoopOut(ctx context.Context,
// UpdateLoopOut stores a new event for a target loop out swap. This // UpdateLoopOut stores a new event for a target loop out swap. This
// appends to the event log for a particular swap as it goes through // appends to the event log for a particular swap as it goes through
// the various stages in its lifetime. // the various stages in its lifetime.
func (s *BaseDB) UpdateLoopOut(ctx context.Context, hash lntypes.Hash, func (db *BaseDB) UpdateLoopOut(ctx context.Context, hash lntypes.Hash,
time time.Time, state SwapStateData) error { time time.Time, state SwapStateData) error {
return s.updateLoop(ctx, hash, time, state) return db.updateLoop(ctx, hash, time, state)
} }
// FetchLoopInSwaps returns all swaps currently in the store. // FetchLoopInSwaps returns all swaps currently in the store.
func (s *BaseDB) FetchLoopInSwaps(ctx context.Context) ( func (db *BaseDB) FetchLoopInSwaps(ctx context.Context) (
[]*LoopIn, error) { []*LoopIn, error) {
var loopIns []*LoopIn var loopIns []*LoopIn
err := s.ExecTx(ctx, NewSqlReadOpts(), func(*sqlc.Queries) error { err := db.ExecTx(ctx, NewSqlReadOpts(), func(tx *sqlc.Queries) error {
swaps, err := s.Queries.GetLoopInSwaps(ctx) swaps, err := tx.GetLoopInSwaps(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -195,12 +195,12 @@ func (s *BaseDB) FetchLoopInSwaps(ctx context.Context) (
loopIns = make([]*LoopIn, len(swaps)) loopIns = make([]*LoopIn, len(swaps))
for i, swap := range swaps { for i, swap := range swaps {
updates, err := s.Queries.GetSwapUpdates(ctx, swap.SwapHash) updates, err := tx.GetSwapUpdates(ctx, swap.SwapHash)
if err != nil { if err != nil {
return err return err
} }
loopIn, err := s.convertLoopInRow( loopIn, err := db.convertLoopInRow(
swap, updates, swap, updates,
) )
if err != nil { if err != nil {
@ -220,11 +220,11 @@ func (s *BaseDB) FetchLoopInSwaps(ctx context.Context) (
} }
// CreateLoopIn adds an initiated swap to the store. // CreateLoopIn adds an initiated swap to the store.
func (s *BaseDB) CreateLoopIn(ctx context.Context, hash lntypes.Hash, func (db *BaseDB) CreateLoopIn(ctx context.Context, hash lntypes.Hash,
swap *LoopInContract) error { swap *LoopInContract) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
insertArgs := loopToInsertArgs( insertArgs := loopToInsertArgs(
hash, &swap.SwapContract, hash, &swap.SwapContract,
) )
@ -257,11 +257,11 @@ func (s *BaseDB) CreateLoopIn(ctx context.Context, hash lntypes.Hash,
} }
// BatchCreateLoopIn adds multiple initiated swaps to the store. // BatchCreateLoopIn adds multiple initiated swaps to the store.
func (s *BaseDB) BatchCreateLoopIn(ctx context.Context, func (db *BaseDB) BatchCreateLoopIn(ctx context.Context,
swaps map[lntypes.Hash]*LoopInContract) error { swaps map[lntypes.Hash]*LoopInContract) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
for swapHash, swap := range swaps { for swapHash, swap := range swaps {
swap := swap swap := swap
@ -301,10 +301,10 @@ func (s *BaseDB) BatchCreateLoopIn(ctx context.Context,
// UpdateLoopIn stores a new event for a target loop in swap. This // UpdateLoopIn stores a new event for a target loop in swap. This
// appends to the event log for a particular swap as it goes through // appends to the event log for a particular swap as it goes through
// the various stages in its lifetime. // the various stages in its lifetime.
func (s *BaseDB) UpdateLoopIn(ctx context.Context, hash lntypes.Hash, func (db *BaseDB) UpdateLoopIn(ctx context.Context, hash lntypes.Hash,
time time.Time, state SwapStateData) error { time time.Time, state SwapStateData) error {
return s.updateLoop(ctx, hash, time, state) return db.updateLoop(ctx, hash, time, state)
} }
// PutLiquidityParams writes the serialized `manager.Parameters` bytes // PutLiquidityParams writes the serialized `manager.Parameters` bytes
@ -312,10 +312,10 @@ func (s *BaseDB) UpdateLoopIn(ctx context.Context, hash lntypes.Hash,
// //
// NOTE: it's the caller's responsibility to encode the param. Atm, // NOTE: it's the caller's responsibility to encode the param. Atm,
// it's encoding using the proto package's `Marshal` method. // it's encoding using the proto package's `Marshal` method.
func (s *BaseDB) PutLiquidityParams(ctx context.Context, func (db *BaseDB) PutLiquidityParams(ctx context.Context,
params []byte) error { params []byte) error {
err := s.Queries.UpsertLiquidityParams(ctx, params) err := db.Queries.UpsertLiquidityParams(ctx, params)
if err != nil { if err != nil {
return err return err
} }
@ -328,11 +328,11 @@ func (s *BaseDB) PutLiquidityParams(ctx context.Context,
// //
// NOTE: it's the caller's responsibility to decode the param. Atm, // NOTE: it's the caller's responsibility to decode the param. Atm,
// it's decoding using the proto package's `Unmarshal` method. // it's decoding using the proto package's `Unmarshal` method.
func (s *BaseDB) FetchLiquidityParams(ctx context.Context) ([]byte, func (db *BaseDB) FetchLiquidityParams(ctx context.Context) ([]byte,
error) { error) {
var params []byte var params []byte
params, err := s.Queries.FetchLiquidityParams(ctx) params, err := db.Queries.FetchLiquidityParams(ctx)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return params, nil return params, nil
} else if err != nil { } else if err != nil {
@ -348,11 +348,11 @@ var _ SwapStore = (*BaseDB)(nil)
// updateLoop updates the swap with the given hash by inserting a new update // updateLoop updates the swap with the given hash by inserting a new update
// in the swap_updates table. // in the swap_updates table.
func (s *BaseDB) updateLoop(ctx context.Context, hash lntypes.Hash, func (db *BaseDB) updateLoop(ctx context.Context, hash lntypes.Hash,
time time.Time, state SwapStateData) error { time time.Time, state SwapStateData) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
updateParams := sqlc.InsertSwapUpdateParams{ updateParams := sqlc.InsertSwapUpdateParams{
SwapHash: hash[:], SwapHash: hash[:],
UpdateTimestamp: time.UTC(), UpdateTimestamp: time.UTC(),
@ -376,11 +376,11 @@ func (s *BaseDB) updateLoop(ctx context.Context, hash lntypes.Hash,
} }
// BatchInsertUpdate inserts multiple swap updates to the store. // BatchInsertUpdate inserts multiple swap updates to the store.
func (s *BaseDB) BatchInsertUpdate(ctx context.Context, func (db *BaseDB) BatchInsertUpdate(ctx context.Context,
updateData map[lntypes.Hash][]BatchInsertUpdateData) error { updateData map[lntypes.Hash][]BatchInsertUpdateData) error {
writeOpts := &SqliteTxOptions{} writeOpts := NewSqlWriteOpts()
return s.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error { return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
for swapHash, updates := range updateData { for swapHash, updates := range updateData {
for _, update := range updates { for _, update := range updates {
updateParams := sqlc.InsertSwapUpdateParams{ updateParams := sqlc.InsertSwapUpdateParams{
@ -407,6 +407,61 @@ func (s *BaseDB) BatchInsertUpdate(ctx context.Context,
}) })
} }
// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out
// swaps.
func (db *BaseDB) BatchUpdateLoopOutSwapCosts(ctx context.Context,
costs map[lntypes.Hash]SwapCost) error {
writeOpts := NewSqlWriteOpts()
return db.ExecTx(ctx, writeOpts, func(tx *sqlc.Queries) error {
for swapHash, cost := range costs {
lastUpdateID, err := tx.GetLastUpdateID(
ctx, swapHash[:],
)
if err != nil {
return err
}
err = tx.OverrideSwapCosts(
ctx, sqlc.OverrideSwapCostsParams{
ID: lastUpdateID,
ServerCost: int64(cost.Server),
OnchainCost: int64(cost.Onchain),
OffchainCost: int64(cost.Offchain),
},
)
if err != nil {
return err
}
}
return nil
})
}
// HasMigration returns true if the migration with the given ID has been done.
func (db *BaseDB) HasMigration(ctx context.Context, migrationID string) (
bool, error) {
migration, err := db.GetMigration(ctx, migrationID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return false, err
}
return migration.MigrationTs.Valid, nil
}
// SetMigration marks the migration with the given ID as done.
func (db *BaseDB) SetMigration(ctx context.Context, migrationID string) error {
return db.InsertMigration(ctx, sqlc.InsertMigrationParams{
MigrationID: migrationID,
MigrationTs: sql.NullTime{
Time: time.Now().UTC(),
Valid: true,
},
})
}
// loopToInsertArgs converts a SwapContract struct to the arguments needed to // loopToInsertArgs converts a SwapContract struct to the arguments needed to
// insert it into the database. // insert it into the database.
func loopToInsertArgs(hash lntypes.Hash, func loopToInsertArgs(hash lntypes.Hash,
@ -443,6 +498,7 @@ func loopOutToInsertArgs(hash lntypes.Hash,
PrepayInvoice: loopOut.PrepayInvoice, PrepayInvoice: loopOut.PrepayInvoice,
MaxPrepayRoutingFee: int64(loopOut.MaxPrepayRoutingFee), MaxPrepayRoutingFee: int64(loopOut.MaxPrepayRoutingFee),
PublicationDeadline: loopOut.SwapPublicationDeadline.UTC(), PublicationDeadline: loopOut.SwapPublicationDeadline.UTC(),
PaymentTimeout: int32(loopOut.PaymentTimeout.Seconds()),
} }
} }
@ -536,6 +592,9 @@ func ConvertLoopOutRow(network *chaincfg.Params, row sqlc.GetLoopOutSwapRow,
PrepayInvoice: row.PrepayInvoice, PrepayInvoice: row.PrepayInvoice,
MaxPrepayRoutingFee: btcutil.Amount(row.MaxPrepayRoutingFee), MaxPrepayRoutingFee: btcutil.Amount(row.MaxPrepayRoutingFee),
SwapPublicationDeadline: row.PublicationDeadline, SwapPublicationDeadline: row.PublicationDeadline,
PaymentTimeout: time.Duration(
row.PaymentTimeout,
) * time.Second,
}, },
Loop: Loop{ Loop: Loop{
Hash: swapHash, Hash: swapHash,
@ -568,7 +627,7 @@ func ConvertLoopOutRow(network *chaincfg.Params, row sqlc.GetLoopOutSwapRow,
// convertLoopInRow converts a database row containing a loop in swap to a // convertLoopInRow converts a database row containing a loop in swap to a
// LoopIn struct. // LoopIn struct.
func (s *BaseDB) convertLoopInRow(row sqlc.GetLoopInSwapsRow, func (db *BaseDB) convertLoopInRow(row sqlc.GetLoopInSwapsRow,
updates []sqlc.SwapUpdate) (*LoopIn, error) { updates []sqlc.SwapUpdate) (*LoopIn, error) {
htlcKeys, err := fetchHtlcKeys( htlcKeys, err := fetchHtlcKeys(

@ -13,6 +13,7 @@ import (
"github.com/lightninglabs/loop/loopdb/sqlc" "github.com/lightninglabs/loop/loopdb/sqlc"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -60,6 +61,7 @@ func TestSqliteLoopOutStore(t *testing.T) {
SweepConfTarget: 2, SweepConfTarget: 2,
HtlcConfirmations: 2, HtlcConfirmations: 2,
SwapPublicationDeadline: initiationTime, SwapPublicationDeadline: initiationTime,
PaymentTimeout: time.Second * 11,
} }
t.Run("no outgoing set", func(t *testing.T) { t.Run("no outgoing set", func(t *testing.T) {
@ -120,6 +122,8 @@ func testSqliteLoopOutStore(t *testing.T, pendingSwap *LoopOutContract) {
if expectedState == StatePreimageRevealed { if expectedState == StatePreimageRevealed {
require.NotNil(t, swap.State().HtlcTxHash) require.NotNil(t, swap.State().HtlcTxHash)
} }
require.Equal(t, time.Second*11, swap.Contract.PaymentTimeout)
} }
// If we create a new swap, then it should show up as being initialized // If we create a new swap, then it should show up as being initialized
@ -393,6 +397,140 @@ func TestIssue615(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
} }
// TestBatchUpdateCost tests that we can batch update the cost of multiple swaps
// at once.
func TestBatchUpdateCost(t *testing.T) {
// Create a new sqlite store for testing.
store := NewTestDB(t)
destAddr := test.GetDestAddr(t, 0)
initiationTime := time.Date(2018, 11, 1, 0, 0, 0, 0, time.UTC)
testContract := LoopOutContract{
SwapContract: SwapContract{
AmountRequested: 100,
CltvExpiry: 144,
HtlcKeys: HtlcKeys{
SenderScriptKey: senderKey,
ReceiverScriptKey: receiverKey,
SenderInternalPubKey: senderInternalKey,
ReceiverInternalPubKey: receiverInternalKey,
ClientScriptKeyLocator: keychain.KeyLocator{
Family: 1,
Index: 2,
},
},
MaxMinerFee: 10,
MaxSwapFee: 20,
InitiationHeight: 99,
InitiationTime: initiationTime,
ProtocolVersion: ProtocolVersionMuSig2,
},
MaxPrepayRoutingFee: 40,
PrepayInvoice: "prepayinvoice",
DestAddr: destAddr,
SwapInvoice: "swapinvoice",
MaxSwapRoutingFee: 30,
SweepConfTarget: 2,
HtlcConfirmations: 2,
SwapPublicationDeadline: initiationTime,
PaymentTimeout: time.Second * 11,
}
makeSwap := func(preimage lntypes.Preimage) *LoopOutContract {
contract := testContract
contract.Preimage = preimage
return &contract
}
// Next, we'll add two swaps to the database.
preimage1 := testPreimage
preimage2 := lntypes.Preimage{4, 4, 4}
ctxb := context.Background()
swap1 := makeSwap(preimage1)
swap2 := makeSwap(preimage2)
hash1 := swap1.Preimage.Hash()
err := store.CreateLoopOut(ctxb, hash1, swap1)
require.NoError(t, err)
hash2 := swap2.Preimage.Hash()
err = store.CreateLoopOut(ctxb, hash2, swap2)
require.NoError(t, err)
// Add an update to both swaps containing the cost.
err = store.UpdateLoopOut(
ctxb, hash1, testTime,
SwapStateData{
State: StateSuccess,
Cost: SwapCost{
Server: 1,
Onchain: 2,
Offchain: 3,
},
},
)
require.NoError(t, err)
err = store.UpdateLoopOut(
ctxb, hash2, testTime,
SwapStateData{
State: StateSuccess,
Cost: SwapCost{
Server: 4,
Onchain: 5,
Offchain: 6,
},
},
)
require.NoError(t, err)
updateMap := map[lntypes.Hash]SwapCost{
hash1: {
Server: 2,
Onchain: 3,
Offchain: 4,
},
hash2: {
Server: 6,
Onchain: 7,
Offchain: 8,
},
}
require.NoError(t, store.BatchUpdateLoopOutSwapCosts(ctxb, updateMap))
swaps, err := store.FetchLoopOutSwaps(ctxb)
require.NoError(t, err)
require.Len(t, swaps, 2)
swapsMap := make(map[lntypes.Hash]*LoopOut)
swapsMap[swaps[0].Hash] = swaps[0]
swapsMap[swaps[1].Hash] = swaps[1]
require.Equal(t, updateMap[hash1], swapsMap[hash1].State().Cost)
require.Equal(t, updateMap[hash2], swapsMap[hash2].State().Cost)
}
// TestMigrationTracker tests the migration tracker functionality.
func TestMigrationTracker(t *testing.T) {
ctxb := context.Background()
// Create a new sqlite store for testing.
sqlDB := NewTestDB(t)
hasMigration, err := sqlDB.HasMigration(ctxb, "test")
require.NoError(t, err)
require.False(t, hasMigration)
require.NoError(t, sqlDB.SetMigration(ctxb, "test"))
hasMigration, err = sqlDB.HasMigration(ctxb, "test")
require.NoError(t, err)
require.True(t, hasMigration)
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func randomString(length int) string { func randomString(length int) string {

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
// source: batch.sql // source: batch.sql
package sqlc package sqlc
@ -8,7 +8,6 @@ package sqlc
import ( import (
"context" "context"
"database/sql" "database/sql"
"time"
) )
const confirmBatch = `-- name: ConfirmBatch :exec const confirmBatch = `-- name: ConfirmBatch :exec
@ -25,74 +24,35 @@ func (q *Queries) ConfirmBatch(ctx context.Context, id int32) error {
return err return err
} }
const dropBatch = `-- name: DropBatch :exec
DELETE FROM sweep_batches WHERE id = $1
`
func (q *Queries) DropBatch(ctx context.Context, id int32) error {
_, err := q.db.ExecContext(ctx, dropBatch, id)
return err
}
const getBatchSweeps = `-- name: GetBatchSweeps :many const getBatchSweeps = `-- name: GetBatchSweeps :many
SELECT SELECT
sweeps.id, sweeps.swap_hash, sweeps.batch_id, sweeps.outpoint_txid, sweeps.outpoint_index, sweeps.amt, sweeps.completed, id, swap_hash, batch_id, outpoint_txid, outpoint_index, amt, completed
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
FROM FROM
sweeps sweeps
JOIN
swaps ON sweeps.swap_hash = swaps.swap_hash
JOIN
loopout_swaps ON sweeps.swap_hash = loopout_swaps.swap_hash
JOIN
htlc_keys ON sweeps.swap_hash = htlc_keys.swap_hash
WHERE WHERE
sweeps.batch_id = $1 batch_id = $1
ORDER BY ORDER BY
sweeps.id ASC id ASC
` `
type GetBatchSweepsRow struct { func (q *Queries) GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error) {
ID int32
SwapHash []byte
BatchID int32
OutpointTxid []byte
OutpointIndex int32
Amt int64
Completed bool
ID_2 int32
SwapHash_2 []byte
Preimage []byte
InitiationTime time.Time
AmountRequested int64
CltvExpiry int32
MaxMinerFee int64
MaxSwapFee int64
InitiationHeight int32
ProtocolVersion int32
Label string
SwapHash_3 []byte
DestAddress string
SwapInvoice string
MaxSwapRoutingFee int64
SweepConfTarget int32
HtlcConfirmations int32
OutgoingChanSet string
PrepayInvoice string
MaxPrepayRoutingFee int64
PublicationDeadline time.Time
SingleSweep bool
SwapHash_4 []byte
SenderScriptPubkey []byte
ReceiverScriptPubkey []byte
SenderInternalPubkey []byte
ReceiverInternalPubkey []byte
ClientKeyFamily int32
ClientKeyIndex int32
}
func (q *Queries) GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatchSweepsRow, error) {
rows, err := q.db.QueryContext(ctx, getBatchSweeps, batchID) rows, err := q.db.QueryContext(ctx, getBatchSweeps, batchID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var items []GetBatchSweepsRow var items []Sweep
for rows.Next() { for rows.Next() {
var i GetBatchSweepsRow var i Sweep
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.SwapHash, &i.SwapHash,
@ -101,35 +61,6 @@ func (q *Queries) GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatch
&i.OutpointIndex, &i.OutpointIndex,
&i.Amt, &i.Amt,
&i.Completed, &i.Completed,
&i.ID_2,
&i.SwapHash_2,
&i.Preimage,
&i.InitiationTime,
&i.AmountRequested,
&i.CltvExpiry,
&i.MaxMinerFee,
&i.MaxSwapFee,
&i.InitiationHeight,
&i.ProtocolVersion,
&i.Label,
&i.SwapHash_3,
&i.DestAddress,
&i.SwapInvoice,
&i.MaxSwapRoutingFee,
&i.SweepConfTarget,
&i.HtlcConfirmations,
&i.OutgoingChanSet,
&i.PrepayInvoice,
&i.MaxPrepayRoutingFee,
&i.PublicationDeadline,
&i.SingleSweep,
&i.SwapHash_4,
&i.SenderScriptPubkey,
&i.ReceiverScriptPubkey,
&i.SenderInternalPubkey,
&i.ReceiverInternalPubkey,
&i.ClientKeyFamily,
&i.ClientKeyIndex,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@ -144,6 +75,54 @@ func (q *Queries) GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatch
return items, nil return items, nil
} }
const getBatchSweptAmount = `-- name: GetBatchSweptAmount :one
SELECT
SUM(amt) AS total
FROM
sweeps
WHERE
batch_id = $1
AND
completed = TRUE
`
func (q *Queries) GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error) {
row := q.db.QueryRowContext(ctx, getBatchSweptAmount, batchID)
var total int64
err := row.Scan(&total)
return total, err
}
const getParentBatch = `-- name: GetParentBatch :one
SELECT
sweep_batches.id, sweep_batches.confirmed, sweep_batches.batch_tx_id, sweep_batches.batch_pk_script, sweep_batches.last_rbf_height, sweep_batches.last_rbf_sat_per_kw, sweep_batches.max_timeout_distance
FROM
sweep_batches
JOIN
sweeps ON sweep_batches.id = sweeps.batch_id
WHERE
sweeps.swap_hash = $1
AND
sweeps.completed = TRUE
AND
sweep_batches.confirmed = TRUE
`
func (q *Queries) GetParentBatch(ctx context.Context, swapHash []byte) (SweepBatch, error) {
row := q.db.QueryRowContext(ctx, getParentBatch, swapHash)
var i SweepBatch
err := row.Scan(
&i.ID,
&i.Confirmed,
&i.BatchTxID,
&i.BatchPkScript,
&i.LastRbfHeight,
&i.LastRbfSatPerKw,
&i.MaxTimeoutDistance,
)
return i, err
}
const getSweepStatus = `-- name: GetSweepStatus :one const getSweepStatus = `-- name: GetSweepStatus :one
SELECT SELECT
COALESCE(s.completed, f.false_value) AS completed COALESCE(s.completed, f.false_value) AS completed

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
package sqlc package sqlc

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
// source: instantout.sql // source: instantout.sql
package sqlc package sqlc
@ -133,7 +133,7 @@ func (q *Queries) GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte)
} }
const getInstantOutSwaps = `-- name: GetInstantOutSwaps :many const getInstantOutSwaps = `-- name: GetInstantOutSwaps :many
SELECT SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
instantout_swaps.swap_hash, instantout_swaps.preimage, instantout_swaps.sweep_address, instantout_swaps.outgoing_chan_set, instantout_swaps.htlc_fee_rate, instantout_swaps.reservation_ids, instantout_swaps.swap_invoice, instantout_swaps.finalized_htlc_tx, instantout_swaps.sweep_tx_hash, instantout_swaps.finalized_sweepless_sweep_tx, instantout_swaps.sweep_confirmation_height, instantout_swaps.swap_hash, instantout_swaps.preimage, instantout_swaps.sweep_address, instantout_swaps.outgoing_chan_set, instantout_swaps.htlc_fee_rate, instantout_swaps.reservation_ids, instantout_swaps.swap_invoice, instantout_swaps.finalized_htlc_tx, instantout_swaps.sweep_tx_hash, instantout_swaps.finalized_sweepless_sweep_tx, instantout_swaps.sweep_confirmation_height,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
@ -235,7 +235,7 @@ func (q *Queries) GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsR
const insertInstantOut = `-- name: InsertInstantOut :exec const insertInstantOut = `-- name: InsertInstantOut :exec
INSERT INTO instantout_swaps ( INSERT INTO instantout_swaps (
swap_hash, swap_hash,
preimage, preimage,
sweep_address, sweep_address,
outgoing_chan_set, outgoing_chan_set,
htlc_fee_rate, htlc_fee_rate,

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
// source: liquidity_params.sql // source: liquidity_params.sql
package sqlc package sqlc

@ -0,0 +1,45 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.25.0
// source: migration_tracker.sql
package sqlc
import (
"context"
"database/sql"
)
const getMigration = `-- name: GetMigration :one
SELECT
migration_id,
migration_ts
FROM
migration_tracker
WHERE
migration_id = $1
`
func (q *Queries) GetMigration(ctx context.Context, migrationID string) (MigrationTracker, error) {
row := q.db.QueryRowContext(ctx, getMigration, migrationID)
var i MigrationTracker
err := row.Scan(&i.MigrationID, &i.MigrationTs)
return i, err
}
const insertMigration = `-- name: InsertMigration :exec
INSERT INTO migration_tracker (
migration_id,
migration_ts
) VALUES ($1, $2)
`
type InsertMigrationParams struct {
MigrationID string
MigrationTs sql.NullTime
}
func (q *Queries) InsertMigration(ctx context.Context, arg InsertMigrationParams) error {
_, err := q.db.ExecContext(ctx, insertMigration, arg.MigrationID, arg.MigrationTs)
return err
}

@ -0,0 +1,3 @@
-- payment_timeout is the timeout in seconds for each individual off-chain
-- payment.
ALTER TABLE loopout_swaps DROP COLUMN payment_timeout;

@ -0,0 +1,3 @@
-- payment_timeout is the timeout in seconds for each individual off-chain
-- payment.
ALTER TABLE loopout_swaps ADD payment_timeout INTEGER NOT NULL DEFAULT 0;

@ -0,0 +1,9 @@
CREATE TABLE migration_tracker (
-- migration_id is the id of the migration.
migration_id TEXT NOT NULL,
-- migration_ts is the timestamp at which the migration was run.
migration_ts TIMESTAMP,
PRIMARY KEY (migration_id)
);

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
package sqlc package sqlc
@ -64,6 +64,12 @@ type LoopoutSwap struct {
MaxPrepayRoutingFee int64 MaxPrepayRoutingFee int64
PublicationDeadline time.Time PublicationDeadline time.Time
SingleSweep bool SingleSweep bool
PaymentTimeout int32
}
type MigrationTracker struct {
MigrationID string
MigrationTs sql.NullTime
} }
type Reservation struct { type Reservation struct {

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
package sqlc package sqlc
@ -11,15 +11,20 @@ import (
type Querier interface { type Querier interface {
ConfirmBatch(ctx context.Context, id int32) error ConfirmBatch(ctx context.Context, id int32) error
CreateReservation(ctx context.Context, arg CreateReservationParams) error CreateReservation(ctx context.Context, arg CreateReservationParams) error
DropBatch(ctx context.Context, id int32) error
FetchLiquidityParams(ctx context.Context) ([]byte, error) FetchLiquidityParams(ctx context.Context) ([]byte, error)
GetBatchSweeps(ctx context.Context, batchID int32) ([]GetBatchSweepsRow, error) GetBatchSweeps(ctx context.Context, batchID int32) ([]Sweep, error)
GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error)
GetInstantOutSwap(ctx context.Context, swapHash []byte) (GetInstantOutSwapRow, error) GetInstantOutSwap(ctx context.Context, swapHash []byte) (GetInstantOutSwapRow, error)
GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte) ([]InstantoutUpdate, error) GetInstantOutSwapUpdates(ctx context.Context, swapHash []byte) ([]InstantoutUpdate, error)
GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsRow, error) GetInstantOutSwaps(ctx context.Context) ([]GetInstantOutSwapsRow, error)
GetLastUpdateID(ctx context.Context, swapHash []byte) (int32, error)
GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error) GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopInSwapRow, error)
GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, error)
GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopOutSwapRow, error)
GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, error) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, error)
GetMigration(ctx context.Context, migrationID string) (MigrationTracker, error)
GetParentBatch(ctx context.Context, swapHash []byte) (SweepBatch, error)
GetReservation(ctx context.Context, reservationID []byte) (Reservation, error) GetReservation(ctx context.Context, reservationID []byte) (Reservation, error)
GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error) GetReservationUpdates(ctx context.Context, reservationID []byte) ([]ReservationUpdate, error)
GetReservations(ctx context.Context) ([]Reservation, error) GetReservations(ctx context.Context) ([]Reservation, error)
@ -32,9 +37,11 @@ type Querier interface {
InsertInstantOutUpdate(ctx context.Context, arg InsertInstantOutUpdateParams) error InsertInstantOutUpdate(ctx context.Context, arg InsertInstantOutUpdateParams) error
InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error InsertLoopIn(ctx context.Context, arg InsertLoopInParams) error
InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error
InsertMigration(ctx context.Context, arg InsertMigrationParams) error
InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error InsertReservationUpdate(ctx context.Context, arg InsertReservationUpdateParams) error
InsertSwap(ctx context.Context, arg InsertSwapParams) error InsertSwap(ctx context.Context, arg InsertSwapParams) error
InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdateParams) error
OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error
UpdateBatch(ctx context.Context, arg UpdateBatchParams) error UpdateBatch(ctx context.Context, arg UpdateBatchParams) error
UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error UpdateInstantOut(ctx context.Context, arg UpdateInstantOutParams) error
UpdateReservation(ctx context.Context, arg UpdateReservationParams) error UpdateReservation(ctx context.Context, arg UpdateReservationParams) error

@ -23,6 +23,9 @@ INSERT INTO sweep_batches (
$6 $6
) RETURNING id; ) RETURNING id;
-- name: DropBatch :exec
DELETE FROM sweep_batches WHERE id = $1;
-- name: UpdateBatch :exec -- name: UpdateBatch :exec
UPDATE sweep_batches SET UPDATE sweep_batches SET
confirmed = $2, confirmed = $2,
@ -62,25 +65,39 @@ INSERT INTO sweeps (
amt = $5, amt = $5,
completed = $6; completed = $6;
-- name: GetParentBatch :one
SELECT
sweep_batches.*
FROM
sweep_batches
JOIN
sweeps ON sweep_batches.id = sweeps.batch_id
WHERE
sweeps.swap_hash = $1
AND
sweeps.completed = TRUE
AND
sweep_batches.confirmed = TRUE;
-- name: GetBatchSweptAmount :one
SELECT
SUM(amt) AS total
FROM
sweeps
WHERE
batch_id = $1
AND
completed = TRUE;
-- name: GetBatchSweeps :many -- name: GetBatchSweeps :many
SELECT SELECT
sweeps.*, *
swaps.*,
loopout_swaps.*,
htlc_keys.*
FROM FROM
sweeps sweeps
JOIN
swaps ON sweeps.swap_hash = swaps.swap_hash
JOIN
loopout_swaps ON sweeps.swap_hash = loopout_swaps.swap_hash
JOIN
htlc_keys ON sweeps.swap_hash = htlc_keys.swap_hash
WHERE WHERE
sweeps.batch_id = $1 batch_id = $1
ORDER BY ORDER BY
sweeps.id ASC; id ASC;
-- name: GetSweepStatus :one -- name: GetSweepStatus :one
SELECT SELECT

@ -1,7 +1,7 @@
-- name: InsertInstantOut :exec -- name: InsertInstantOut :exec
INSERT INTO instantout_swaps ( INSERT INTO instantout_swaps (
swap_hash, swap_hash,
preimage, preimage,
sweep_address, sweep_address,
outgoing_chan_set, outgoing_chan_set,
htlc_fee_rate, htlc_fee_rate,
@ -53,7 +53,7 @@ WHERE
swaps.swap_hash = $1; swaps.swap_hash = $1;
-- name: GetInstantOutSwaps :many -- name: GetInstantOutSwaps :many
SELECT SELECT
swaps.*, swaps.*,
instantout_swaps.*, instantout_swaps.*,
htlc_keys.* htlc_keys.*

@ -0,0 +1,14 @@
-- name: InsertMigration :exec
INSERT INTO migration_tracker (
migration_id,
migration_ts
) VALUES ($1, $2);
-- name: GetMigration :one
SELECT
migration_id,
migration_ts
FROM
migration_tracker
WHERE
migration_id = $1;

@ -1,9 +1,9 @@
-- name: GetLoopOutSwaps :many -- name: GetLoopOutSwaps :many
SELECT SELECT
swaps.*, swaps.*,
loopout_swaps.*, loopout_swaps.*,
htlc_keys.* htlc_keys.*
FROM FROM
swaps swaps
JOIN JOIN
loopout_swaps ON swaps.swap_hash = loopout_swaps.swap_hash loopout_swaps ON swaps.swap_hash = loopout_swaps.swap_hash
@ -13,7 +13,7 @@ ORDER BY
swaps.id; swaps.id;
-- name: GetLoopOutSwap :one -- name: GetLoopOutSwap :one
SELECT SELECT
swaps.*, swaps.*,
loopout_swaps.*, loopout_swaps.*,
htlc_keys.* htlc_keys.*
@ -27,7 +27,7 @@ WHERE
swaps.swap_hash = $1; swaps.swap_hash = $1;
-- name: GetLoopInSwaps :many -- name: GetLoopInSwaps :many
SELECT SELECT
swaps.*, swaps.*,
loopin_swaps.*, loopin_swaps.*,
htlc_keys.* htlc_keys.*
@ -41,7 +41,7 @@ ORDER BY
swaps.id; swaps.id;
-- name: GetLoopInSwap :one -- name: GetLoopInSwap :one
SELECT SELECT
swaps.*, swaps.*,
loopin_swaps.*, loopin_swaps.*,
htlc_keys.* htlc_keys.*
@ -55,7 +55,7 @@ WHERE
swaps.swap_hash = $1; swaps.swap_hash = $1;
-- name: GetSwapUpdates :many -- name: GetSwapUpdates :many
SELECT SELECT
* *
FROM FROM
swap_updates swap_updates
@ -105,9 +105,10 @@ INSERT INTO loopout_swaps (
prepay_invoice, prepay_invoice,
max_prepay_routing_fee, max_prepay_routing_fee,
publication_deadline, publication_deadline,
single_sweep single_sweep,
payment_timeout
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
); );
-- name: InsertLoopIn :exec -- name: InsertLoopIn :exec
@ -131,4 +132,20 @@ INSERT INTO htlc_keys(
client_key_index client_key_index
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7 $1, $2, $3, $4, $5, $6, $7
); );
-- name: GetLastUpdateID :one
SELECT id
FROM swap_updates
WHERE swap_hash = $1
ORDER BY update_timestamp DESC
LIMIT 1;
-- name: OverrideSwapCosts :exec
UPDATE swap_updates
SET
server_cost = $2,
onchain_cost = $3,
offchain_cost = $4
WHERE id = $1;

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
// source: reservations.sql // source: reservations.sql
package sqlc package sqlc

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.17.2 // sqlc v1.25.0
// source: swaps.sql // source: swaps.sql
package sqlc package sqlc
@ -10,8 +10,23 @@ import (
"time" "time"
) )
const getLastUpdateID = `-- name: GetLastUpdateID :one
SELECT id
FROM swap_updates
WHERE swap_hash = $1
ORDER BY update_timestamp DESC
LIMIT 1
`
func (q *Queries) GetLastUpdateID(ctx context.Context, swapHash []byte) (int32, error) {
row := q.db.QueryRowContext(ctx, getLastUpdateID, swapHash)
var id int32
err := row.Scan(&id)
return id, err
}
const getLoopInSwap = `-- name: GetLoopInSwap :one const getLoopInSwap = `-- name: GetLoopInSwap :one
SELECT SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
loopin_swaps.swap_hash, loopin_swaps.htlc_conf_target, loopin_swaps.last_hop, loopin_swaps.external_htlc, loopin_swaps.swap_hash, loopin_swaps.htlc_conf_target, loopin_swaps.last_hop, loopin_swaps.external_htlc,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
@ -81,7 +96,7 @@ func (q *Queries) GetLoopInSwap(ctx context.Context, swapHash []byte) (GetLoopIn
} }
const getLoopInSwaps = `-- name: GetLoopInSwaps :many const getLoopInSwaps = `-- name: GetLoopInSwaps :many
SELECT SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
loopin_swaps.swap_hash, loopin_swaps.htlc_conf_target, loopin_swaps.last_hop, loopin_swaps.external_htlc, loopin_swaps.swap_hash, loopin_swaps.htlc_conf_target, loopin_swaps.last_hop, loopin_swaps.external_htlc,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
@ -167,9 +182,9 @@ func (q *Queries) GetLoopInSwaps(ctx context.Context) ([]GetLoopInSwapsRow, erro
} }
const getLoopOutSwap = `-- name: GetLoopOutSwap :one const getLoopOutSwap = `-- name: GetLoopOutSwap :one
SELECT SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep, loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep, loopout_swaps.payment_timeout,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
FROM FROM
swaps swaps
@ -204,6 +219,7 @@ type GetLoopOutSwapRow struct {
MaxPrepayRoutingFee int64 MaxPrepayRoutingFee int64
PublicationDeadline time.Time PublicationDeadline time.Time
SingleSweep bool SingleSweep bool
PaymentTimeout int32
SwapHash_3 []byte SwapHash_3 []byte
SenderScriptPubkey []byte SenderScriptPubkey []byte
ReceiverScriptPubkey []byte ReceiverScriptPubkey []byte
@ -239,6 +255,7 @@ func (q *Queries) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopO
&i.MaxPrepayRoutingFee, &i.MaxPrepayRoutingFee,
&i.PublicationDeadline, &i.PublicationDeadline,
&i.SingleSweep, &i.SingleSweep,
&i.PaymentTimeout,
&i.SwapHash_3, &i.SwapHash_3,
&i.SenderScriptPubkey, &i.SenderScriptPubkey,
&i.ReceiverScriptPubkey, &i.ReceiverScriptPubkey,
@ -251,11 +268,11 @@ func (q *Queries) GetLoopOutSwap(ctx context.Context, swapHash []byte) (GetLoopO
} }
const getLoopOutSwaps = `-- name: GetLoopOutSwaps :many const getLoopOutSwaps = `-- name: GetLoopOutSwaps :many
SELECT SELECT
swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label, swaps.id, swaps.swap_hash, swaps.preimage, swaps.initiation_time, swaps.amount_requested, swaps.cltv_expiry, swaps.max_miner_fee, swaps.max_swap_fee, swaps.initiation_height, swaps.protocol_version, swaps.label,
loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep, loopout_swaps.swap_hash, loopout_swaps.dest_address, loopout_swaps.swap_invoice, loopout_swaps.max_swap_routing_fee, loopout_swaps.sweep_conf_target, loopout_swaps.htlc_confirmations, loopout_swaps.outgoing_chan_set, loopout_swaps.prepay_invoice, loopout_swaps.max_prepay_routing_fee, loopout_swaps.publication_deadline, loopout_swaps.single_sweep, loopout_swaps.payment_timeout,
htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index htlc_keys.swap_hash, htlc_keys.sender_script_pubkey, htlc_keys.receiver_script_pubkey, htlc_keys.sender_internal_pubkey, htlc_keys.receiver_internal_pubkey, htlc_keys.client_key_family, htlc_keys.client_key_index
FROM FROM
swaps swaps
JOIN JOIN
loopout_swaps ON swaps.swap_hash = loopout_swaps.swap_hash loopout_swaps ON swaps.swap_hash = loopout_swaps.swap_hash
@ -288,6 +305,7 @@ type GetLoopOutSwapsRow struct {
MaxPrepayRoutingFee int64 MaxPrepayRoutingFee int64
PublicationDeadline time.Time PublicationDeadline time.Time
SingleSweep bool SingleSweep bool
PaymentTimeout int32
SwapHash_3 []byte SwapHash_3 []byte
SenderScriptPubkey []byte SenderScriptPubkey []byte
ReceiverScriptPubkey []byte ReceiverScriptPubkey []byte
@ -329,6 +347,7 @@ func (q *Queries) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, er
&i.MaxPrepayRoutingFee, &i.MaxPrepayRoutingFee,
&i.PublicationDeadline, &i.PublicationDeadline,
&i.SingleSweep, &i.SingleSweep,
&i.PaymentTimeout,
&i.SwapHash_3, &i.SwapHash_3,
&i.SenderScriptPubkey, &i.SenderScriptPubkey,
&i.ReceiverScriptPubkey, &i.ReceiverScriptPubkey,
@ -351,7 +370,7 @@ func (q *Queries) GetLoopOutSwaps(ctx context.Context) ([]GetLoopOutSwapsRow, er
} }
const getSwapUpdates = `-- name: GetSwapUpdates :many const getSwapUpdates = `-- name: GetSwapUpdates :many
SELECT SELECT
id, swap_hash, update_timestamp, update_state, htlc_txhash, server_cost, onchain_cost, offchain_cost id, swap_hash, update_timestamp, update_state, htlc_txhash, server_cost, onchain_cost, offchain_cost
FROM FROM
swap_updates swap_updates
@ -470,9 +489,10 @@ INSERT INTO loopout_swaps (
prepay_invoice, prepay_invoice,
max_prepay_routing_fee, max_prepay_routing_fee,
publication_deadline, publication_deadline,
single_sweep single_sweep,
payment_timeout
) VALUES ( ) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
) )
` `
@ -488,6 +508,7 @@ type InsertLoopOutParams struct {
MaxPrepayRoutingFee int64 MaxPrepayRoutingFee int64
PublicationDeadline time.Time PublicationDeadline time.Time
SingleSweep bool SingleSweep bool
PaymentTimeout int32
} }
func (q *Queries) InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error { func (q *Queries) InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) error {
@ -503,6 +524,7 @@ func (q *Queries) InsertLoopOut(ctx context.Context, arg InsertLoopOutParams) er
arg.MaxPrepayRoutingFee, arg.MaxPrepayRoutingFee,
arg.PublicationDeadline, arg.PublicationDeadline,
arg.SingleSweep, arg.SingleSweep,
arg.PaymentTimeout,
) )
return err return err
} }
@ -589,3 +611,29 @@ func (q *Queries) InsertSwapUpdate(ctx context.Context, arg InsertSwapUpdatePara
) )
return err return err
} }
const overrideSwapCosts = `-- name: OverrideSwapCosts :exec
UPDATE swap_updates
SET
server_cost = $2,
onchain_cost = $3,
offchain_cost = $4
WHERE id = $1
`
type OverrideSwapCostsParams struct {
ID int32
ServerCost int64
OnchainCost int64
OffchainCost int64
}
func (q *Queries) OverrideSwapCosts(ctx context.Context, arg OverrideSwapCostsParams) error {
_, err := q.db.ExecContext(ctx, overrideSwapCosts,
arg.ID,
arg.ServerCost,
arg.OnchainCost,
arg.OffchainCost,
)
return err
}

@ -15,7 +15,6 @@ import (
sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite" sqlite_migrate "github.com/golang-migrate/migrate/v4/database/sqlite"
"github.com/lightninglabs/loop/loopdb/sqlc" "github.com/lightninglabs/loop/loopdb/sqlc"
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
_ "modernc.org/sqlite" // Register relevant drivers. _ "modernc.org/sqlite" // Register relevant drivers.
) )
@ -68,6 +67,21 @@ func NewSqliteStore(cfg *SqliteConfig, network *chaincfg.Params) (*SqliteSwapSto
name: "busy_timeout", name: "busy_timeout",
value: "5000", value: "5000",
}, },
{
// With the WAL mode, this ensures that we also do an
// extra WAL sync after each transaction. The normal
// sync mode skips this and gives better performance,
// but risks durability.
name: "synchronous",
value: "full",
},
{
// This is used to ensure proper durability for users
// running on Mac OS. It uses the correct fsync system
// call to ensure items are fully flushed to disk.
name: "fullfsync",
value: "true",
},
} }
sqliteOptions := make(url.Values) sqliteOptions := make(url.Values)
for _, option := range pragmaOptions { for _, option := range pragmaOptions {
@ -180,7 +194,7 @@ func (db *BaseDB) BeginTx(ctx context.Context,
} }
// ExecTx is a wrapper for txBody to abstract the creation and commit of a db // ExecTx is a wrapper for txBody to abstract the creation and commit of a db
// transaction. The db transaction is embedded in a `*postgres.Queries` that // transaction. The db transaction is embedded in a `*sqlc.Queries` that
// txBody needs to use when executing each one of the queries that need to be // txBody needs to use when executing each one of the queries that need to be
// applied atomically. // applied atomically.
func (db *BaseDB) ExecTx(ctx context.Context, txOptions TxOptions, func (db *BaseDB) ExecTx(ctx context.Context, txOptions TxOptions,
@ -210,9 +224,9 @@ func (db *BaseDB) ExecTx(ctx context.Context, txOptions TxOptions,
// FixFaultyTimestamps fixes faulty timestamps in the database, caused // FixFaultyTimestamps fixes faulty timestamps in the database, caused
// by using milliseconds instead of seconds as the publication deadline. // by using milliseconds instead of seconds as the publication deadline.
func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error { func (db *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
// Manually fetch all the loop out swaps. // Manually fetch all the loop out swaps.
rows, err := b.DB.QueryContext( rows, err := db.DB.QueryContext(
ctx, "SELECT swap_hash, swap_invoice, publication_deadline FROM loopout_swaps", ctx, "SELECT swap_hash, swap_invoice, publication_deadline FROM loopout_swaps",
) )
if err != nil { if err != nil {
@ -248,7 +262,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
return err return err
} }
tx, err := b.BeginTx(ctx, &SqliteTxOptions{}) tx, err := db.BeginTx(ctx, &SqliteTxOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -269,7 +283,7 @@ func (b *BaseDB) FixFaultyTimestamps(ctx context.Context) error {
continue continue
} }
payReq, err := zpay32.Decode(swap.SwapInvoice, b.network) payReq, err := zpay32.Decode(swap.SwapInvoice, db.network)
if err != nil { if err != nil {
return err return err
} }
@ -309,7 +323,7 @@ type SqliteTxOptions struct {
readOnly bool readOnly bool
} }
// NewSqlReadOpts returns a new KeyStoreTxOptions instance triggers a read // NewSqlReadOpts returns a new KeyStoreTxOptions instance that triggers a read
// transaction. // transaction.
func NewSqlReadOpts() *SqliteTxOptions { func NewSqlReadOpts() *SqliteTxOptions {
return &SqliteTxOptions{ return &SqliteTxOptions{
@ -317,6 +331,14 @@ func NewSqlReadOpts() *SqliteTxOptions {
} }
} }
// NewSqlWriteOpts returns a new KeyStoreTxOptions instance that triggers a
// write (regular) transaction.
func NewSqlWriteOpts() *SqliteTxOptions {
return &SqliteTxOptions{
readOnly: false,
}
}
// ReadOnly returns true if the transaction should be read only. // ReadOnly returns true if the transaction should be read only.
// //
// NOTE: This implements the TxOptions interface. // NOTE: This implements the TxOptions interface.

@ -1009,3 +1009,25 @@ func (b *boltSwapStore) BatchInsertUpdate(ctx context.Context,
return errUnimplemented return errUnimplemented
} }
// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out
// swaps.
func (b *boltSwapStore) BatchUpdateLoopOutSwapCosts(ctx context.Context,
costs map[lntypes.Hash]SwapCost) error {
return errUnimplemented
}
// HasMigration returns true if the migration with the given ID has been done.
func (b *boltSwapStore) HasMigration(ctx context.Context, migrationID string) (
bool, error) {
return false, errUnimplemented
}
// SetMigration marks the migration with the given ID as done.
func (b *boltSwapStore) SetMigration(ctx context.Context,
migrationID string) error {
return errUnimplemented
}

@ -3,6 +3,7 @@ package loopdb
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"testing" "testing"
"time" "time"
@ -23,6 +24,8 @@ type StoreMock struct {
loopInStoreChan chan LoopInContract loopInStoreChan chan LoopInContract
loopInUpdateChan chan SwapStateData loopInUpdateChan chan SwapStateData
migrations map[string]struct{}
t *testing.T t *testing.T
} }
@ -38,6 +41,7 @@ func NewStoreMock(t *testing.T) *StoreMock {
loopInUpdateChan: make(chan SwapStateData, 1), loopInUpdateChan: make(chan SwapStateData, 1),
LoopInSwaps: make(map[lntypes.Hash]*LoopInContract), LoopInSwaps: make(map[lntypes.Hash]*LoopInContract),
LoopInUpdates: make(map[lntypes.Hash][]SwapStateData), LoopInUpdates: make(map[lntypes.Hash][]SwapStateData),
migrations: make(map[string]struct{}),
t: t, t: t,
} }
} }
@ -337,3 +341,46 @@ func (b *StoreMock) BatchInsertUpdate(ctx context.Context,
return errors.New("not implemented") return errors.New("not implemented")
} }
// BatchUpdateLoopOutSwapCosts updates the swap costs for a batch of loop out
// swaps.
func (s *StoreMock) BatchUpdateLoopOutSwapCosts(ctx context.Context,
costs map[lntypes.Hash]SwapCost) error {
for hash, cost := range costs {
if _, ok := s.LoopOutUpdates[hash]; !ok {
return fmt.Errorf("swap has no updates: %v", hash)
}
updates, ok := s.LoopOutUpdates[hash]
if !ok {
return fmt.Errorf("swap has no updates: %v", hash)
}
updates[len(updates)-1].Cost = cost
}
return nil
}
// HasMigration returns true if the migration with the given ID has been done.
func (s *StoreMock) HasMigration(ctx context.Context, migrationID string) (
bool, error) {
_, ok := s.migrations[migrationID]
return ok, nil
}
// SetMigration marks the migration with the given ID as done.
func (s *StoreMock) SetMigration(ctx context.Context,
migrationID string) error {
if _, ok := s.migrations[migrationID]; ok {
return errors.New("migration already done")
}
s.migrations[migrationID] = struct{}{}
return nil
}

@ -0,0 +1,49 @@
package loopdb
import (
"context"
"github.com/lightninglabs/loop/loopdb/sqlc"
)
// BatchedQuerier implements all DB queries and ExecTx on *sqlc.Queries.
// It is implemented by BaseDB, SqliteSwapStore, etc.
type BatchedQuerier interface {
sqlc.Querier
// ExecTx is a wrapper for txBody to abstract the creation and commit of
// a db transaction. The db transaction is embedded in a `*sqlc.Queries`
// that txBody needs to use when executing each one of the queries that
// need to be applied atomically.
ExecTx(ctx context.Context, txOptions TxOptions,
txBody func(*sqlc.Queries) error) error
}
// TypedStore is similar to BaseDB but provides parameterized ExecTx.
// It is used in other packages expecting ExecTx operating on subset of methods.
type TypedStore[Q any] struct {
BatchedQuerier
}
// NewTypedStore wraps a db, replacing generic ExecTx method with the typed one.
func NewTypedStore[Q any](db BatchedQuerier) *TypedStore[Q] {
// Make sure *sqlc.Queries can be casted to Q.
_ = any((*sqlc.Queries)(nil)).(Q)
return &TypedStore[Q]{
BatchedQuerier: db,
}
}
// ExecTx will execute the passed txBody, operating upon generic parameter Q
// (usually a storage interface) in a single transaction. The set of TxOptions
// are passed in to allow the caller to specify if a transaction is read-only.
func (s *TypedStore[Q]) ExecTx(ctx context.Context,
txOptions TxOptions, txBody func(Q) error) error {
return s.BatchedQuerier.ExecTx(ctx, txOptions,
func(q *sqlc.Queries) error {
return txBody(any(q).(Q))
},
)
}

@ -111,8 +111,8 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
// Because the Private flag is set, we'll generate our own set // Because the Private flag is set, we'll generate our own set
// of hop hints. // of hop hints.
request.RouteHints, err = SelectHopHints( request.RouteHints, err = SelectHopHints(
globalCtx, cfg.lnd, request.Amount, DefaultMaxHopHints, globalCtx, cfg.lnd.Client, request.Amount,
includeNodes, DefaultMaxHopHints, includeNodes,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -837,7 +837,7 @@ func getTxFee(tx *wire.MsgTx, fee chainfee.SatPerKVByte) btcutil.Amount {
btcTx := btcutil.NewTx(tx) btcTx := btcutil.NewTx(tx)
vsize := mempool.GetTxVirtualSize(btcTx) vsize := mempool.GetTxVirtualSize(btcTx)
return fee.FeeForVSize(vsize) return fee.FeeForVSize(lntypes.VByte(vsize))
} }
// waitForSwapComplete waits until a spending tx of the htlc gets confirmed and // waitForSwapComplete waits until a spending tx of the htlc gets confirmed and
@ -919,9 +919,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
s.log.Infof("Htlc spend by tx: %v", s.log.Infof("Htlc spend by tx: %v",
spendDetails.SpenderTxHash) spendDetails.SpenderTxHash)
err := s.processHtlcSpend( err := s.processHtlcSpend(ctx, spendDetails, sweepFee)
ctx, spendDetails, htlcValue, sweepFee,
)
if err != nil { if err != nil {
return err return err
} }
@ -959,8 +957,6 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
switch update.State { switch update.State {
// Swap invoice was paid, so update server cost balance. // Swap invoice was paid, so update server cost balance.
case invpkg.ContractSettled: case invpkg.ContractSettled:
s.cost.Server -= update.AmtPaid
// If invoice settlement and htlc spend happen // If invoice settlement and htlc spend happen
// in the expected order, move the swap to an // in the expected order, move the swap to an
// intermediate state that indicates that the // intermediate state that indicates that the
@ -977,6 +973,8 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
invoiceFinalized = true invoiceFinalized = true
htlcKeyRevealed = s.tryPushHtlcKey(ctx) htlcKeyRevealed = s.tryPushHtlcKey(ctx)
s.cost.Server = s.AmountRequested -
update.AmtPaid
// Canceled invoice has no effect on server cost // Canceled invoice has no effect on server cost
// balance. // balance.
@ -1023,8 +1021,7 @@ func (s *loopInSwap) tryPushHtlcKey(ctx context.Context) bool {
} }
func (s *loopInSwap) processHtlcSpend(ctx context.Context, func (s *loopInSwap) processHtlcSpend(ctx context.Context,
spend *chainntnfs.SpendDetail, htlcValue, spend *chainntnfs.SpendDetail, sweepFee btcutil.Amount) error {
sweepFee btcutil.Amount) error {
// Determine the htlc input of the spending tx and inspect the witness // Determine the htlc input of the spending tx and inspect the witness
// to find out whether a success or a timeout tx spent the htlc. // to find out whether a success or a timeout tx spent the htlc.
@ -1032,10 +1029,6 @@ func (s *loopInSwap) processHtlcSpend(ctx context.Context,
if s.htlc.IsSuccessWitness(htlcInput.Witness) { if s.htlc.IsSuccessWitness(htlcInput.Witness) {
s.setState(loopdb.StateSuccess) s.setState(loopdb.StateSuccess)
// Server swept the htlc. The htlc value can be added to the
// server cost balance.
s.cost.Server += htlcValue
} else { } else {
// We needed another on chain tx to sweep the timeout clause, // We needed another on chain tx to sweep the timeout clause,
// which we now include in our costs. // which we now include in our costs.

@ -77,6 +77,10 @@ type loopOutSwap struct {
swapInvoicePaymentAddr [32]byte swapInvoicePaymentAddr [32]byte
// prepayAmount holds the amount of the prepay invoice. We use this
// to calculate the total cost of the swap.
prepayAmount btcutil.Amount
swapPaymentChan chan paymentResult swapPaymentChan chan paymentResult
prePaymentChan chan paymentResult prePaymentChan chan paymentResult
@ -195,6 +199,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
ProtocolVersion: loopdb.CurrentProtocolVersion(), ProtocolVersion: loopdb.CurrentProtocolVersion(),
}, },
OutgoingChanSet: chanSet, OutgoingChanSet: chanSet,
PaymentTimeout: request.PaymentTimeout,
} }
swapKit := newSwapKit( swapKit := newSwapKit(
@ -306,7 +311,11 @@ func (s *loopOutSwap) sendUpdate(ctx context.Context) error {
info := s.swapInfo() info := s.swapInfo()
s.log.Infof("Loop out swap state: %v", info.State) s.log.Infof("Loop out swap state: %v", info.State)
info.HtlcAddressP2WSH = s.htlc.Address if s.htlc.OutputType == swap.HtlcP2WSH {
info.HtlcAddressP2WSH = s.htlc.Address
} else {
info.HtlcAddressP2TR = s.htlc.Address
}
// In order to avoid potentially dangerous ownership sharing // In order to avoid potentially dangerous ownership sharing
// we copy the outgoing channel set. // we copy the outgoing channel set.
@ -398,7 +407,7 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
case result := <-s.swapPaymentChan: case result := <-s.swapPaymentChan:
s.swapPaymentChan = nil s.swapPaymentChan = nil
err := s.handlePaymentResult(result) err := s.handlePaymentResult(result, true)
if err != nil { if err != nil {
return err return err
} }
@ -414,7 +423,7 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
case result := <-s.prePaymentChan: case result := <-s.prePaymentChan:
s.prePaymentChan = nil s.prePaymentChan = nil
err := s.handlePaymentResult(result) err := s.handlePaymentResult(result, false)
if err != nil { if err != nil {
return err return err
} }
@ -444,7 +453,12 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
return s.persistState(globalCtx) return s.persistState(globalCtx)
} }
func (s *loopOutSwap) handlePaymentResult(result paymentResult) error { // handlePaymentResult processes the result of a payment attempt. If the
// payment was successful and this is the main swap payment, the cost of the
// swap is updated.
func (s *loopOutSwap) handlePaymentResult(result paymentResult,
swapPayment bool) error {
switch { switch {
// If our result has a non-nil error, our status will be nil. In this // If our result has a non-nil error, our status will be nil. In this
// case the payment failed so we do not need to take any action. // case the payment failed so we do not need to take any action.
@ -452,7 +466,22 @@ func (s *loopOutSwap) handlePaymentResult(result paymentResult) error {
return nil return nil
case result.status.State == lnrpc.Payment_SUCCEEDED: case result.status.State == lnrpc.Payment_SUCCEEDED:
s.cost.Server += result.status.Value.ToSatoshis() // Update the cost of the swap if this is the main swap payment.
if swapPayment {
// The client pays for the swap with the swap invoice,
// so we can calculate the total cost of the swap by
// subtracting the amount requested from the total
// amount that we actually paid (which is the sum of
// the swap invoice amount and the prepay invoice
// amount).
s.cost.Server += s.prepayAmount +
result.status.Value.ToSatoshis() -
s.AmountRequested
}
// On top of the swap cost we also pay for routing which
// is reflected in the fee. We add the off-chain fee for both
// the swap payment and the prepay.
s.cost.Offchain += result.status.Fee.ToSatoshis() s.cost.Offchain += result.status.Fee.ToSatoshis()
return nil return nil
@ -468,6 +497,16 @@ func (s *loopOutSwap) handlePaymentResult(result paymentResult) error {
// executeSwap executes the swap, but returns as soon as the swap outcome is // executeSwap executes the swap, but returns as soon as the swap outcome is
// final. At that point, there may still be pending off-chain payment(s). // final. At that point, there may still be pending off-chain payment(s).
func (s *loopOutSwap) executeSwap(globalCtx context.Context) error { func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
// Decode the prepay invoice so we can ensure that we account for the
// prepay amount when calculating the final costs of the swap.
_, _, _, amt, err := swap.DecodeInvoice(
s.lnd.ChainParams, s.PrepayInvoice,
)
if err != nil {
return err
}
s.prepayAmount = amt
// We always pay both invoices (again). This is currently the only way // We always pay both invoices (again). This is currently the only way
// to sort of resume payments. // to sort of resume payments.
// //
@ -514,7 +553,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
} }
// Try to spend htlc and continue (rbf) until a spend has confirmed. // Try to spend htlc and continue (rbf) until a spend has confirmed.
spendTx, err := s.waitForHtlcSpendConfirmedV2( spend, err := s.waitForHtlcSpendConfirmedV2(
globalCtx, *htlcOutpoint, htlcValue, globalCtx, *htlcOutpoint, htlcValue,
) )
if err != nil { if err != nil {
@ -523,7 +562,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
// If spend details are nil, we resolved the swap without waiting for // If spend details are nil, we resolved the swap without waiting for
// its spend, so we can exit. // its spend, so we can exit.
if spendTx == nil { if spend == nil {
return nil return nil
} }
@ -531,7 +570,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
// don't just try to match with the hash of our sweep tx, because it // don't just try to match with the hash of our sweep tx, because it
// may be swept by a different (fee) sweep tx from a previous run. // may be swept by a different (fee) sweep tx from a previous run.
htlcInput, err := swap.GetTxInputByOutpoint( htlcInput, err := swap.GetTxInputByOutpoint(
spendTx, htlcOutpoint, spend.Tx, htlcOutpoint,
) )
if err != nil { if err != nil {
return err return err
@ -539,11 +578,7 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
sweepSuccessful := s.htlc.IsSuccessWitness(htlcInput.Witness) sweepSuccessful := s.htlc.IsSuccessWitness(htlcInput.Witness)
if sweepSuccessful { if sweepSuccessful {
s.cost.Server -= htlcValue s.cost.Onchain = spend.OnChainFeePortion
s.cost.Onchain = htlcValue -
btcutil.Amount(spendTx.TxOut[0].Value)
s.state = loopdb.StateSuccess s.state = loopdb.StateSuccess
} else { } else {
s.state = loopdb.StateFailSweepTimeout s.state = loopdb.StateFailSweepTimeout
@ -594,15 +629,18 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) {
// Use the recommended routing plugin. // Use the recommended routing plugin.
s.swapPaymentChan = s.payInvoice( s.swapPaymentChan = s.payInvoice(
ctx, s.SwapInvoice, s.MaxSwapRoutingFee, ctx, s.SwapInvoice, s.MaxSwapRoutingFee,
s.LoopOutContract.OutgoingChanSet, pluginType, true, s.LoopOutContract.OutgoingChanSet,
s.LoopOutContract.PaymentTimeout, pluginType, true,
) )
// Pay the prepay invoice. Won't use the routing plugin here as the // Pay the prepay invoice. Won't use the routing plugin here as the
// prepay is trivially small and shouldn't normally need any help. // prepay is trivially small and shouldn't normally need any help. We
// are sending it over the same channel as the loop out payment.
s.log.Infof("Sending prepayment %v", s.PrepayInvoice) s.log.Infof("Sending prepayment %v", s.PrepayInvoice)
s.prePaymentChan = s.payInvoice( s.prePaymentChan = s.payInvoice(
ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee, ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee,
nil, RoutingPluginNone, false, s.LoopOutContract.OutgoingChanSet,
s.LoopOutContract.PaymentTimeout, RoutingPluginNone, false,
) )
} }
@ -630,7 +668,7 @@ func (p paymentResult) failure() error {
// payInvoice pays a single invoice. // payInvoice pays a single invoice.
func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string, func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
maxFee btcutil.Amount, outgoingChanIds loopdb.ChannelSet, maxFee btcutil.Amount, outgoingChanIds loopdb.ChannelSet,
pluginType RoutingPluginType, paymentTimeout time.Duration, pluginType RoutingPluginType,
reportPluginResult bool) chan paymentResult { reportPluginResult bool) chan paymentResult {
resultChan := make(chan paymentResult) resultChan := make(chan paymentResult)
@ -645,8 +683,8 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
var result paymentResult var result paymentResult
status, err := s.payInvoiceAsync( status, err := s.payInvoiceAsync(
ctx, invoice, maxFee, outgoingChanIds, pluginType, ctx, invoice, maxFee, outgoingChanIds, paymentTimeout,
reportPluginResult, pluginType, reportPluginResult,
) )
if err != nil { if err != nil {
result.err = err result.err = err
@ -674,8 +712,9 @@ func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
// payInvoiceAsync is the asynchronously executed part of paying an invoice. // payInvoiceAsync is the asynchronously executed part of paying an invoice.
func (s *loopOutSwap) payInvoiceAsync(ctx context.Context, func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
invoice string, maxFee btcutil.Amount, invoice string, maxFee btcutil.Amount,
outgoingChanIds loopdb.ChannelSet, pluginType RoutingPluginType, outgoingChanIds loopdb.ChannelSet, paymentTimeout time.Duration,
reportPluginResult bool) (*lndclient.PaymentStatus, error) { pluginType RoutingPluginType, reportPluginResult bool) (
*lndclient.PaymentStatus, error) {
// Extract hash from payment request. Unfortunately the request // Extract hash from payment request. Unfortunately the request
// components aren't available directly. // components aren't available directly.
@ -688,7 +727,7 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
} }
maxRetries := 1 maxRetries := 1
paymentTimeout := s.executeConfig.totalPaymentTimeout totalPaymentTimeout := s.executeConfig.totalPaymentTimeout
// Attempt to acquire and initialize the routing plugin. // Attempt to acquire and initialize the routing plugin.
routingPlugin, err := AcquireRoutingPlugin( routingPlugin, err := AcquireRoutingPlugin(
@ -703,8 +742,30 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
pluginType, hash.String()) pluginType, hash.String())
maxRetries = s.executeConfig.maxPaymentRetries maxRetries = s.executeConfig.maxPaymentRetries
paymentTimeout /= time.Duration(maxRetries)
// If not set, default to the per payment timeout to the total
// payment timeout divied by the configured maximum retries.
if paymentTimeout == 0 {
paymentTimeout = totalPaymentTimeout /
time.Duration(maxRetries)
}
// If the payment timeout is too long, we need to adjust the
// number of retries to ensure we don't exceed the total
// payment timeout.
if paymentTimeout*time.Duration(maxRetries) >
totalPaymentTimeout {
maxRetries = int(totalPaymentTimeout / paymentTimeout)
s.log.Infof("Adjusted max routing plugin retries to "+
"%v to stay within total payment timeout",
maxRetries)
}
defer ReleaseRoutingPlugin(ctx) defer ReleaseRoutingPlugin(ctx)
} else if paymentTimeout == 0 {
// If not set, default the payment timeout to the total payment
// timeout.
paymentTimeout = totalPaymentTimeout
} }
req := lndclient.SendPaymentRequest{ req := lndclient.SendPaymentRequest{
@ -915,7 +976,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
case result := <-s.swapPaymentChan: case result := <-s.swapPaymentChan:
s.swapPaymentChan = nil s.swapPaymentChan = nil
err := s.handlePaymentResult(result) err := s.handlePaymentResult(result, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -937,7 +998,7 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
case result := <-s.prePaymentChan: case result := <-s.prePaymentChan:
s.prePaymentChan = nil s.prePaymentChan = nil
err := s.handlePaymentResult(result) err := s.handlePaymentResult(result, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1005,9 +1066,9 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
// sweep or a server revocation tx. // sweep or a server revocation tx.
func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context, func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
htlcOutpoint wire.OutPoint, htlcValue btcutil.Amount) ( htlcOutpoint wire.OutPoint, htlcValue btcutil.Amount) (
*wire.MsgTx, error) { *sweepbatcher.SpendDetail, error) {
spendChan := make(chan *wire.MsgTx) spendChan := make(chan *sweepbatcher.SpendDetail)
spendErrChan := make(chan error, 1) spendErrChan := make(chan error, 1)
quitChan := make(chan bool, 1) quitChan := make(chan bool, 1)
@ -1054,10 +1115,10 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmedV2(globalCtx context.Context,
for { for {
select { select {
// Htlc spend, break loop. // Htlc spend, break loop.
case spendTx := <-spendChan: case spend := <-spendChan:
s.log.Infof("Htlc spend by tx: %v", spendTx.TxHash()) s.log.Infof("Htlc spend by tx: %v", spend.Tx.TxHash())
return spendTx, nil return spend, nil
// Spend notification error. // Spend notification error.
case err := <-spendErrChan: case err := <-spendErrChan:

@ -17,6 +17,7 @@ import (
"github.com/lightninglabs/loop/sweepbatcher" "github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightninglabs/loop/test" "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -298,10 +299,15 @@ func testCustomSweepConfTarget(t *testing.T) {
batcherStore := sweepbatcher.NewStoreMock() batcherStore := sweepbatcher.NewStoreMock()
sweepStore, err := sweepbatcher.NewSweepFetcherFromSwapStore(
cfg.store, lnd.ChainParams,
)
require.NoError(t, err)
batcher := sweepbatcher.NewBatcher( batcher := sweepbatcher.NewBatcher(
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess, mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
lnd.ChainParams, batcherStore, cfg.store, lnd.ChainParams, batcherStore, sweepStore,
) )
tctx, cancel := context.WithCancel(context.Background()) tctx, cancel := context.WithCancel(context.Background())
@ -423,7 +429,7 @@ func testCustomSweepConfTarget(t *testing.T) {
) )
require.NoError(t, err, "unable to retrieve fee estimate") require.NoError(t, err, "unable to retrieve fee estimate")
minFee := feeRate.FeeForWeight(weight) minFee := feeRate.FeeForWeight(lntypes.WeightUnit(weight))
// Just an estimate that works to sanity check fee upper bound. // Just an estimate that works to sanity check fee upper bound.
maxFee := btcutil.Amount(float64(minFee) * 1.5) maxFee := btcutil.Amount(float64(minFee) * 1.5)
@ -531,10 +537,15 @@ func testPreimagePush(t *testing.T) {
batcherStore := sweepbatcher.NewStoreMock() batcherStore := sweepbatcher.NewStoreMock()
sweepStore, err := sweepbatcher.NewSweepFetcherFromSwapStore(
cfg.store, lnd.ChainParams,
)
require.NoError(t, err)
batcher := sweepbatcher.NewBatcher( batcher := sweepbatcher.NewBatcher(
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess, mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
lnd.ChainParams, batcherStore, cfg.store, lnd.ChainParams, batcherStore, sweepStore,
) )
tctx, cancel := context.WithCancel(context.Background()) tctx, cancel := context.WithCancel(context.Background())
@ -952,10 +963,15 @@ func TestLoopOutMuSig2Sweep(t *testing.T) {
batcherStore := sweepbatcher.NewStoreMock() batcherStore := sweepbatcher.NewStoreMock()
sweepStore, err := sweepbatcher.NewSweepFetcherFromSwapStore(
cfg.store, lnd.ChainParams,
)
require.NoError(t, err)
batcher := sweepbatcher.NewBatcher( batcher := sweepbatcher.NewBatcher(
lnd.WalletKit, lnd.ChainNotifier, lnd.Signer, lnd.WalletKit, lnd.ChainNotifier, lnd.Signer,
mockMuSig2SignSweep, mockVerifySchnorrSigSuccess, mockMuSig2SignSweep, mockVerifySchnorrSigSuccess,
lnd.ChainParams, batcherStore, cfg.store, lnd.ChainParams, batcherStore, sweepStore,
) )
tctx, cancel := context.WithCancel(context.Background()) tctx, cancel := context.WithCancel(context.Background())

@ -1,9 +1,9 @@
FROM golang:1.16.3-buster FROM golang:1.21.9-bookworm
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
git \ git \
protobuf-compiler='3.6*' \ protobuf-compiler='3.21.12*' \
clang-format='1:7.0*' clang-format='1:14.0*'
# We don't want any default values for these variables to make sure they're # We don't want any default values for these variables to make sure they're
# explicitly provided by parsing the go.mod file. Otherwise we might forget to # explicitly provided by parsing the go.mod file. Otherwise we might forget to
@ -13,14 +13,19 @@ ARG GRPC_GATEWAY_VERSION
ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0" ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0"
ENV FALAFEL_VERSION="v0.9.1" ENV FALAFEL_VERSION="v0.9.1"
ENV GOCACHE=/tmp/build/.cache
ENV GOMODCACHE=/tmp/build/.modcache
RUN cd /tmp \ RUN cd /tmp \
&& export GO111MODULE=on \ && mkdir -p /tmp/build/.cache \
&& go get google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \ && mkdir -p /tmp/build/.modcache \
&& go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \ && go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \
&& go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@${GRPC_GATEWAY_VERSION} \ && go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \
&& go get github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} \ && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@${GRPC_GATEWAY_VERSION} \
&& go get github.com/lightninglabs/falafel@${FALAFEL_VERSION} && go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${GRPC_GATEWAY_VERSION} \
&& go install github.com/lightninglabs/falafel@${FALAFEL_VERSION} \
&& go install golang.org/x/tools/cmd/goimports@v0.1.7 \
&& chmod -R 777 /tmp/build/
WORKDIR /build WORKDIR /build

File diff suppressed because it is too large Load Diff

@ -433,20 +433,38 @@ func local_request_SwapClient_Probe_0(ctx context.Context, marshaler runtime.Mar
} }
func request_SwapClient_GetLsatTokens_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_SwapClient_GetL402Tokens_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TokensRequest var protoReq TokensRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
msg, err := client.GetLsatTokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) msg, err := client.GetL402Tokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err return msg, metadata, err
} }
func local_request_SwapClient_GetLsatTokens_0(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func local_request_SwapClient_GetL402Tokens_0(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TokensRequest var protoReq TokensRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
msg, err := server.GetLsatTokens(ctx, &protoReq) msg, err := server.GetL402Tokens(ctx, &protoReq)
return msg, metadata, err
}
func request_SwapClient_GetL402Tokens_1(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TokensRequest
var metadata runtime.ServerMetadata
msg, err := client.GetL402Tokens(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_SwapClient_GetL402Tokens_1(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TokensRequest
var metadata runtime.ServerMetadata
msg, err := server.GetL402Tokens(ctx, &protoReq)
return msg, metadata, err return msg, metadata, err
} }
@ -770,7 +788,7 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_SwapClient_GetLsatTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_SwapClient_GetL402Tokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
var stream runtime.ServerTransportStream var stream runtime.ServerTransportStream
@ -778,12 +796,12 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux,
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error var err error
var annotatedContext context.Context var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/looprpc.SwapClient/GetLsatTokens", runtime.WithHTTPPathPattern("/v1/lsat/tokens")) annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/looprpc.SwapClient/GetL402Tokens", runtime.WithHTTPPathPattern("/v1/l402/tokens"))
if err != nil { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return return
} }
resp, md, err := local_request_SwapClient_GetLsatTokens_0(annotatedContext, inboundMarshaler, server, req, pathParams) resp, md, err := local_request_SwapClient_GetL402Tokens_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil { if err != nil {
@ -791,7 +809,32 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux,
return return
} }
forward_SwapClient_GetLsatTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_SwapClient_GetL402Tokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_SwapClient_GetL402Tokens_1, 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, "/looprpc.SwapClient/GetL402Tokens", runtime.WithHTTPPathPattern("/v1/lsat/tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_SwapClient_GetL402Tokens_1(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_SwapClient_GetL402Tokens_1(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
@ -1134,25 +1177,47 @@ func RegisterSwapClientHandlerClient(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_SwapClient_GetLsatTokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_SwapClient_GetL402Tokens_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error var err error
var annotatedContext context.Context var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/looprpc.SwapClient/GetLsatTokens", runtime.WithHTTPPathPattern("/v1/lsat/tokens")) annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/looprpc.SwapClient/GetL402Tokens", runtime.WithHTTPPathPattern("/v1/l402/tokens"))
if err != nil { if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return return
} }
resp, md, err := request_SwapClient_GetLsatTokens_0(annotatedContext, inboundMarshaler, client, req, pathParams) resp, md, err := request_SwapClient_GetL402Tokens_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil { if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return return
} }
forward_SwapClient_GetLsatTokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) forward_SwapClient_GetL402Tokens_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_SwapClient_GetL402Tokens_1, 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, "/looprpc.SwapClient/GetL402Tokens", runtime.WithHTTPPathPattern("/v1/lsat/tokens"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_SwapClient_GetL402Tokens_1(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_SwapClient_GetL402Tokens_1(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
}) })
@ -1266,7 +1331,9 @@ var (
pattern_SwapClient_Probe_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "loop", "in", "probe", "amt"}, "")) pattern_SwapClient_Probe_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "loop", "in", "probe", "amt"}, ""))
pattern_SwapClient_GetLsatTokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "lsat", "tokens"}, "")) pattern_SwapClient_GetL402Tokens_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "l402", "tokens"}, ""))
pattern_SwapClient_GetL402Tokens_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "lsat", "tokens"}, ""))
pattern_SwapClient_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "loop", "info"}, "")) pattern_SwapClient_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "loop", "info"}, ""))
@ -1296,7 +1363,9 @@ var (
forward_SwapClient_Probe_0 = runtime.ForwardResponseMessage forward_SwapClient_Probe_0 = runtime.ForwardResponseMessage
forward_SwapClient_GetLsatTokens_0 = runtime.ForwardResponseMessage forward_SwapClient_GetL402Tokens_0 = runtime.ForwardResponseMessage
forward_SwapClient_GetL402Tokens_1 = runtime.ForwardResponseMessage
forward_SwapClient_GetInfo_0 = runtime.ForwardResponseMessage forward_SwapClient_GetInfo_0 = runtime.ForwardResponseMessage

@ -76,7 +76,16 @@ service SwapClient {
rpc Probe (ProbeRequest) returns (ProbeResponse); rpc Probe (ProbeRequest) returns (ProbeResponse);
/* loop: `listauth` /* loop: `listauth`
GetLsatTokens returns all LSAT tokens the daemon ever paid for. GetL402Tokens returns all L402 tokens the daemon ever paid for.
*/
rpc GetL402Tokens (TokensRequest) returns (TokensResponse);
/*
Deprecated: use GetL402Tokens.
This API is provided to maintain backward compatibility with gRPC clients
(e.g. `loop listauth`, Terminal Web, RTL).
Type LsatToken used by GetLsatTokens in the past was renamed to L402Token,
but this does not affect binary encoding, so we can use type L402Token here.
*/ */
rpc GetLsatTokens (TokensRequest) returns (TokensResponse); rpc GetLsatTokens (TokensRequest) returns (TokensResponse);
@ -127,6 +136,13 @@ service SwapClient {
*/ */
rpc InstantOutQuote (InstantOutQuoteRequest) rpc InstantOutQuote (InstantOutQuoteRequest)
returns (InstantOutQuoteResponse); returns (InstantOutQuoteResponse);
/* loop: `listinstantouts`
ListInstantOuts returns a list of all currently known instant out swaps and
their current status.
*/
rpc ListInstantOuts (ListInstantOutsRequest)
returns (ListInstantOutsResponse);
} }
message LoopOutRequest { message LoopOutRequest {
@ -262,6 +278,13 @@ message LoopOutRequest {
be equal to the sum of the amounts of the reservations. be equal to the sum of the amounts of the reservations.
*/ */
repeated bytes reservation_ids = 18; repeated bytes reservation_ids = 18;
/*
The timeout in seconds to use for off-chain payments. Note that the swap
payment is attempted multiple times where each attempt will set this value
as the timeout for the payment.
*/
uint32 payment_timeout = 19;
} }
/* /*
@ -804,10 +827,10 @@ message TokensResponse {
/* /*
List of all tokens the daemon knows of, including old/expired tokens. List of all tokens the daemon knows of, including old/expired tokens.
*/ */
repeated LsatToken tokens = 1; repeated L402Token tokens = 1;
} }
message LsatToken { message L402Token {
/* /*
The base macaroon that was baked by the auth server. The base macaroon that was baked by the auth server.
*/ */
@ -1332,7 +1355,13 @@ message InstantOutRequest {
channel(s) that will be used are selected based on the lowest routing fee channel(s) that will be used are selected based on the lowest routing fee
for the swap payment to the server. for the swap payment to the server.
*/ */
repeated uint64 outgoing_chan_set = 11; repeated uint64 outgoing_chan_set = 2;
/*
An optional address to sweep the onchain funds to. If not set, the funds
will be swept to the wallet's internal address.
*/
string dest_addr = 3;
} }
message InstantOutResponse { message InstantOutResponse {
@ -1373,4 +1402,41 @@ message InstantOutQuoteResponse {
Sweep. Sweep.
*/ */
int64 sweep_fee_sat = 2; int64 sweep_fee_sat = 2;
} }
message ListInstantOutsRequest {
}
message ListInstantOutsResponse {
/*
The list of all currently known instant out swaps and their status.
*/
repeated InstantOut swaps = 1;
}
message InstantOut {
/*
The swap hash that identifies this swap.
*/
bytes swap_hash = 1;
/*
The state the swap is in.
*/
string state = 2;
/*
The amount of the swap.
*/
uint64 amount = 3;
/*
The used reservations for the swap.
*/
repeated bytes reservation_ids = 4;
/*
The sweep transaction id of the swap.
*/
string sweep_tx_id = 5;
}

@ -39,6 +39,29 @@
] ]
} }
}, },
"/v1/l402/tokens": {
"get": {
"summary": "loop: `listauth`\nGetL402Tokens returns all L402 tokens the daemon ever paid for.",
"operationId": "SwapClient_GetL402Tokens",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/looprpcTokensResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"SwapClient"
]
}
},
"/v1/liquidity/params": { "/v1/liquidity/params": {
"get": { "get": {
"summary": "loop: `getparams`\nGetLiquidityParams gets the parameters that the daemon's liquidity manager\nis currently configured with. This may be nil if nothing is configured.\n[EXPERIMENTAL]: endpoint is subject to change.", "summary": "loop: `getparams`\nGetLiquidityParams gets the parameters that the daemon's liquidity manager\nis currently configured with. This may be nil if nothing is configured.\n[EXPERIMENTAL]: endpoint is subject to change.",
@ -518,8 +541,8 @@
}, },
"/v1/lsat/tokens": { "/v1/lsat/tokens": {
"get": { "get": {
"summary": "loop: `listauth`\nGetLsatTokens returns all LSAT tokens the daemon ever paid for.", "summary": "loop: `listauth`\nGetL402Tokens returns all L402 tokens the daemon ever paid for.",
"operationId": "SwapClient_GetLsatTokens", "operationId": "SwapClient_GetL402Tokens2",
"responses": { "responses": {
"200": { "200": {
"description": "A successful response.", "description": "A successful response.",
@ -760,6 +783,37 @@
} }
} }
}, },
"looprpcInstantOut": {
"type": "object",
"properties": {
"swap_hash": {
"type": "string",
"format": "byte",
"description": "The swap hash that identifies this swap."
},
"state": {
"type": "string",
"description": "The state the swap is in."
},
"amount": {
"type": "string",
"format": "uint64",
"description": "The amount of the swap."
},
"reservation_ids": {
"type": "array",
"items": {
"type": "string",
"format": "byte"
},
"description": "The used reservations for the swap."
},
"sweep_tx_id": {
"type": "string",
"description": "The sweep transaction id of the swap."
}
}
},
"looprpcInstantOutQuoteResponse": { "looprpcInstantOutQuoteResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -793,6 +847,53 @@
} }
} }
}, },
"looprpcL402Token": {
"type": "object",
"properties": {
"base_macaroon": {
"type": "string",
"format": "byte",
"description": "The base macaroon that was baked by the auth server."
},
"payment_hash": {
"type": "string",
"format": "byte",
"description": "The payment hash of the payment that was paid to obtain the token."
},
"payment_preimage": {
"type": "string",
"format": "byte",
"description": "The preimage of the payment hash, knowledge of this is proof that the\npayment has been paid. If the preimage is set to all zeros, this means the\npayment is still pending and the token is not yet fully valid."
},
"amount_paid_msat": {
"type": "string",
"format": "int64",
"description": "The amount of millisatoshis that was paid to get the token."
},
"routing_fee_paid_msat": {
"type": "string",
"format": "int64",
"description": "The amount of millisatoshis paid in routing fee to pay for the token."
},
"time_created": {
"type": "string",
"format": "int64",
"description": "The creation time of the token as UNIX timestamp in seconds."
},
"expired": {
"type": "boolean",
"description": "Indicates whether the token is expired or still valid."
},
"storage_name": {
"type": "string",
"description": "Identifying attribute of this token in the store. Currently represents the\nfile name of the token where it's stored on the file system."
},
"id": {
"type": "string",
"description": "The l402 ID of the token."
}
}
},
"looprpcLiquidityParameters": { "looprpcLiquidityParameters": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -956,6 +1057,18 @@
], ],
"default": "UNKNOWN" "default": "UNKNOWN"
}, },
"looprpcListInstantOutsResponse": {
"type": "object",
"properties": {
"swaps": {
"type": "array",
"items": {
"$ref": "#/definitions/looprpcInstantOut"
},
"description": "The list of all currently known instant out swaps and their status."
}
}
},
"looprpcListReservationsResponse": { "looprpcListReservationsResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1155,6 +1268,11 @@
"format": "byte" "format": "byte"
}, },
"description": "The reservations to use for the swap. If this field is set, loop will try\nto use the instant out flow using the given reservations. If the\nreservations are not sufficient, the swap will fail. The swap amount must\nbe equal to the sum of the amounts of the reservations." "description": "The reservations to use for the swap. If this field is set, loop will try\nto use the instant out flow using the given reservations. If the\nreservations are not sufficient, the swap will fail. The swap amount must\nbe equal to the sum of the amounts of the reservations."
},
"payment_timeout": {
"type": "integer",
"format": "int64",
"description": "The timeout in seconds to use for off-chain payments. Note that the swap\npayment is attempted multiple times where each attempt will set this value\nas the timeout for the payment."
} }
} }
}, },
@ -1188,53 +1306,6 @@
} }
} }
}, },
"looprpcLsatToken": {
"type": "object",
"properties": {
"base_macaroon": {
"type": "string",
"format": "byte",
"description": "The base macaroon that was baked by the auth server."
},
"payment_hash": {
"type": "string",
"format": "byte",
"description": "The payment hash of the payment that was paid to obtain the token."
},
"payment_preimage": {
"type": "string",
"format": "byte",
"description": "The preimage of the payment hash, knowledge of this is proof that the\npayment has been paid. If the preimage is set to all zeros, this means the\npayment is still pending and the token is not yet fully valid."
},
"amount_paid_msat": {
"type": "string",
"format": "int64",
"description": "The amount of millisatoshis that was paid to get the token."
},
"routing_fee_paid_msat": {
"type": "string",
"format": "int64",
"description": "The amount of millisatoshis paid in routing fee to pay for the token."
},
"time_created": {
"type": "string",
"format": "int64",
"description": "The creation time of the token as UNIX timestamp in seconds."
},
"expired": {
"type": "boolean",
"description": "Indicates whether the token is expired or still valid."
},
"storage_name": {
"type": "string",
"description": "Identifying attribute of this token in the store. Currently represents the\nfile name of the token where it's stored on the file system."
},
"id": {
"type": "string",
"description": "The l402 ID of the token."
}
}
},
"looprpcOutQuoteResponse": { "looprpcOutQuoteResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -1491,7 +1562,7 @@
"tokens": { "tokens": {
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/looprpcLsatToken" "$ref": "#/definitions/looprpcL402Token"
}, },
"description": "List of all tokens the daemon knows of, including old/expired tokens." "description": "List of all tokens the daemon knows of, including old/expired tokens."
} }

@ -26,8 +26,10 @@ http:
get: "/v1/loop/in/probe/{amt}" get: "/v1/loop/in/probe/{amt}"
- selector: looprpc.SwapClient.GetInfo - selector: looprpc.SwapClient.GetInfo
get: "/v1/loop/info" get: "/v1/loop/info"
- selector: looprpc.SwapClient.GetLsatTokens - selector: looprpc.SwapClient.GetL402Tokens
get: "/v1/lsat/tokens" get: "/v1/l402/tokens"
additional_bindings:
- get: "/v1/lsat/tokens"
- selector: looprpc.SwapClient.GetLiquidityParams - selector: looprpc.SwapClient.GetLiquidityParams
get: "/v1/liquidity/params" get: "/v1/liquidity/params"
- selector: looprpc.SwapClient.SetLiquidityParams - selector: looprpc.SwapClient.SetLiquidityParams

@ -19,80 +19,89 @@ const _ = grpc.SupportPackageIsVersion7
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type SwapClientClient interface { type SwapClientClient interface {
// loop: `out` // loop: `out`
//LoopOut initiates an loop out swap with the given parameters. The call // LoopOut initiates an loop out swap with the given parameters. The call
//returns after the swap has been set up with the swap server. From that // returns after the swap has been set up with the swap server. From that
//point onwards, progress can be tracked via the SwapStatus stream that is // point onwards, progress can be tracked via the SwapStatus stream that is
//returned from Monitor(). // returned from Monitor().
LoopOut(ctx context.Context, in *LoopOutRequest, opts ...grpc.CallOption) (*SwapResponse, error) LoopOut(ctx context.Context, in *LoopOutRequest, opts ...grpc.CallOption) (*SwapResponse, error)
// loop: `in` // loop: `in`
//LoopIn initiates a loop in swap with the given parameters. The call // LoopIn initiates a loop in swap with the given parameters. The call
//returns after the swap has been set up with the swap server. From that // returns after the swap has been set up with the swap server. From that
//point onwards, progress can be tracked via the SwapStatus stream // point onwards, progress can be tracked via the SwapStatus stream
//that is returned from Monitor(). // that is returned from Monitor().
LoopIn(ctx context.Context, in *LoopInRequest, opts ...grpc.CallOption) (*SwapResponse, error) LoopIn(ctx context.Context, in *LoopInRequest, opts ...grpc.CallOption) (*SwapResponse, error)
// loop: `monitor` // loop: `monitor`
//Monitor will return a stream of swap updates for currently active swaps. // Monitor will return a stream of swap updates for currently active swaps.
Monitor(ctx context.Context, in *MonitorRequest, opts ...grpc.CallOption) (SwapClient_MonitorClient, error) Monitor(ctx context.Context, in *MonitorRequest, opts ...grpc.CallOption) (SwapClient_MonitorClient, error)
// loop: `listswaps` // loop: `listswaps`
//ListSwaps returns a list of all currently known swaps and their current // ListSwaps returns a list of all currently known swaps and their current
//status. // status.
ListSwaps(ctx context.Context, in *ListSwapsRequest, opts ...grpc.CallOption) (*ListSwapsResponse, error) ListSwaps(ctx context.Context, in *ListSwapsRequest, opts ...grpc.CallOption) (*ListSwapsResponse, error)
// loop: `swapinfo` // loop: `swapinfo`
//SwapInfo returns all known details about a single swap. // SwapInfo returns all known details about a single swap.
SwapInfo(ctx context.Context, in *SwapInfoRequest, opts ...grpc.CallOption) (*SwapStatus, error) SwapInfo(ctx context.Context, in *SwapInfoRequest, opts ...grpc.CallOption) (*SwapStatus, error)
// loop: `abandonswap` // loop: `abandonswap`
//AbandonSwap allows the client to abandon a swap. // AbandonSwap allows the client to abandon a swap.
AbandonSwap(ctx context.Context, in *AbandonSwapRequest, opts ...grpc.CallOption) (*AbandonSwapResponse, error) AbandonSwap(ctx context.Context, in *AbandonSwapRequest, opts ...grpc.CallOption) (*AbandonSwapResponse, error)
// loop: `terms` // loop: `terms`
//LoopOutTerms returns the terms that the server enforces for a loop out swap. // LoopOutTerms returns the terms that the server enforces for a loop out swap.
LoopOutTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*OutTermsResponse, error) LoopOutTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*OutTermsResponse, error)
// loop: `quote` // loop: `quote`
//LoopOutQuote returns a quote for a loop out swap with the provided // LoopOutQuote returns a quote for a loop out swap with the provided
//parameters. // parameters.
LoopOutQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*OutQuoteResponse, error) LoopOutQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*OutQuoteResponse, error)
// loop: `terms` // loop: `terms`
//GetTerms returns the terms that the server enforces for swaps. // GetTerms returns the terms that the server enforces for swaps.
GetLoopInTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*InTermsResponse, error) GetLoopInTerms(ctx context.Context, in *TermsRequest, opts ...grpc.CallOption) (*InTermsResponse, error)
// loop: `quote` // loop: `quote`
//GetQuote returns a quote for a swap with the provided parameters. // GetQuote returns a quote for a swap with the provided parameters.
GetLoopInQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*InQuoteResponse, error) GetLoopInQuote(ctx context.Context, in *QuoteRequest, opts ...grpc.CallOption) (*InQuoteResponse, error)
// // Probe asks he sever to probe the route to us to have a better upfront
//Probe asks he sever to probe the route to us to have a better upfront // estimate about routing fees when loopin-in.
//estimate about routing fees when loopin-in.
Probe(ctx context.Context, in *ProbeRequest, opts ...grpc.CallOption) (*ProbeResponse, error) Probe(ctx context.Context, in *ProbeRequest, opts ...grpc.CallOption) (*ProbeResponse, error)
// loop: `listauth` // loop: `listauth`
//GetLsatTokens returns all LSAT tokens the daemon ever paid for. // GetL402Tokens returns all L402 tokens the daemon ever paid for.
GetL402Tokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error)
// Deprecated: use GetL402Tokens.
// This API is provided to maintain backward compatibility with gRPC clients
// (e.g. `loop listauth`, Terminal Web, RTL).
// Type LsatToken used by GetLsatTokens in the past was renamed to L402Token,
// but this does not affect binary encoding, so we can use type L402Token here.
GetLsatTokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error) GetLsatTokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error)
// loop: `getinfo` // loop: `getinfo`
//GetInfo gets basic information about the loop daemon. // GetInfo gets basic information about the loop daemon.
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
// loop: `getparams` // loop: `getparams`
//GetLiquidityParams gets the parameters that the daemon's liquidity manager // GetLiquidityParams gets the parameters that the daemon's liquidity manager
//is currently configured with. This may be nil if nothing is configured. // is currently configured with. This may be nil if nothing is configured.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
GetLiquidityParams(ctx context.Context, in *GetLiquidityParamsRequest, opts ...grpc.CallOption) (*LiquidityParameters, error) GetLiquidityParams(ctx context.Context, in *GetLiquidityParamsRequest, opts ...grpc.CallOption) (*LiquidityParameters, error)
// loop: `setparams` // loop: `setparams`
//SetLiquidityParams sets a new set of parameters for the daemon's liquidity // SetLiquidityParams sets a new set of parameters for the daemon's liquidity
//manager. Note that the full set of parameters must be provided, because // manager. Note that the full set of parameters must be provided, because
//this call fully overwrites our existing parameters. // this call fully overwrites our existing parameters.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
SetLiquidityParams(ctx context.Context, in *SetLiquidityParamsRequest, opts ...grpc.CallOption) (*SetLiquidityParamsResponse, error) SetLiquidityParams(ctx context.Context, in *SetLiquidityParamsRequest, opts ...grpc.CallOption) (*SetLiquidityParamsResponse, error)
// loop: `suggestswaps` // loop: `suggestswaps`
//SuggestSwaps returns a list of recommended swaps based on the current // SuggestSwaps returns a list of recommended swaps based on the current
//state of your node's channels and it's liquidity manager parameters. // state of your node's channels and it's liquidity manager parameters.
//Note that only loop out suggestions are currently supported. // Note that only loop out suggestions are currently supported.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
SuggestSwaps(ctx context.Context, in *SuggestSwapsRequest, opts ...grpc.CallOption) (*SuggestSwapsResponse, error) SuggestSwaps(ctx context.Context, in *SuggestSwapsRequest, opts ...grpc.CallOption) (*SuggestSwapsResponse, error)
// loop: `listreservations` // loop: `listreservations`
//ListReservations returns a list of all reservations the server opened to us. // ListReservations returns a list of all reservations the server opened to us.
ListReservations(ctx context.Context, in *ListReservationsRequest, opts ...grpc.CallOption) (*ListReservationsResponse, error) ListReservations(ctx context.Context, in *ListReservationsRequest, opts ...grpc.CallOption) (*ListReservationsResponse, error)
// loop: `instantout` // loop: `instantout`
//InstantOut initiates an instant out swap with the given parameters. // InstantOut initiates an instant out swap with the given parameters.
InstantOut(ctx context.Context, in *InstantOutRequest, opts ...grpc.CallOption) (*InstantOutResponse, error) InstantOut(ctx context.Context, in *InstantOutRequest, opts ...grpc.CallOption) (*InstantOutResponse, error)
// loop: `instantoutquote` // loop: `instantoutquote`
//InstantOutQuote returns a quote for an instant out swap with the provided // InstantOutQuote returns a quote for an instant out swap with the provided
//parameters. // parameters.
InstantOutQuote(ctx context.Context, in *InstantOutQuoteRequest, opts ...grpc.CallOption) (*InstantOutQuoteResponse, error) InstantOutQuote(ctx context.Context, in *InstantOutQuoteRequest, opts ...grpc.CallOption) (*InstantOutQuoteResponse, error)
// loop: `listinstantouts`
// ListInstantOuts returns a list of all currently known instant out swaps and
// their current status.
ListInstantOuts(ctx context.Context, in *ListInstantOutsRequest, opts ...grpc.CallOption) (*ListInstantOutsResponse, error)
} }
type swapClientClient struct { type swapClientClient struct {
@ -225,6 +234,15 @@ func (c *swapClientClient) Probe(ctx context.Context, in *ProbeRequest, opts ...
return out, nil return out, nil
} }
func (c *swapClientClient) GetL402Tokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error) {
out := new(TokensResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetL402Tokens", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *swapClientClient) GetLsatTokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error) { func (c *swapClientClient) GetLsatTokens(ctx context.Context, in *TokensRequest, opts ...grpc.CallOption) (*TokensResponse, error) {
out := new(TokensResponse) out := new(TokensResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetLsatTokens", in, out, opts...) err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetLsatTokens", in, out, opts...)
@ -297,85 +315,103 @@ func (c *swapClientClient) InstantOutQuote(ctx context.Context, in *InstantOutQu
return out, nil return out, nil
} }
func (c *swapClientClient) ListInstantOuts(ctx context.Context, in *ListInstantOutsRequest, opts ...grpc.CallOption) (*ListInstantOutsResponse, error) {
out := new(ListInstantOutsResponse)
err := c.cc.Invoke(ctx, "/looprpc.SwapClient/ListInstantOuts", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// SwapClientServer is the server API for SwapClient service. // SwapClientServer is the server API for SwapClient service.
// All implementations must embed UnimplementedSwapClientServer // All implementations must embed UnimplementedSwapClientServer
// for forward compatibility // for forward compatibility
type SwapClientServer interface { type SwapClientServer interface {
// loop: `out` // loop: `out`
//LoopOut initiates an loop out swap with the given parameters. The call // LoopOut initiates an loop out swap with the given parameters. The call
//returns after the swap has been set up with the swap server. From that // returns after the swap has been set up with the swap server. From that
//point onwards, progress can be tracked via the SwapStatus stream that is // point onwards, progress can be tracked via the SwapStatus stream that is
//returned from Monitor(). // returned from Monitor().
LoopOut(context.Context, *LoopOutRequest) (*SwapResponse, error) LoopOut(context.Context, *LoopOutRequest) (*SwapResponse, error)
// loop: `in` // loop: `in`
//LoopIn initiates a loop in swap with the given parameters. The call // LoopIn initiates a loop in swap with the given parameters. The call
//returns after the swap has been set up with the swap server. From that // returns after the swap has been set up with the swap server. From that
//point onwards, progress can be tracked via the SwapStatus stream // point onwards, progress can be tracked via the SwapStatus stream
//that is returned from Monitor(). // that is returned from Monitor().
LoopIn(context.Context, *LoopInRequest) (*SwapResponse, error) LoopIn(context.Context, *LoopInRequest) (*SwapResponse, error)
// loop: `monitor` // loop: `monitor`
//Monitor will return a stream of swap updates for currently active swaps. // Monitor will return a stream of swap updates for currently active swaps.
Monitor(*MonitorRequest, SwapClient_MonitorServer) error Monitor(*MonitorRequest, SwapClient_MonitorServer) error
// loop: `listswaps` // loop: `listswaps`
//ListSwaps returns a list of all currently known swaps and their current // ListSwaps returns a list of all currently known swaps and their current
//status. // status.
ListSwaps(context.Context, *ListSwapsRequest) (*ListSwapsResponse, error) ListSwaps(context.Context, *ListSwapsRequest) (*ListSwapsResponse, error)
// loop: `swapinfo` // loop: `swapinfo`
//SwapInfo returns all known details about a single swap. // SwapInfo returns all known details about a single swap.
SwapInfo(context.Context, *SwapInfoRequest) (*SwapStatus, error) SwapInfo(context.Context, *SwapInfoRequest) (*SwapStatus, error)
// loop: `abandonswap` // loop: `abandonswap`
//AbandonSwap allows the client to abandon a swap. // AbandonSwap allows the client to abandon a swap.
AbandonSwap(context.Context, *AbandonSwapRequest) (*AbandonSwapResponse, error) AbandonSwap(context.Context, *AbandonSwapRequest) (*AbandonSwapResponse, error)
// loop: `terms` // loop: `terms`
//LoopOutTerms returns the terms that the server enforces for a loop out swap. // LoopOutTerms returns the terms that the server enforces for a loop out swap.
LoopOutTerms(context.Context, *TermsRequest) (*OutTermsResponse, error) LoopOutTerms(context.Context, *TermsRequest) (*OutTermsResponse, error)
// loop: `quote` // loop: `quote`
//LoopOutQuote returns a quote for a loop out swap with the provided // LoopOutQuote returns a quote for a loop out swap with the provided
//parameters. // parameters.
LoopOutQuote(context.Context, *QuoteRequest) (*OutQuoteResponse, error) LoopOutQuote(context.Context, *QuoteRequest) (*OutQuoteResponse, error)
// loop: `terms` // loop: `terms`
//GetTerms returns the terms that the server enforces for swaps. // GetTerms returns the terms that the server enforces for swaps.
GetLoopInTerms(context.Context, *TermsRequest) (*InTermsResponse, error) GetLoopInTerms(context.Context, *TermsRequest) (*InTermsResponse, error)
// loop: `quote` // loop: `quote`
//GetQuote returns a quote for a swap with the provided parameters. // GetQuote returns a quote for a swap with the provided parameters.
GetLoopInQuote(context.Context, *QuoteRequest) (*InQuoteResponse, error) GetLoopInQuote(context.Context, *QuoteRequest) (*InQuoteResponse, error)
// // Probe asks he sever to probe the route to us to have a better upfront
//Probe asks he sever to probe the route to us to have a better upfront // estimate about routing fees when loopin-in.
//estimate about routing fees when loopin-in.
Probe(context.Context, *ProbeRequest) (*ProbeResponse, error) Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)
// loop: `listauth` // loop: `listauth`
//GetLsatTokens returns all LSAT tokens the daemon ever paid for. // GetL402Tokens returns all L402 tokens the daemon ever paid for.
GetL402Tokens(context.Context, *TokensRequest) (*TokensResponse, error)
// Deprecated: use GetL402Tokens.
// This API is provided to maintain backward compatibility with gRPC clients
// (e.g. `loop listauth`, Terminal Web, RTL).
// Type LsatToken used by GetLsatTokens in the past was renamed to L402Token,
// but this does not affect binary encoding, so we can use type L402Token here.
GetLsatTokens(context.Context, *TokensRequest) (*TokensResponse, error) GetLsatTokens(context.Context, *TokensRequest) (*TokensResponse, error)
// loop: `getinfo` // loop: `getinfo`
//GetInfo gets basic information about the loop daemon. // GetInfo gets basic information about the loop daemon.
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
// loop: `getparams` // loop: `getparams`
//GetLiquidityParams gets the parameters that the daemon's liquidity manager // GetLiquidityParams gets the parameters that the daemon's liquidity manager
//is currently configured with. This may be nil if nothing is configured. // is currently configured with. This may be nil if nothing is configured.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
GetLiquidityParams(context.Context, *GetLiquidityParamsRequest) (*LiquidityParameters, error) GetLiquidityParams(context.Context, *GetLiquidityParamsRequest) (*LiquidityParameters, error)
// loop: `setparams` // loop: `setparams`
//SetLiquidityParams sets a new set of parameters for the daemon's liquidity // SetLiquidityParams sets a new set of parameters for the daemon's liquidity
//manager. Note that the full set of parameters must be provided, because // manager. Note that the full set of parameters must be provided, because
//this call fully overwrites our existing parameters. // this call fully overwrites our existing parameters.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
SetLiquidityParams(context.Context, *SetLiquidityParamsRequest) (*SetLiquidityParamsResponse, error) SetLiquidityParams(context.Context, *SetLiquidityParamsRequest) (*SetLiquidityParamsResponse, error)
// loop: `suggestswaps` // loop: `suggestswaps`
//SuggestSwaps returns a list of recommended swaps based on the current // SuggestSwaps returns a list of recommended swaps based on the current
//state of your node's channels and it's liquidity manager parameters. // state of your node's channels and it's liquidity manager parameters.
//Note that only loop out suggestions are currently supported. // Note that only loop out suggestions are currently supported.
//[EXPERIMENTAL]: endpoint is subject to change. // [EXPERIMENTAL]: endpoint is subject to change.
SuggestSwaps(context.Context, *SuggestSwapsRequest) (*SuggestSwapsResponse, error) SuggestSwaps(context.Context, *SuggestSwapsRequest) (*SuggestSwapsResponse, error)
// loop: `listreservations` // loop: `listreservations`
//ListReservations returns a list of all reservations the server opened to us. // ListReservations returns a list of all reservations the server opened to us.
ListReservations(context.Context, *ListReservationsRequest) (*ListReservationsResponse, error) ListReservations(context.Context, *ListReservationsRequest) (*ListReservationsResponse, error)
// loop: `instantout` // loop: `instantout`
//InstantOut initiates an instant out swap with the given parameters. // InstantOut initiates an instant out swap with the given parameters.
InstantOut(context.Context, *InstantOutRequest) (*InstantOutResponse, error) InstantOut(context.Context, *InstantOutRequest) (*InstantOutResponse, error)
// loop: `instantoutquote` // loop: `instantoutquote`
//InstantOutQuote returns a quote for an instant out swap with the provided // InstantOutQuote returns a quote for an instant out swap with the provided
//parameters. // parameters.
InstantOutQuote(context.Context, *InstantOutQuoteRequest) (*InstantOutQuoteResponse, error) InstantOutQuote(context.Context, *InstantOutQuoteRequest) (*InstantOutQuoteResponse, error)
// loop: `listinstantouts`
// ListInstantOuts returns a list of all currently known instant out swaps and
// their current status.
ListInstantOuts(context.Context, *ListInstantOutsRequest) (*ListInstantOutsResponse, error)
mustEmbedUnimplementedSwapClientServer() mustEmbedUnimplementedSwapClientServer()
} }
@ -416,6 +452,9 @@ func (UnimplementedSwapClientServer) GetLoopInQuote(context.Context, *QuoteReque
func (UnimplementedSwapClientServer) Probe(context.Context, *ProbeRequest) (*ProbeResponse, error) { func (UnimplementedSwapClientServer) Probe(context.Context, *ProbeRequest) (*ProbeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented") return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented")
} }
func (UnimplementedSwapClientServer) GetL402Tokens(context.Context, *TokensRequest) (*TokensResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetL402Tokens not implemented")
}
func (UnimplementedSwapClientServer) GetLsatTokens(context.Context, *TokensRequest) (*TokensResponse, error) { func (UnimplementedSwapClientServer) GetLsatTokens(context.Context, *TokensRequest) (*TokensResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLsatTokens not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetLsatTokens not implemented")
} }
@ -440,6 +479,9 @@ func (UnimplementedSwapClientServer) InstantOut(context.Context, *InstantOutRequ
func (UnimplementedSwapClientServer) InstantOutQuote(context.Context, *InstantOutQuoteRequest) (*InstantOutQuoteResponse, error) { func (UnimplementedSwapClientServer) InstantOutQuote(context.Context, *InstantOutQuoteRequest) (*InstantOutQuoteResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method InstantOutQuote not implemented") return nil, status.Errorf(codes.Unimplemented, "method InstantOutQuote not implemented")
} }
func (UnimplementedSwapClientServer) ListInstantOuts(context.Context, *ListInstantOutsRequest) (*ListInstantOutsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListInstantOuts not implemented")
}
func (UnimplementedSwapClientServer) mustEmbedUnimplementedSwapClientServer() {} func (UnimplementedSwapClientServer) mustEmbedUnimplementedSwapClientServer() {}
// UnsafeSwapClientServer may be embedded to opt out of forward compatibility for this service. // UnsafeSwapClientServer may be embedded to opt out of forward compatibility for this service.
@ -654,6 +696,24 @@ func _SwapClient_Probe_Handler(srv interface{}, ctx context.Context, dec func(in
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _SwapClient_GetL402Tokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TokensRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).GetL402Tokens(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/GetL402Tokens",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).GetL402Tokens(ctx, req.(*TokensRequest))
}
return interceptor(ctx, in, info, handler)
}
func _SwapClient_GetLsatTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _SwapClient_GetLsatTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TokensRequest) in := new(TokensRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -798,6 +858,24 @@ func _SwapClient_InstantOutQuote_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _SwapClient_ListInstantOuts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListInstantOutsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(SwapClientServer).ListInstantOuts(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/looprpc.SwapClient/ListInstantOuts",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SwapClientServer).ListInstantOuts(ctx, req.(*ListInstantOutsRequest))
}
return interceptor(ctx, in, info, handler)
}
// SwapClient_ServiceDesc is the grpc.ServiceDesc for SwapClient service. // SwapClient_ServiceDesc is the grpc.ServiceDesc for SwapClient service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy) // and not to be introspected or modified (even as a copy)
@ -845,6 +923,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{
MethodName: "Probe", MethodName: "Probe",
Handler: _SwapClient_Probe_Handler, Handler: _SwapClient_Probe_Handler,
}, },
{
MethodName: "GetL402Tokens",
Handler: _SwapClient_GetL402Tokens_Handler,
},
{ {
MethodName: "GetLsatTokens", MethodName: "GetLsatTokens",
Handler: _SwapClient_GetLsatTokens_Handler, Handler: _SwapClient_GetLsatTokens_Handler,
@ -877,6 +959,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{
MethodName: "InstantOutQuote", MethodName: "InstantOutQuote",
Handler: _SwapClient_InstantOutQuote_Handler, Handler: _SwapClient_InstantOutQuote_Handler,
}, },
{
MethodName: "ListInstantOuts",
Handler: _SwapClient_ListInstantOuts_Handler,
},
}, },
Streams: []grpc.StreamDesc{ Streams: []grpc.StreamDesc{
{ {

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.31.0 // protoc-gen-go v1.33.0
// protoc v3.6.1 // protoc v3.21.12
// source: debug.proto // source: debug.proto
package looprpc package looprpc

@ -18,10 +18,9 @@ const _ = grpc.SupportPackageIsVersion7
// //
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type DebugClient interface { type DebugClient interface {
// // ForceAutoLoop is intended for *testing purposes only* and will not work on
//ForceAutoLoop is intended for *testing purposes only* and will not work on // mainnet. This endpoint ticks our autoloop timer, triggering automated
//mainnet. This endpoint ticks our autoloop timer, triggering automated // dispatch of a swap if one is suggested.
//dispatch of a swap if one is suggested.
ForceAutoLoop(ctx context.Context, in *ForceAutoLoopRequest, opts ...grpc.CallOption) (*ForceAutoLoopResponse, error) ForceAutoLoop(ctx context.Context, in *ForceAutoLoopRequest, opts ...grpc.CallOption) (*ForceAutoLoopResponse, error)
} }
@ -46,10 +45,9 @@ func (c *debugClient) ForceAutoLoop(ctx context.Context, in *ForceAutoLoopReques
// All implementations must embed UnimplementedDebugServer // All implementations must embed UnimplementedDebugServer
// for forward compatibility // for forward compatibility
type DebugServer interface { type DebugServer interface {
// // ForceAutoLoop is intended for *testing purposes only* and will not work on
//ForceAutoLoop is intended for *testing purposes only* and will not work on // mainnet. This endpoint ticks our autoloop timer, triggering automated
//mainnet. This endpoint ticks our autoloop timer, triggering automated // dispatch of a swap if one is suggested.
//dispatch of a swap if one is suggested.
ForceAutoLoop(context.Context, *ForceAutoLoopRequest) (*ForceAutoLoopResponse, error) ForceAutoLoop(context.Context, *ForceAutoLoopRequest) (*ForceAutoLoopResponse, error)
mustEmbedUnimplementedDebugServer() mustEmbedUnimplementedDebugServer()
} }

@ -313,6 +313,31 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["looprpc.SwapClient.GetL402Tokens"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &TokensRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSwapClientClient(conn)
resp, err := client.GetL402Tokens(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["looprpc.SwapClient.GetLsatTokens"] = func(ctx context.Context, registry["looprpc.SwapClient.GetLsatTokens"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
@ -512,4 +537,29 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex
} }
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["looprpc.SwapClient.ListInstantOuts"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &ListInstantOutsRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewSwapClientClient(conn)
resp, err := client.ListInstantOuts(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
} }

@ -130,7 +130,7 @@ services:
- "loopd" - "loopd"
- "--network=regtest" - "--network=regtest"
- "--debuglevel=debug" - "--debuglevel=debug"
- "--server.host=loopclient:11009" - "--server.host=loopserver:11009"
- "--server.notls" - "--server.notls"
- "--lnd.host=lndclient:10009" - "--lnd.host=lndclient:10009"
- "--lnd.macaroonpath=/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon" - "--lnd.macaroonpath=/root/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
@ -142,4 +142,4 @@ networks:
volumes: volumes:
bitcoind: bitcoind:
lndserver: lndserver:
lndclient: lndclient:

@ -513,7 +513,7 @@ func (r *lowToHighRoutingPlugin) BeforePayment(ctx context.Context,
// Calculate the limit until we'll disable edges. The way we calculate // Calculate the limit until we'll disable edges. The way we calculate
// this limit is that we take the minimum and maximum fee peers which // this limit is that we take the minimum and maximum fee peers which
// define our fee range. Within this fee range we'll scale linearly // define our fee range. Within this fee range we'll scale linearly
// where each step euqals to the range divided by maxAttempts. // where each step equals to the range divided by maxAttempts.
minFee := r.nodesByMaxFee[0].fee minFee := r.nodesByMaxFee[0].fee
maxFee := r.nodesByMaxFee[len(r.nodesByMaxFee)-1].fee maxFee := r.nodesByMaxFee[len(r.nodesByMaxFee)-1].fee
limit := minFee + limit := minFee +

@ -86,11 +86,11 @@
; Maximum cost in satoshis that loopd is going to pay for an L402 token ; Maximum cost in satoshis that loopd is going to pay for an L402 token
; automatically. Does not include routing fees. ; automatically. Does not include routing fees.
; maxlsatcost=1000 ; maxl402cost=1000
; Maximum routing fee in satoshis that we are willing to pay while paying for an ; Maximum routing fee in satoshis that we are willing to pay while paying for an
; L402 token. ; L402 token.
; maxlsatfee=10 ; maxl402fee=10
; The maximum number of payment parts that may be used for a loop out swap. ; The maximum number of payment parts that may be used for a loop out swap.
; loopoutmaxparts=5 ; loopoutmaxparts=5

@ -16,5 +16,5 @@ docker run \
-e UID=$UID \ -e UID=$UID \
-v "$DIR/../:/build" \ -v "$DIR/../:/build" \
-w /build \ -w /build \
kjconroy/sqlc:1.17.2 generate sqlc/sqlc:1.25.0 generate

@ -42,6 +42,7 @@ type serverMock struct {
swapInvoice string swapInvoice string
swapHash lntypes.Hash swapHash lntypes.Hash
prepayHash lntypes.Hash
// preimagePush is a channel that preimage pushes are sent into. // preimagePush is a channel that preimage pushes are sent into.
preimagePush chan lntypes.Preimage preimagePush chan lntypes.Preimage
@ -81,13 +82,17 @@ func (s *serverMock) NewLoopOutSwap(_ context.Context, swapHash lntypes.Hash,
return nil, errors.New("unexpected test swap amount") return nil, errors.New("unexpected test swap amount")
} }
s.swapHash = swapHash
swapPayReqString, err := getInvoice(swapHash, s.swapInvoiceAmt, swapPayReqString, err := getInvoice(swapHash, s.swapInvoiceAmt,
swapInvoiceDesc) swapInvoiceDesc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
prePayReqString, err := getInvoice(swapHash, s.prepayInvoiceAmt, // Set the prepay hash to be different from the swap hash.
s.prepayHash = swapHash
s.prepayHash[0] ^= 1
prePayReqString, err := getInvoice(s.prepayHash, s.prepayInvoiceAmt,
prepayInvoiceDesc) prepayInvoiceDesc)
if err != nil { if err != nil {
return nil, err return nil, err

@ -1,4 +1,4 @@
package staticaddr package version
import ( import (
looprpc "github.com/lightninglabs/loop/swapserverrpc" looprpc "github.com/lightninglabs/loop/swapserverrpc"

@ -41,7 +41,7 @@ const (
HtlcV3 HtlcV3
) )
// htlcScript defines an interface for the different HTLC implementations. // HtlcScript defines an interface for the different HTLC implementations.
type HtlcScript interface { type HtlcScript interface {
// genSuccessWitness returns the success script to spend this htlc with // genSuccessWitness returns the success script to spend this htlc with
// the preimage. // the preimage.
@ -63,11 +63,11 @@ type HtlcScript interface {
// MaxSuccessWitnessSize returns the maximum witness size for the // MaxSuccessWitnessSize returns the maximum witness size for the
// success case witness. // success case witness.
MaxSuccessWitnessSize() int MaxSuccessWitnessSize() lntypes.WeightUnit
// MaxTimeoutWitnessSize returns the maximum witness size for the // MaxTimeoutWitnessSize returns the maximum witness size for the
// timeout case witness. // timeout case witness.
MaxTimeoutWitnessSize() int MaxTimeoutWitnessSize() lntypes.WeightUnit
// TimeoutScript returns the redeem script required to unlock the htlc // TimeoutScript returns the redeem script required to unlock the htlc
// after timeout. // after timeout.
@ -436,7 +436,7 @@ func (h *HtlcScriptV2) SuccessScript() []byte {
} }
// MaxSuccessWitnessSize returns maximum success witness size. // MaxSuccessWitnessSize returns maximum success witness size.
func (h *HtlcScriptV2) MaxSuccessWitnessSize() int { func (h *HtlcScriptV2) MaxSuccessWitnessSize() lntypes.WeightUnit {
// Calculate maximum success witness size // Calculate maximum success witness size
// //
// - number_of_witness_elements: 1 byte // - number_of_witness_elements: 1 byte
@ -446,11 +446,11 @@ func (h *HtlcScriptV2) MaxSuccessWitnessSize() int {
// - preimage: 32 bytes // - preimage: 32 bytes
// - witness_script_length: 1 byte // - witness_script_length: 1 byte
// - witness_script: len(script) bytes // - witness_script: len(script) bytes
return 1 + 1 + 73 + 1 + 32 + 1 + len(h.script) return lntypes.WeightUnit(1 + 1 + 73 + 1 + 32 + 1 + len(h.script))
} }
// MaxTimeoutWitnessSize returns maximum timeout witness size. // MaxTimeoutWitnessSize returns maximum timeout witness size.
func (h *HtlcScriptV2) MaxTimeoutWitnessSize() int { func (h *HtlcScriptV2) MaxTimeoutWitnessSize() lntypes.WeightUnit {
// Calculate maximum timeout witness size // Calculate maximum timeout witness size
// //
// - number_of_witness_elements: 1 byte // - number_of_witness_elements: 1 byte
@ -461,7 +461,7 @@ func (h *HtlcScriptV2) MaxTimeoutWitnessSize() int {
// - zero: 1 byte // - zero: 1 byte
// - witness_script_length: 1 byte // - witness_script_length: 1 byte
// - witness_script: len(script) bytes // - witness_script: len(script) bytes
return 1 + 1 + 73 + 1 + 33 + 1 + 1 + len(h.script) return lntypes.WeightUnit(1 + 1 + 73 + 1 + 33 + 1 + 1 + len(h.script))
} }
// SuccessSequence returns the sequence to spend this htlc in the success case. // SuccessSequence returns the sequence to spend this htlc in the success case.
@ -735,7 +735,7 @@ func (h *HtlcScriptV3) SuccessScript() []byte {
// MaxSuccessWitnessSize returns the maximum witness size for the // MaxSuccessWitnessSize returns the maximum witness size for the
// success case witness. // success case witness.
func (h *HtlcScriptV3) MaxSuccessWitnessSize() int { func (h *HtlcScriptV3) MaxSuccessWitnessSize() lntypes.WeightUnit {
// Calculate maximum success witness size // Calculate maximum success witness size
// //
// - number_of_witness_elements: 1 byte // - number_of_witness_elements: 1 byte
@ -755,7 +755,7 @@ func (h *HtlcScriptV3) MaxSuccessWitnessSize() int {
// MaxTimeoutWitnessSize returns the maximum witness size for the // MaxTimeoutWitnessSize returns the maximum witness size for the
// timeout case witness. // timeout case witness.
func (h *HtlcScriptV3) MaxTimeoutWitnessSize() int { func (h *HtlcScriptV3) MaxTimeoutWitnessSize() lntypes.WeightUnit {
// Calculate maximum timeout witness size // Calculate maximum timeout witness size
// //
// - number_of_witness_elements: 1 byte // - number_of_witness_elements: 1 byte
@ -768,7 +768,7 @@ func (h *HtlcScriptV3) MaxTimeoutWitnessSize() int {
// - leafVersionAndParity: 1 // - leafVersionAndParity: 1
// - internalPubkey: 32 // - internalPubkey: 32
// - proof: 32 // - proof: 32
return 1 + 1 + 64 + 1 + 36 + 1 + 65 return lntypes.WeightUnit(1 + 1 + 64 + 1 + 36 + 1 + 65)
} }
// SuccessSequence returns the sequence to spend this htlc in the // SuccessSequence returns the sequence to spend this htlc in the

@ -15,7 +15,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/aperture/lsat" "github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
looprpc "github.com/lightninglabs/loop/swapserverrpc" looprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
@ -161,14 +161,14 @@ func (s *grpcSwapServerClient) stop() {
var _ swapServerClient = (*grpcSwapServerClient)(nil) var _ swapServerClient = (*grpcSwapServerClient)(nil)
func newSwapServerClient(cfg *ClientConfig, lsatStore lsat.Store) ( func newSwapServerClient(cfg *ClientConfig, l402Store l402.Store) (
*grpcSwapServerClient, error) { *grpcSwapServerClient, error) {
// Create the server connection with the interceptor that will handle // Create the server connection with the interceptor that will handle
// the LSAT protocol for us. // the L402 protocol for us.
clientInterceptor := lsat.NewInterceptor( clientInterceptor := l402.NewInterceptor(
cfg.Lnd, lsatStore, serverRPCTimeout, cfg.MaxLsatCost, cfg.Lnd, l402Store, serverRPCTimeout, cfg.MaxL402Cost,
cfg.MaxLsatFee, false, cfg.MaxL402Fee, false,
) )
serverConn, err := getSwapServerConn( serverConn, err := getSwapServerConn(
cfg.ServerAddress, cfg.ProxyAddress, cfg.SwapServerNoTLS, cfg.ServerAddress, cfg.ProxyAddress, cfg.SwapServerNoTLS,
@ -636,7 +636,7 @@ const (
paymentTypeInvoice paymentTypeInvoice
) )
// routeCancelMetadata contains cancelation information for swaps that are // routeCancelMetadata contains cancellation information for swaps that are
// canceled because the client could not route off-chain to the server. // canceled because the client could not route off-chain to the server.
type routeCancelMetadata struct { type routeCancelMetadata struct {
// paymentType is the type of payment that failed. // paymentType is the type of payment that failed.
@ -895,7 +895,7 @@ func rpcRouteCancel(details *outCancelDetails) (
// proxyAddr indicates that a SOCKS proxy found at the address should be used to // proxyAddr indicates that a SOCKS proxy found at the address should be used to
// establish the connection. // establish the connection.
func getSwapServerConn(address, proxyAddress string, insecure bool, func getSwapServerConn(address, proxyAddress string, insecure bool,
tlsPath string, interceptor *lsat.ClientInterceptor) (*grpc.ClientConn, tlsPath string, interceptor *l402.ClientInterceptor) (*grpc.ClientConn,
error) { error) {
// Create a dial options array. // Create a dial options array.

@ -1,9 +1,9 @@
FROM golang:1.16.3-buster FROM golang:1.21.9-bookworm
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
git \ git \
protobuf-compiler='3.6*' \ protobuf-compiler='3.21.12*' \
clang-format='1:7.0*' clang-format='1:14.0*'
# We don't want any default values for these variables to make sure they're # We don't want any default values for these variables to make sure they're
# explicitly provided by parsing the go.mod file. Otherwise we might forget to # explicitly provided by parsing the go.mod file. Otherwise we might forget to
@ -11,11 +11,15 @@ RUN apt-get update && apt-get install -y \
ARG PROTOBUF_VERSION ARG PROTOBUF_VERSION
ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0" ENV PROTOC_GEN_GO_GRPC_VERSION="v1.1.0"
ENV GOCACHE=/tmp/build/.cache
ENV GOMODCACHE=/tmp/build/.modcache
RUN cd /tmp \ RUN cd /tmp \
&& export GO111MODULE=on \ && mkdir -p /tmp/build/.cache \
&& go get google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \ && mkdir -p /tmp/build/.modcache \
&& go get google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} && go install google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOBUF_VERSION} \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION} \
&& chmod -R 777 /tmp/build/
WORKDIR /build WORKDIR /build

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.30.0 // protoc-gen-go v1.30.0
// protoc v3.6.1 // protoc v3.21.12
// source: common.proto // source: common.proto
// We can't change this to swapserverrpc, it would be a breaking change because // We can't change this to swapserverrpc, it would be a breaking change because
@ -36,9 +36,8 @@ type HopHint struct {
ChanId uint64 `protobuf:"varint,2,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"` ChanId uint64 `protobuf:"varint,2,opt,name=chan_id,json=chanId,proto3" json:"chan_id,omitempty"`
// The base fee of the channel denominated in millisatoshis. // The base fee of the channel denominated in millisatoshis.
FeeBaseMsat uint32 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"` FeeBaseMsat uint32 `protobuf:"varint,3,opt,name=fee_base_msat,json=feeBaseMsat,proto3" json:"fee_base_msat,omitempty"`
// // The fee rate of the channel for sending one satoshi across it denominated in
//The fee rate of the channel for sending one satoshi across it denominated in // millionths of a satoshi.
//millionths of a satoshi.
FeeProportionalMillionths uint32 `protobuf:"varint,4,opt,name=fee_proportional_millionths,json=feeProportionalMillionths,proto3" json:"fee_proportional_millionths,omitempty"` FeeProportionalMillionths uint32 `protobuf:"varint,4,opt,name=fee_proportional_millionths,json=feeProportionalMillionths,proto3" json:"fee_proportional_millionths,omitempty"`
// The time-lock delta of the channel. // The time-lock delta of the channel.
CltvExpiryDelta uint32 `protobuf:"varint,5,opt,name=cltv_expiry_delta,json=cltvExpiryDelta,proto3" json:"cltv_expiry_delta,omitempty"` CltvExpiryDelta uint32 `protobuf:"varint,5,opt,name=cltv_expiry_delta,json=cltvExpiryDelta,proto3" json:"cltv_expiry_delta,omitempty"`
@ -116,9 +115,8 @@ type RouteHint struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
// // A list of hop hints that when chained together can assist in reaching a
//A list of hop hints that when chained together can assist in reaching a // specific destination.
//specific destination.
HopHints []*HopHint `protobuf:"bytes,1,rep,name=hop_hints,json=hopHints,proto3" json:"hop_hints,omitempty"` HopHints []*HopHint `protobuf:"bytes,1,rep,name=hop_hints,json=hopHints,proto3" json:"hop_hints,omitempty"`
} }

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.30.0 // protoc-gen-go v1.30.0
// protoc v3.6.1 // protoc v3.21.12
// source: instantout.proto // source: instantout.proto
// We can't change this to swapserverrpc, it would be a breaking change because // We can't change this to swapserverrpc, it would be a breaking change because

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.30.0 // protoc-gen-go v1.30.0
// protoc v3.6.1 // protoc v3.21.12
// source: reservation.proto // source: reservation.proto
// We can't change this to swapserverrpc, it would be a breaking change because // We can't change this to swapserverrpc, it would be a breaking change because

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.30.0 // protoc-gen-go v1.30.0
// protoc v3.6.1 // protoc v3.21.12
// source: server.proto // source: server.proto
// We can't change this to swapserverrpc, it would be a breaking change because // We can't change this to swapserverrpc, it would be a breaking change because
@ -25,27 +25,26 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
) )
//* // *
//This enum defines the protocol versions that clients may adhere to. Note that // This enum defines the protocol versions that clients may adhere to. Note that
//this is not a flagged enum. If a particular protocol version adds a feature, // this is not a flagged enum. If a particular protocol version adds a feature,
//then in general all the preceding features are also supported. Exception to this // then in general all the preceding features are also supported. Exception to this
//is when features get deprecated. // is when features get deprecated.
type ProtocolVersion int32 type ProtocolVersion int32
const ( const (
/// No protocol version reported at all. // / No protocol version reported at all.
ProtocolVersion_LEGACY ProtocolVersion = 0 ProtocolVersion_LEGACY ProtocolVersion = 0
/// Client may attempt to send the loop out payment in multiple parts. // / Client may attempt to send the loop out payment in multiple parts.
ProtocolVersion_MULTI_LOOP_OUT ProtocolVersion = 1 ProtocolVersion_MULTI_LOOP_OUT ProtocolVersion = 1
//* // *
//Loop will use native segwit (P2WSH) htlcs by default, while externally // Loop will use native segwit (P2WSH) htlcs by default, while externally
//published htlcs may use native (P2WSH) or nested (NP2WSH) segwit as well. // published htlcs may use native (P2WSH) or nested (NP2WSH) segwit as well.
ProtocolVersion_NATIVE_SEGWIT_LOOP_IN ProtocolVersion = 2 ProtocolVersion_NATIVE_SEGWIT_LOOP_IN ProtocolVersion = 2
// // Once the on chain loop out htlc is confirmed, the client can push the swap
//Once the on chain loop out htlc is confirmed, the client can push the swap // preimage to the server to speed up claim of their off chain htlc (acquiring
//preimage to the server to speed up claim of their off chain htlc (acquiring // incoming liquidity more quickly than if the server waited for the on chain
//incoming liquidity more quickly than if the server waited for the on chain // claim tx).
//claim tx).
ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT ProtocolVersion = 3 ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT ProtocolVersion = 3
// The client will propose a cltv expiry height for loop out. // The client will propose a cltv expiry height for loop out.
ProtocolVersion_USER_EXPIRY_LOOP_OUT ProtocolVersion = 4 ProtocolVersion_USER_EXPIRY_LOOP_OUT ProtocolVersion = 4
@ -138,23 +137,20 @@ const (
ServerSwapState_SERVER_HTLC_PUBLISHED ServerSwapState = 1 ServerSwapState_SERVER_HTLC_PUBLISHED ServerSwapState = 1
// The swap completed successfully. // The swap completed successfully.
ServerSwapState_SERVER_SUCCESS ServerSwapState = 2 ServerSwapState_SERVER_SUCCESS ServerSwapState = 2
// // The swap failed for a reason that is unknown to the server, this is only
//The swap failed for a reason that is unknown to the server, this is only // set for older swaps.
//set for older swaps.
ServerSwapState_SERVER_FAILED_UNKNOWN ServerSwapState = 3 ServerSwapState_SERVER_FAILED_UNKNOWN ServerSwapState = 3
// No htlc was confirmed in time for the loop in swap to complete. // No htlc was confirmed in time for the loop in swap to complete.
ServerSwapState_SERVER_FAILED_NO_HTLC ServerSwapState = 4 ServerSwapState_SERVER_FAILED_NO_HTLC ServerSwapState = 4
// A loop in htlc confirmed on chain, but it did not have the correct value. // A loop in htlc confirmed on chain, but it did not have the correct value.
ServerSwapState_SERVER_FAILED_INVALID_HTLC_AMOUNT ServerSwapState = 5 ServerSwapState_SERVER_FAILED_INVALID_HTLC_AMOUNT ServerSwapState = 5
// // We did not succeed in completing the loop in off chain payment before the
//We did not succeed in completing the loop in off chain payment before the // timeout.
//timeout.
ServerSwapState_SERVER_FAILED_OFF_CHAIN_TIMEOUT ServerSwapState = 6 ServerSwapState_SERVER_FAILED_OFF_CHAIN_TIMEOUT ServerSwapState = 6
// The on chain timeout was claimed. // The on chain timeout was claimed.
ServerSwapState_SERVER_FAILED_TIMEOUT ServerSwapState = 7 ServerSwapState_SERVER_FAILED_TIMEOUT ServerSwapState = 7
// // The server could not publish the loop out on chain htlc before the deadline
//The server could not publish the loop out on chain htlc before the deadline // provided.
//provided.
ServerSwapState_SERVER_FAILED_SWAP_DEADLINE ServerSwapState = 8 ServerSwapState_SERVER_FAILED_SWAP_DEADLINE ServerSwapState = 8
// The server could not publish the loop out on chain htlc. // The server could not publish the loop out on chain htlc.
ServerSwapState_SERVER_FAILED_HTLC_PUBLICATION ServerSwapState = 9 ServerSwapState_SERVER_FAILED_HTLC_PUBLICATION ServerSwapState = 9
@ -169,12 +165,10 @@ const (
// The client canceled the swap because they could not route the swap // The client canceled the swap because they could not route the swap
// payment. // payment.
ServerSwapState_SERVER_CLIENT_INVOICE_CANCEL ServerSwapState = 14 ServerSwapState_SERVER_CLIENT_INVOICE_CANCEL ServerSwapState = 14
// // A loop in swap was rejected because it contained multiple outputs for a
//A loop in swap was rejected because it contained multiple outputs for a // single swap.
//single swap.
ServerSwapState_SERVER_FAILED_MULTIPLE_SWAP_SCRIPTS ServerSwapState = 15 ServerSwapState_SERVER_FAILED_MULTIPLE_SWAP_SCRIPTS ServerSwapState = 15
// // The swap failed during creation.
//The swap failed during creation.
ServerSwapState_SERVER_FAILED_INITIALIZATION ServerSwapState = 16 ServerSwapState_SERVER_FAILED_INITIALIZATION ServerSwapState = 16
) )
@ -306,25 +300,19 @@ func (RoutePaymentType) EnumDescriptor() ([]byte, []int) {
type PaymentFailureReason int32 type PaymentFailureReason int32
const ( const (
// // Payment isn't failed (yet).
//Payment isn't failed (yet).
PaymentFailureReason_LND_FAILURE_REASON_NONE PaymentFailureReason = 0 PaymentFailureReason_LND_FAILURE_REASON_NONE PaymentFailureReason = 0
// // There are more routes to try, but the payment timeout was exceeded.
//There are more routes to try, but the payment timeout was exceeded.
PaymentFailureReason_LND_FAILURE_REASON_TIMEOUT PaymentFailureReason = 1 PaymentFailureReason_LND_FAILURE_REASON_TIMEOUT PaymentFailureReason = 1
// // All possible routes were tried and failed permanently. Or were no
//All possible routes were tried and failed permanently. Or were no // routes to the destination at all.
//routes to the destination at all.
PaymentFailureReason_LND_FAILURE_REASON_NO_ROUTE PaymentFailureReason = 2 PaymentFailureReason_LND_FAILURE_REASON_NO_ROUTE PaymentFailureReason = 2
// // A non-recoverable error has occured.
//A non-recoverable error has occured.
PaymentFailureReason_LND_FAILURE_REASON_ERROR PaymentFailureReason = 3 PaymentFailureReason_LND_FAILURE_REASON_ERROR PaymentFailureReason = 3
// // Payment details incorrect (unknown hash, invalid amt or
//Payment details incorrect (unknown hash, invalid amt or // invalid final cltv delta)
//invalid final cltv delta)
PaymentFailureReason_LND_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS PaymentFailureReason = 4 PaymentFailureReason_LND_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS PaymentFailureReason = 4
// // Insufficient local balance.
//Insufficient local balance.
PaymentFailureReason_LND_FAILURE_REASON_INSUFFICIENT_BALANCE PaymentFailureReason = 5 PaymentFailureReason_LND_FAILURE_REASON_INSUFFICIENT_BALANCE PaymentFailureReason = 5
) )
@ -477,9 +465,9 @@ type ServerLoopOutRequest struct {
ReceiverKey []byte `protobuf:"bytes,1,opt,name=receiver_key,json=receiverKey,proto3" json:"receiver_key,omitempty"` ReceiverKey []byte `protobuf:"bytes,1,opt,name=receiver_key,json=receiverKey,proto3" json:"receiver_key,omitempty"`
SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"` SwapHash []byte `protobuf:"bytes,2,opt,name=swap_hash,json=swapHash,proto3" json:"swap_hash,omitempty"`
Amt uint64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"` Amt uint64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"`
/// The unix time in seconds we want the on-chain swap to be published by. // / The unix time in seconds we want the on-chain swap to be published by.
SwapPublicationDeadline int64 `protobuf:"varint,4,opt,name=swap_publication_deadline,json=swapPublicationDeadline,proto3" json:"swap_publication_deadline,omitempty"` SwapPublicationDeadline int64 `protobuf:"varint,4,opt,name=swap_publication_deadline,json=swapPublicationDeadline,proto3" json:"swap_publication_deadline,omitempty"`
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,5,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,5,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The requested absolute block height of the on-chain htlc. This is // The requested absolute block height of the on-chain htlc. This is
// subjected to min and max constraints as reported in the LoopOutTerms // subjected to min and max constraints as reported in the LoopOutTerms
@ -488,10 +476,13 @@ type ServerLoopOutRequest struct {
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,7,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,7,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -666,11 +657,11 @@ type ServerLoopOutQuoteRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
/// The swap amount. If zero, a quote for a maximum amt swap will be given. // / The swap amount. If zero, a quote for a maximum amt swap will be given.
Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
/// The unix time in seconds we want the on-chain swap to be published by. // / The unix time in seconds we want the on-chain swap to be published by.
SwapPublicationDeadline int64 `protobuf:"varint,2,opt,name=swap_publication_deadline,json=swapPublicationDeadline,proto3" json:"swap_publication_deadline,omitempty"` SwapPublicationDeadline int64 `protobuf:"varint,2,opt,name=swap_publication_deadline,json=swapPublicationDeadline,proto3" json:"swap_publication_deadline,omitempty"`
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,3,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,3,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The requested absolute block height of the on-chain htlc. This is // The requested absolute block height of the on-chain htlc. This is
// subjected to min and max constraints as reported in the LoopOutTerms // subjected to min and max constraints as reported in the LoopOutTerms
@ -679,10 +670,13 @@ type ServerLoopOutQuoteRequest struct {
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,5,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,5,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -759,9 +753,9 @@ type ServerLoopOutQuote struct {
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
SwapPaymentDest string `protobuf:"bytes,1,opt,name=swap_payment_dest,json=swapPaymentDest,proto3" json:"swap_payment_dest,omitempty"` SwapPaymentDest string `protobuf:"bytes,1,opt,name=swap_payment_dest,json=swapPaymentDest,proto3" json:"swap_payment_dest,omitempty"`
/// The total estimated swap fee given the quote amt. // / The total estimated swap fee given the quote amt.
SwapFee int64 `protobuf:"varint,2,opt,name=swap_fee,json=swapFee,proto3" json:"swap_fee,omitempty"` SwapFee int64 `protobuf:"varint,2,opt,name=swap_fee,json=swapFee,proto3" json:"swap_fee,omitempty"`
/// Deprecated, total swap fee given quote amt is calculated in swap_fee. // / Deprecated, total swap fee given quote amt is calculated in swap_fee.
// //
// Deprecated: Marked as deprecated in server.proto. // Deprecated: Marked as deprecated in server.proto.
SwapFeeRate int64 `protobuf:"varint,3,opt,name=swap_fee_rate,json=swapFeeRate,proto3" json:"swap_fee_rate,omitempty"` SwapFeeRate int64 `protobuf:"varint,3,opt,name=swap_fee_rate,json=swapFeeRate,proto3" json:"swap_fee_rate,omitempty"`
@ -867,15 +861,18 @@ type ServerLoopOutTermsRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -1009,17 +1006,20 @@ type ServerLoopInRequest struct {
Amt uint64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"` Amt uint64 `protobuf:"varint,3,opt,name=amt,proto3" json:"amt,omitempty"`
SwapInvoice string `protobuf:"bytes,4,opt,name=swap_invoice,json=swapInvoice,proto3" json:"swap_invoice,omitempty"` SwapInvoice string `protobuf:"bytes,4,opt,name=swap_invoice,json=swapInvoice,proto3" json:"swap_invoice,omitempty"`
LastHop []byte `protobuf:"bytes,5,opt,name=last_hop,json=lastHop,proto3" json:"last_hop,omitempty"` LastHop []byte `protobuf:"bytes,5,opt,name=last_hop,json=lastHop,proto3" json:"last_hop,omitempty"`
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,6,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,6,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// An invoice that can be used for the purpose of probing. // An invoice that can be used for the purpose of probing.
ProbeInvoice string `protobuf:"bytes,7,opt,name=probe_invoice,json=probeInvoice,proto3" json:"probe_invoice,omitempty"` ProbeInvoice string `protobuf:"bytes,7,opt,name=probe_invoice,json=probeInvoice,proto3" json:"probe_invoice,omitempty"`
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,8,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,8,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -1195,7 +1195,7 @@ type ServerLoopInQuoteRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
/// The swap amount. If zero, a quote for a maximum amt swap will be given. // / The swap amount. If zero, a quote for a maximum amt swap will be given.
Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"` Amt uint64 `protobuf:"varint,1,opt,name=amt,proto3" json:"amt,omitempty"`
// The destination pubkey. // The destination pubkey.
Pubkey []byte `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty"` Pubkey []byte `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty"`
@ -1203,15 +1203,18 @@ type ServerLoopInQuoteRequest struct {
LastHop []byte `protobuf:"bytes,4,opt,name=last_hop,json=lastHop,proto3" json:"last_hop,omitempty"` LastHop []byte `protobuf:"bytes,4,opt,name=last_hop,json=lastHop,proto3" json:"last_hop,omitempty"`
// Optional route hints to reach the destination through private channels. // Optional route hints to reach the destination through private channels.
RouteHints []*RouteHint `protobuf:"bytes,5,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"` RouteHints []*RouteHint `protobuf:"bytes,5,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,2,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,2,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,6,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,6,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -1379,15 +1382,18 @@ type ServerLoopInTermsRequest struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
/// The protocol version that the client adheres to. // / The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// The user agent string that identifies the software running on the user's // The user agent string that identifies the software running on the user's
// side. This can be changed in the user's client software but it _SHOULD_ // side. This can be changed in the user's client software but it _SHOULD_
// conform to the following pattern: // conform to the following pattern:
// Agent-Name/semver-version(/additional-info) //
// Agent-Name/semver-version(/additional-info)
//
// Examples: // Examples:
// loopd/v0.10.0-beta/commit=3b635821 //
// litd/v0.2.0-alpha/commit=326d754 // loopd/v0.10.0-beta/commit=3b635821
// litd/v0.2.0-alpha/commit=326d754
UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` UserAgent string `protobuf:"bytes,2,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"`
} }
@ -1502,9 +1508,8 @@ type ServerLoopOutPushPreimageRequest struct {
// The protocol version that the client adheres to. // The protocol version that the client adheres to.
ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"` ProtocolVersion ProtocolVersion `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3,enum=looprpc.ProtocolVersion" json:"protocol_version,omitempty"`
// // Preimage is the preimage of the loop out swap that we wish to push to the
//Preimage is the preimage of the loop out swap that we wish to push to the // server to speed up off-chain claim once the on-chain htlc has confirmed.
//server to speed up off-chain claim once the on-chain htlc has confirmed.
Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"` Preimage []byte `protobuf:"bytes,2,opt,name=preimage,proto3" json:"preimage,omitempty"`
} }
@ -1893,6 +1898,7 @@ type CancelLoopOutSwapRequest struct {
// Additional information about the swap cancelation. // Additional information about the swap cancelation.
// //
// Types that are assignable to CancelInfo: // Types that are assignable to CancelInfo:
//
// *CancelLoopOutSwapRequest_RouteCancel // *CancelLoopOutSwapRequest_RouteCancel
CancelInfo isCancelLoopOutSwapRequest_CancelInfo `protobuf_oneof:"cancel_info"` CancelInfo isCancelLoopOutSwapRequest_CancelInfo `protobuf_oneof:"cancel_info"`
} }

@ -14,6 +14,8 @@ import (
"github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
// Sweeper creates htlc sweep txes. // Sweeper creates htlc sweep txes.
@ -181,10 +183,26 @@ func (s *Sweeper) GetSweepFee(ctx context.Context,
destAddr btcutil.Address, sweepConfTarget int32) ( destAddr btcutil.Address, sweepConfTarget int32) (
btcutil.Amount, error) { btcutil.Amount, error) {
// Use GetSweepFeeDetails to get the fee and other unused data.
fee, _, _, err := s.GetSweepFeeDetails(
ctx, addInputEstimate, destAddr, sweepConfTarget,
)
return fee, err
}
// GetSweepFee calculates the required tx fee to spend to P2WKH. It takes a
// function that is expected to add the weight of the input to the weight
// estimator. It returns also the fee rate and transaction weight.
func (s *Sweeper) GetSweepFeeDetails(ctx context.Context,
addInputEstimate func(*input.TxWeightEstimator) error,
destAddr btcutil.Address, sweepConfTarget int32) (
btcutil.Amount, chainfee.SatPerKWeight, lntypes.WeightUnit, error) {
// Get fee estimate from lnd. // Get fee estimate from lnd.
feeRate, err := s.Lnd.WalletKit.EstimateFeeRate(ctx, sweepConfTarget) feeRate, err := s.Lnd.WalletKit.EstimateFeeRate(ctx, sweepConfTarget)
if err != nil { if err != nil {
return 0, fmt.Errorf("estimate fee: %v", err) return 0, 0, 0, fmt.Errorf("estimate fee: %v", err)
} }
// Calculate weight for this tx. // Calculate weight for this tx.
@ -206,16 +224,16 @@ func (s *Sweeper) GetSweepFee(ctx context.Context,
weightEstimate.AddP2TROutput() weightEstimate.AddP2TROutput()
default: default:
return 0, fmt.Errorf("estimate fee: unknown address type %T", return 0, 0, 0, fmt.Errorf("estimate fee: unknown address "+
destAddr) "type %T", destAddr)
} }
err = addInputEstimate(&weightEstimate) err = addInputEstimate(&weightEstimate)
if err != nil { if err != nil {
return 0, err return 0, 0, 0, err
} }
weight := weightEstimate.Weight() weight := weightEstimate.Weight()
return feeRate.FeeForWeight(int64(weight)), nil return feeRate.FeeForWeight(weight), feeRate, weight, nil
} }

@ -17,7 +17,8 @@ func init() {
UseLogger(build.NewSubLogger("SWEEP", nil)) UseLogger(build.NewSubLogger("SWEEP", nil))
} }
// batchPrefixLogger returns a logger that prefixes all log messages with the ID. // batchPrefixLogger returns a logger that prefixes all log messages with
// the ID.
func batchPrefixLogger(batchID string) btclog.Logger { func batchPrefixLogger(batchID string) btclog.Logger {
return build.NewPrefixLog(fmt.Sprintf("[Batch %s]", batchID), log) return build.NewPrefixLog(fmt.Sprintf("[Batch %s]", batchID), log)
} }

@ -3,6 +3,7 @@ package sweepbatcher
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
@ -14,22 +15,28 @@ import (
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
type BaseDB interface { // Querier is the interface that contains all the queries generated
// by sqlc for sweep batcher.
type Querier interface {
// ConfirmBatch confirms a batch by setting the state to confirmed. // ConfirmBatch confirms a batch by setting the state to confirmed.
ConfirmBatch(ctx context.Context, id int32) error ConfirmBatch(ctx context.Context, id int32) error
// GetBatchSweeps fetches all the sweeps that are part a batch. // GetBatchSweeps fetches all the sweeps that are part a batch.
GetBatchSweeps(ctx context.Context, batchID int32) ( GetBatchSweeps(ctx context.Context, batchID int32) (
[]sqlc.GetBatchSweepsRow, error) []sqlc.Sweep, error)
// GetBatchSweptAmount returns the total amount of sats swept by a
// (confirmed) batch.
GetBatchSweptAmount(ctx context.Context, batchID int32) (int64, error)
// GetSweepStatus returns true if the sweep has been completed. // GetSweepStatus returns true if the sweep has been completed.
GetSweepStatus(ctx context.Context, swapHash []byte) (bool, error) GetSweepStatus(ctx context.Context, swapHash []byte) (bool, error)
// GetSwapUpdates fetches all the updates for a swap. // GetParentBatch fetches the parent batch of a completed sweep.
GetSwapUpdates(ctx context.Context, swapHash []byte) ( GetParentBatch(ctx context.Context, swapHash []byte) (sqlc.SweepBatch,
[]sqlc.SwapUpdate, error) error)
// FetchUnconfirmedSweepBatches fetches all the batches from the // GetUnconfirmedBatches fetches all the batches from the
// database that are not in a confirmed state. // database that are not in a confirmed state.
GetUnconfirmedBatches(ctx context.Context) ([]sqlc.SweepBatch, error) GetUnconfirmedBatches(ctx context.Context) ([]sqlc.SweepBatch, error)
@ -38,17 +45,26 @@ type BaseDB interface {
InsertBatch(ctx context.Context, arg sqlc.InsertBatchParams) ( InsertBatch(ctx context.Context, arg sqlc.InsertBatchParams) (
int32, error) int32, error)
// DropBatch drops a batch from the database.
DropBatch(ctx context.Context, id int32) error
// UpdateBatch updates a batch in the database. // UpdateBatch updates a batch in the database.
UpdateBatch(ctx context.Context, arg sqlc.UpdateBatchParams) error UpdateBatch(ctx context.Context, arg sqlc.UpdateBatchParams) error
// UpsertSweep inserts a sweep into the database, or updates an existing // UpsertSweep inserts a sweep into the database, or updates an existing
// sweep if it already exists. // sweep if it already exists.
UpsertSweep(ctx context.Context, arg sqlc.UpsertSweepParams) error UpsertSweep(ctx context.Context, arg sqlc.UpsertSweepParams) error
}
// BaseDB is the interface that contains all the queries generated
// by sqlc for sweep batcher and transaction functionality.
type BaseDB interface {
Querier
// ExecTx allows for executing a function in the context of a database // ExecTx allows for executing a function in the context of a database
// transaction. // transaction.
ExecTx(ctx context.Context, txOptions loopdb.TxOptions, ExecTx(ctx context.Context, txOptions loopdb.TxOptions,
txBody func(*sqlc.Queries) error) error txBody func(Querier) error) error
} }
// SQLStore manages the reservations in the database. // SQLStore manages the reservations in the database.
@ -70,8 +86,8 @@ func NewSQLStore(db BaseDB, network *chaincfg.Params) *SQLStore {
// FetchUnconfirmedSweepBatches fetches all the batches from the database that // FetchUnconfirmedSweepBatches fetches all the batches from the database that
// are not in a confirmed state. // are not in a confirmed state.
func (s *SQLStore) FetchUnconfirmedSweepBatches(ctx context.Context) ([]*dbBatch, func (s *SQLStore) FetchUnconfirmedSweepBatches(ctx context.Context) (
error) { []*dbBatch, error) {
var batches []*dbBatch var batches []*dbBatch
@ -100,6 +116,24 @@ func (s *SQLStore) InsertSweepBatch(ctx context.Context, batch *dbBatch) (int32,
return s.baseDb.InsertBatch(ctx, batchToInsertArgs(*batch)) return s.baseDb.InsertBatch(ctx, batchToInsertArgs(*batch))
} }
// DropBatch drops a batch from the database. Note that we only use this call
// for batches that have no sweeps and so we'd not be able to resume.
func (s *SQLStore) DropBatch(ctx context.Context, id int32) error {
readOpts := loopdb.NewSqlWriteOpts()
return s.baseDb.ExecTx(ctx, readOpts, func(tx Querier) error {
dbSweeps, err := tx.GetBatchSweeps(ctx, id)
if err != nil {
return err
}
if len(dbSweeps) != 0 {
return fmt.Errorf("cannot drop a non-empty batch")
}
return tx.DropBatch(ctx, id)
})
}
// UpdateSweepBatch updates a batch in the database. // UpdateSweepBatch updates a batch in the database.
func (s *SQLStore) UpdateSweepBatch(ctx context.Context, batch *dbBatch) error { func (s *SQLStore) UpdateSweepBatch(ctx context.Context, batch *dbBatch) error {
return s.baseDb.UpdateBatch(ctx, batchToUpdateArgs(*batch)) return s.baseDb.UpdateBatch(ctx, batchToUpdateArgs(*batch))
@ -117,21 +151,14 @@ func (s *SQLStore) FetchBatchSweeps(ctx context.Context, id int32) (
readOpts := loopdb.NewSqlReadOpts() readOpts := loopdb.NewSqlReadOpts()
var sweeps []*dbSweep var sweeps []*dbSweep
err := s.baseDb.ExecTx(ctx, readOpts, func(tx *sqlc.Queries) error { err := s.baseDb.ExecTx(ctx, readOpts, func(tx Querier) error {
dbSweeps, err := tx.GetBatchSweeps(ctx, id) dbSweeps, err := tx.GetBatchSweeps(ctx, id)
if err != nil { if err != nil {
return err return err
} }
for _, dbSweep := range dbSweeps { for _, dbSweep := range dbSweeps {
updates, err := s.baseDb.GetSwapUpdates( sweep, err := s.convertSweepRow(dbSweep)
ctx, dbSweep.SwapHash,
)
if err != nil {
return err
}
sweep, err := s.convertSweepRow(dbSweep, updates)
if err != nil { if err != nil {
return err return err
} }
@ -148,6 +175,34 @@ func (s *SQLStore) FetchBatchSweeps(ctx context.Context, id int32) (
return sweeps, nil return sweeps, nil
} }
// TotalSweptAmount returns the total amount swept by a (confirmed) batch.
func (s *SQLStore) TotalSweptAmount(ctx context.Context, id int32) (
btcutil.Amount, error) {
amt, err := s.baseDb.GetBatchSweptAmount(ctx, id)
if err != nil {
return 0, err
}
return btcutil.Amount(amt), nil
}
// GetParentBatch fetches the parent batch of a completed sweep.
func (s *SQLStore) GetParentBatch(ctx context.Context, swapHash lntypes.Hash) (
*dbBatch, error) {
batch, err := s.baseDb.GetParentBatch(ctx, swapHash[:])
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
return convertBatchRow(batch), nil
}
// UpsertSweep inserts a sweep into the database, or updates an existing sweep // UpsertSweep inserts a sweep into the database, or updates an existing sweep
// if it already exists. // if it already exists.
func (s *SQLStore) UpsertSweep(ctx context.Context, sweep *dbSweep) error { func (s *SQLStore) UpsertSweep(ctx context.Context, sweep *dbSweep) error {
@ -202,9 +257,6 @@ type dbSweep struct {
// Completed indicates whether this sweep is completed. // Completed indicates whether this sweep is completed.
Completed bool Completed bool
// LoopOut is the loop out that the sweep belongs to.
LoopOut *loopdb.LoopOut
} }
// convertBatchRow converts a batch row from db to a sweepbatcher.Batch struct. // convertBatchRow converts a batch row from db to a sweepbatcher.Batch struct.
@ -296,9 +348,7 @@ func batchToUpdateArgs(batch dbBatch) sqlc.UpdateBatchParams {
} }
// convertSweepRow converts a sweep row from db to a sweep struct. // convertSweepRow converts a sweep row from db to a sweep struct.
func (s *SQLStore) convertSweepRow(row sqlc.GetBatchSweepsRow, func (s *SQLStore) convertSweepRow(row sqlc.Sweep) (dbSweep, error) {
updates []sqlc.SwapUpdate) (dbSweep, error) {
sweep := dbSweep{ sweep := dbSweep{
ID: row.ID, ID: row.ID,
BatchID: row.BatchID, BatchID: row.BatchID,
@ -322,40 +372,7 @@ func (s *SQLStore) convertSweepRow(row sqlc.GetBatchSweepsRow,
Index: uint32(row.OutpointIndex), Index: uint32(row.OutpointIndex),
} }
sweep.LoopOut, err = loopdb.ConvertLoopOutRow( return sweep, nil
s.network,
sqlc.GetLoopOutSwapRow{
ID: row.ID,
SwapHash: row.SwapHash,
Preimage: row.Preimage,
InitiationTime: row.InitiationTime,
AmountRequested: row.AmountRequested,
CltvExpiry: row.CltvExpiry,
MaxMinerFee: row.MaxMinerFee,
MaxSwapFee: row.MaxSwapFee,
InitiationHeight: row.InitiationHeight,
ProtocolVersion: row.ProtocolVersion,
Label: row.Label,
DestAddress: row.DestAddress,
SwapInvoice: row.SwapInvoice,
MaxSwapRoutingFee: row.MaxSwapRoutingFee,
SweepConfTarget: row.SweepConfTarget,
HtlcConfirmations: row.HtlcConfirmations,
OutgoingChanSet: row.OutgoingChanSet,
PrepayInvoice: row.PrepayInvoice,
MaxPrepayRoutingFee: row.MaxPrepayRoutingFee,
PublicationDeadline: row.PublicationDeadline,
SingleSweep: row.SingleSweep,
SenderScriptPubkey: row.SenderScriptPubkey,
ReceiverScriptPubkey: row.ReceiverScriptPubkey,
SenderInternalPubkey: row.SenderInternalPubkey,
ReceiverInternalPubkey: row.ReceiverInternalPubkey,
ClientKeyFamily: row.ClientKeyFamily,
ClientKeyIndex: row.ClientKeyIndex,
}, updates,
)
return sweep, err
} }
// sweepToUpsertArgs converts a Sweep struct to the arguments needed to insert. // sweepToUpsertArgs converts a Sweep struct to the arguments needed to insert.

@ -5,6 +5,7 @@ import (
"errors" "errors"
"sort" "sort"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
) )
@ -22,7 +23,7 @@ func NewStoreMock() *StoreMock {
} }
} }
// FetchUnconfirmedBatches fetches all the loop out sweep batches from the // FetchUnconfirmedSweepBatches fetches all the loop out sweep batches from the
// database that are not in a confirmed state. // database that are not in a confirmed state.
func (s *StoreMock) FetchUnconfirmedSweepBatches(ctx context.Context) ( func (s *StoreMock) FetchUnconfirmedSweepBatches(ctx context.Context) (
[]*dbBatch, error) { []*dbBatch, error) {
@ -55,6 +56,12 @@ func (s *StoreMock) InsertSweepBatch(ctx context.Context,
return id, nil return id, nil
} }
// DropBatch drops a batch from the database.
func (s *StoreMock) DropBatch(ctx context.Context, id int32) error {
delete(s.batches, id)
return nil
}
// UpdateSweepBatch updates a batch in the database. // UpdateSweepBatch updates a batch in the database.
func (s *StoreMock) UpdateSweepBatch(ctx context.Context, func (s *StoreMock) UpdateSweepBatch(ctx context.Context,
batch *dbBatch) error { batch *dbBatch) error {
@ -123,3 +130,44 @@ func (s *StoreMock) AssertSweepStored(id lntypes.Hash) bool {
_, ok := s.sweeps[id] _, ok := s.sweeps[id]
return ok return ok
} }
// GetParentBatch returns the parent batch of a swap.
func (s *StoreMock) GetParentBatch(ctx context.Context, swapHash lntypes.Hash) (
*dbBatch, error) {
for _, sweep := range s.sweeps {
if sweep.SwapHash == swapHash {
batch, ok := s.batches[sweep.BatchID]
if !ok {
return nil, errors.New("batch not found")
}
return &batch, nil
}
}
return nil, errors.New("batch not found")
}
// TotalSweptAmount returns the total amount of BTC that has been swept from a
// batch.
func (s *StoreMock) TotalSweptAmount(ctx context.Context, batchID int32) (
btcutil.Amount, error) {
batch, ok := s.batches[batchID]
if !ok {
return 0, errors.New("batch not found")
}
if batch.State != batchConfirmed && batch.State != batchClosed {
return 0, nil
}
var total btcutil.Amount
for _, sweep := range s.sweeps {
if sweep.BatchID == batchID {
total += sweep.Amount
}
}
return 0, nil
}

@ -3,6 +3,8 @@ package sweepbatcher
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/hex"
"errors"
"fmt" "fmt"
"math" "math"
"sync" "sync"
@ -33,10 +35,6 @@ const (
// fee rate is increased when an rbf is attempted. // fee rate is increased when an rbf is attempted.
defaultFeeRateStep = chainfee.SatPerKWeight(100) defaultFeeRateStep = chainfee.SatPerKWeight(100)
// defaultBatchConfTarget is the default confirmation target of the
// batch transaction.
defaultBatchConfTarget = 12
// batchConfHeight is the default confirmation height of the batch // batchConfHeight is the default confirmation height of the batch
// transaction. // transaction.
batchConfHeight = 3 batchConfHeight = 3
@ -47,7 +45,7 @@ const (
) )
var ( var (
ErrBatchShuttingDown = fmt.Errorf("batch shutting down") ErrBatchShuttingDown = errors.New("batch shutting down")
) )
// sweep stores any data related to sweeping a specific outpoint. // sweep stores any data related to sweeping a specific outpoint.
@ -195,7 +193,15 @@ type batch struct {
// main event loop. // main event loop.
callLeave chan struct{} callLeave chan struct{}
// quit signals that the batch must stop. // stopping signals that the batch is stopping.
stopping chan struct{}
// finished signals that the batch has stopped and all child goroutines
// have finished.
finished chan struct{}
// quit is owned by the parent batcher and signals that the batch must
// stop.
quit chan struct{} quit chan struct{}
// wallet is the wallet client used to create and publish the batch // wallet is the wallet client used to create and publish the batch
@ -209,7 +215,7 @@ type batch struct {
// signerClient is the signer client used to sign the batch transaction. // signerClient is the signer client used to sign the batch transaction.
signerClient lndclient.SignerClient signerClient lndclient.SignerClient
// muSig2Kit includes all the required functionality to collect // muSig2SignSweep includes all the required functionality to collect
// and verify signatures by the swap server in order to cooperatively // and verify signatures by the swap server in order to cooperatively
// sweep funds. // sweep funds.
muSig2SignSweep MuSig2SignSweep muSig2SignSweep MuSig2SignSweep
@ -259,6 +265,7 @@ type batchKit struct {
purger Purger purger Purger
store BatcherStore store BatcherStore
log btclog.Logger log btclog.Logger
quit chan struct{}
} }
// scheduleNextCall schedules the next call to the batch handler's main event // scheduleNextCall schedules the next call to the batch handler's main event
@ -268,6 +275,12 @@ func (b *batch) scheduleNextCall() (func(), error) {
case b.callEnter <- struct{}{}: case b.callEnter <- struct{}{}:
case <-b.quit: case <-b.quit:
return func() {}, ErrBatcherShuttingDown
case <-b.stopping:
return func() {}, ErrBatchShuttingDown
case <-b.finished:
return func() {}, ErrBatchShuttingDown return func() {}, ErrBatchShuttingDown
} }
@ -291,7 +304,9 @@ func NewBatch(cfg batchConfig, bk batchKit) *batch {
errChan: make(chan error, 1), errChan: make(chan error, 1),
callEnter: make(chan struct{}), callEnter: make(chan struct{}),
callLeave: make(chan struct{}), callLeave: make(chan struct{}),
quit: make(chan struct{}), stopping: make(chan struct{}),
finished: make(chan struct{}),
quit: bk.quit,
batchTxid: bk.batchTxid, batchTxid: bk.batchTxid,
wallet: bk.wallet, wallet: bk.wallet,
chainNotifier: bk.chainNotifier, chainNotifier: bk.chainNotifier,
@ -305,7 +320,22 @@ func NewBatch(cfg batchConfig, bk batchKit) *batch {
} }
// NewBatchFromDB creates a new batch that already existed in storage. // NewBatchFromDB creates a new batch that already existed in storage.
func NewBatchFromDB(cfg batchConfig, bk batchKit) *batch { func NewBatchFromDB(cfg batchConfig, bk batchKit) (*batch, error) {
// Make sure the batch is not empty.
if len(bk.sweeps) == 0 {
// This should never happen, as this precondition is already
// ensured in spinUpBatchFromDB.
return nil, fmt.Errorf("empty batch is not allowed")
}
// Assign batchConfTarget to primary sweep's confTarget.
for _, sweep := range bk.sweeps {
if sweep.swapHash == bk.primaryID {
cfg.batchConfTarget = sweep.confTarget
break
}
}
return &batch{ return &batch{
id: bk.id, id: bk.id,
state: bk.state, state: bk.state,
@ -318,7 +348,9 @@ func NewBatchFromDB(cfg batchConfig, bk batchKit) *batch {
errChan: make(chan error, 1), errChan: make(chan error, 1),
callEnter: make(chan struct{}), callEnter: make(chan struct{}),
callLeave: make(chan struct{}), callLeave: make(chan struct{}),
quit: make(chan struct{}), stopping: make(chan struct{}),
finished: make(chan struct{}),
quit: bk.quit,
batchTxid: bk.batchTxid, batchTxid: bk.batchTxid,
batchPkScript: bk.batchPkScript, batchPkScript: bk.batchPkScript,
rbfCache: bk.rbfCache, rbfCache: bk.rbfCache,
@ -331,7 +363,7 @@ func NewBatchFromDB(cfg batchConfig, bk batchKit) *batch {
store: bk.store, store: bk.store,
log: bk.log, log: bk.log,
cfg: &cfg, cfg: &cfg,
} }, nil
} }
// addSweep tries to add a sweep to the batch. If this is the first sweep being // addSweep tries to add a sweep to the batch. If this is the first sweep being
@ -437,7 +469,7 @@ func (b *batch) sweepExists(hash lntypes.Hash) bool {
// Wait waits for the batch to gracefully stop. // Wait waits for the batch to gracefully stop.
func (b *batch) Wait() { func (b *batch) Wait() {
b.log.Infof("Stopping") b.log.Infof("Stopping")
b.wg.Wait() <-b.finished
} }
// Run is the batch's main event loop. // Run is the batch's main event loop.
@ -445,8 +477,12 @@ func (b *batch) Run(ctx context.Context) error {
runCtx, cancel := context.WithCancel(ctx) runCtx, cancel := context.WithCancel(ctx)
defer func() { defer func() {
cancel() cancel()
close(b.quit) close(b.stopping)
// Make sure not to call b.wg.Wait from any other place to avoid
// race condition between b.wg.Add(1) and b.wg.Wait().
b.wg.Wait() b.wg.Wait()
close(b.finished)
}() }()
if b.muSig2SignSweep == nil { if b.muSig2SignSweep == nil {
@ -538,6 +574,12 @@ func (b *batch) publish(ctx context.Context) error {
coopSuccess bool coopSuccess bool
) )
if len(b.sweeps) == 0 {
b.log.Debugf("skipping publish: no sweeps in the batch")
return nil
}
// Run the RBF rate update. // Run the RBF rate update.
err = b.updateRbfRate(ctx) err = b.updateRbfRate(ctx)
if err != nil { if err != nil {
@ -574,9 +616,11 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
batchTx.LockTime = uint32(b.currentHeight) batchTx.LockTime = uint32(b.currentHeight)
var ( var (
batchAmt btcutil.Amount batchAmt btcutil.Amount
prevOuts = make([]*wire.TxOut, 0, len(b.sweeps)) prevOuts = make([]*wire.TxOut, 0, len(b.sweeps))
signDescs = make([]*lndclient.SignDescriptor, 0, len(b.sweeps)) signDescs = make(
[]*lndclient.SignDescriptor, 0, len(b.sweeps),
)
sweeps = make([]sweep, 0, len(b.sweeps)) sweeps = make([]sweep, 0, len(b.sweeps))
fee btcutil.Amount fee btcutil.Amount
inputCounter int inputCounter int
@ -665,9 +709,7 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
weightEstimate.AddP2TROutput() weightEstimate.AddP2TROutput()
totalWeight := int64(weightEstimate.Weight()) fee = b.rbfCache.FeeRate.FeeForWeight(weightEstimate.Weight())
fee = b.rbfCache.FeeRate.FeeForWeight(totalWeight)
// Clamp the calculated fee to the max allowed fee amount for the batch. // Clamp the calculated fee to the max allowed fee amount for the batch.
fee = clampBatchFee(fee, batchAmt) fee = clampBatchFee(fee, batchAmt)
@ -700,9 +742,11 @@ func (b *batch) publishBatch(ctx context.Context) (btcutil.Amount, error) {
batchTx.TxIn[i].Witness = witness batchTx.TxIn[i].Witness = witness
} }
b.log.Debugf("attempting to publish non-coop tx with feerate=%v, "+ b.log.Infof("attempting to publish non-coop tx=%v with feerate=%v, "+
"totalfee=%v, sweeps=%v, destAddr=%s", b.rbfCache.FeeRate, fee, "totalfee=%v, sweeps=%d, destAddr=%s", batchTx.TxHash(),
len(batchTx.TxIn), address.String()) b.rbfCache.FeeRate, fee, len(batchTx.TxIn), address)
b.debugLogTx("serialized non-coop sweep", batchTx)
err = b.wallet.PublishTransaction( err = b.wallet.PublishTransaction(
ctx, batchTx, labels.LoopOutBatchSweepSuccess(b.id), ctx, batchTx, labels.LoopOutBatchSweepSuccess(b.id),
@ -795,9 +839,7 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
weightEstimate.AddP2TROutput() weightEstimate.AddP2TROutput()
totalWeight := int64(weightEstimate.Weight()) fee = b.rbfCache.FeeRate.FeeForWeight(weightEstimate.Weight())
fee = b.rbfCache.FeeRate.FeeForWeight(totalWeight)
// Clamp the calculated fee to the max allowed fee amount for the batch. // Clamp the calculated fee to the max allowed fee amount for the batch.
fee = clampBatchFee(fee, batchAmt) fee = clampBatchFee(fee, batchAmt)
@ -846,9 +888,11 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
return fee, err, false return fee, err, false
} }
b.log.Debugf("attempting to publish coop tx with feerate=%v, "+ b.log.Infof("attempting to publish coop tx=%v with feerate=%v, "+
"totalfee=%v, sweeps=%v, destAddr=%s", b.rbfCache.FeeRate, fee, "totalfee=%v, sweeps=%d, destAddr=%s", batchTx.TxHash(),
len(batchTx.TxIn), address.String()) b.rbfCache.FeeRate, fee, len(batchTx.TxIn), address)
b.debugLogTx("serialized coop sweep", batchTx)
err = b.wallet.PublishTransaction( err = b.wallet.PublishTransaction(
ctx, batchTx, labels.LoopOutBatchSweepSuccess(b.id), ctx, batchTx, labels.LoopOutBatchSweepSuccess(b.id),
@ -866,6 +910,17 @@ func (b *batch) publishBatchCoop(ctx context.Context) (btcutil.Amount,
return fee, nil, true return fee, nil, true
} }
func (b *batch) debugLogTx(msg string, tx *wire.MsgTx) {
// Serialize the transaction and convert to hex string.
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
b.log.Errorf("failed to serialize tx for debug log: %v", err)
return
}
b.log.Debugf("%s: %s", msg, hex.EncodeToString(buf.Bytes()))
}
// coopSignBatchTx collects the necessary signatures from the server in order // coopSignBatchTx collects the necessary signatures from the server in order
// to cooperatively sweep the funds. // to cooperatively sweep the funds.
func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet, func (b *batch) coopSignBatchTx(ctx context.Context, packet *psbt.Packet,
@ -1009,6 +1064,10 @@ func (b *batch) updateRbfRate(ctx context.Context) error {
// If the feeRate is unset then we never published before, so we // If the feeRate is unset then we never published before, so we
// retrieve the fee estimate from our wallet. // retrieve the fee estimate from our wallet.
if b.rbfCache.FeeRate == 0 { if b.rbfCache.FeeRate == 0 {
if b.cfg.batchConfTarget == 0 {
b.log.Warnf("updateRbfRate called with zero " +
"batchConfTarget")
}
b.log.Infof("initializing rbf fee rate for conf target=%v", b.log.Infof("initializing rbf fee rate for conf target=%v",
b.cfg.batchConfTarget) b.cfg.batchConfTarget)
rate, err := b.wallet.EstimateFeeRate( rate, err := b.wallet.EstimateFeeRate(
@ -1136,6 +1195,32 @@ func (b *batch) monitorConfirmations(ctx context.Context) error {
return nil return nil
} }
// getFeePortionForSweep calculates the fee portion that each sweep should pay
// for the batch transaction. The fee is split evenly among the sweeps, If the
// fee cannot be split evenly, the remainder is paid by the first sweep.
func getFeePortionForSweep(spendTx *wire.MsgTx, numSweeps int,
totalSweptAmt btcutil.Amount) (btcutil.Amount, btcutil.Amount) {
totalFee := int64(totalSweptAmt) - spendTx.TxOut[0].Value
feePortionPerSweep := totalFee / int64(numSweeps)
roundingDiff := totalFee - (int64(numSweeps) * feePortionPerSweep)
return btcutil.Amount(feePortionPerSweep), btcutil.Amount(roundingDiff)
}
// getFeePortionPaidBySweep returns the fee portion that the sweep should pay
// for the batch transaction. If the sweep is the first sweep in the batch, it
// pays the rounding difference.
func getFeePortionPaidBySweep(spendTx *wire.MsgTx, feePortionPerSweep,
roundingDiff btcutil.Amount, sweep *sweep) btcutil.Amount {
if bytes.Equal(spendTx.TxIn[0].SignatureScript, sweep.htlc.SigScript) {
return feePortionPerSweep + roundingDiff
}
return feePortionPerSweep
}
// handleSpend handles a spend notification. // handleSpend handles a spend notification.
func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error { func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
var ( var (
@ -1151,12 +1236,14 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
// sweeps that did not make it to the confirmed transaction and feed // sweeps that did not make it to the confirmed transaction and feed
// them back to the batcher. This will ensure that the sweeps will enter // them back to the batcher. This will ensure that the sweeps will enter
// a new batch instead of remaining dangling. // a new batch instead of remaining dangling.
var totalSweptAmt btcutil.Amount
for _, sweep := range b.sweeps { for _, sweep := range b.sweeps {
found := false found := false
for _, txIn := range spendTx.TxIn { for _, txIn := range spendTx.TxIn {
if txIn.PreviousOutPoint == sweep.outpoint { if txIn.PreviousOutPoint == sweep.outpoint {
found = true found = true
totalSweptAmt += sweep.value
notifyList = append(notifyList, sweep) notifyList = append(notifyList, sweep)
} }
} }
@ -1176,7 +1263,13 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
} }
} }
// Calculate the fee portion that each sweep should pay for the batch.
feePortionPaidPerSweep, roundingDifference := getFeePortionForSweep(
spendTx, len(notifyList), totalSweptAmt,
)
for _, sweep := range notifyList { for _, sweep := range notifyList {
sweep := sweep
// Save the sweep as completed. // Save the sweep as completed.
err := b.persistSweep(ctx, sweep, true) err := b.persistSweep(ctx, sweep, true)
if err != nil { if err != nil {
@ -1192,9 +1285,17 @@ func (b *batch) handleSpend(ctx context.Context, spendTx *wire.MsgTx) error {
continue continue
} }
spendDetail := SpendDetail{
Tx: spendTx,
OnChainFeePortion: getFeePortionPaidBySweep(
spendTx, feePortionPaidPerSweep,
roundingDifference, &sweep,
),
}
// Dispatch the sweep notifier, we don't care about the outcome // Dispatch the sweep notifier, we don't care about the outcome
// of this action so we don't wait for it. // of this action so we don't wait for it.
go notifySweepSpend(ctx, sweep, spendTx) go sweep.notifySweepSpend(ctx, &spendDetail)
} }
// Proceed with purging the sweeps. This will feed the sweeps that // Proceed with purging the sweeps. This will feed the sweeps that
@ -1318,10 +1419,12 @@ func (b *batch) insertAndAcquireID(ctx context.Context) (int32, error) {
} }
// notifySweepSpend writes the spendTx to the sweep's notifier channel. // notifySweepSpend writes the spendTx to the sweep's notifier channel.
func notifySweepSpend(ctx context.Context, s sweep, spendTx *wire.MsgTx) { func (s *sweep) notifySweepSpend(ctx context.Context,
spendDetail *SpendDetail) {
select { select {
// Try to write the update to the notification channel. // Try to write the update to the notification channel.
case s.notifier.SpendChan <- spendTx: case s.notifier.SpendChan <- spendDetail:
// If a quit signal was provided by the swap, continue. // If a quit signal was provided by the swap, continue.
case <-s.notifier.QuitChan: case <-s.notifier.QuitChan:

@ -2,6 +2,7 @@ package sweepbatcher
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sync" "sync"
"time" "time"
@ -12,7 +13,9 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient" "github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/utils" "github.com/lightninglabs/loop/utils"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@ -46,32 +49,83 @@ const (
type BatcherStore interface { type BatcherStore interface {
// FetchUnconfirmedSweepBatches fetches all the batches from the // FetchUnconfirmedSweepBatches fetches all the batches from the
// database that are not in a confirmed state. // database that are not in a confirmed state.
FetchUnconfirmedSweepBatches(ctx context.Context) ([]*dbBatch, FetchUnconfirmedSweepBatches(ctx context.Context) ([]*dbBatch, error)
error)
// InsertSweepBatch inserts a batch into the database, returning the id // InsertSweepBatch inserts a batch into the database, returning the id
// of the inserted batch. // of the inserted batch.
InsertSweepBatch(ctx context.Context, InsertSweepBatch(ctx context.Context, batch *dbBatch) (int32, error)
batch *dbBatch) (int32, error)
// DropBatch drops a batch from the database. This should only be used
// when a batch is empty.
DropBatch(ctx context.Context, id int32) error
// UpdateSweepBatch updates a batch in the database. // UpdateSweepBatch updates a batch in the database.
UpdateSweepBatch(ctx context.Context, UpdateSweepBatch(ctx context.Context, batch *dbBatch) error
batch *dbBatch) error
// ConfirmBatch confirms a batch by setting its state to confirmed. // ConfirmBatch confirms a batch by setting its state to confirmed.
ConfirmBatch(ctx context.Context, id int32) error ConfirmBatch(ctx context.Context, id int32) error
// FetchBatchSweeps fetches all the sweeps that belong to a batch. // FetchBatchSweeps fetches all the sweeps that belong to a batch.
FetchBatchSweeps(ctx context.Context, FetchBatchSweeps(ctx context.Context, id int32) ([]*dbSweep, error)
id int32) ([]*dbSweep, error)
// UpsertSweep inserts a sweep into the database, or updates an existing // UpsertSweep inserts a sweep into the database, or updates an existing
// sweep if it already exists. // sweep if it already exists.
UpsertSweep(ctx context.Context, sweep *dbSweep) error UpsertSweep(ctx context.Context, sweep *dbSweep) error
// GetSweepStatus returns the completed status of the sweep. // GetSweepStatus returns the completed status of the sweep.
GetSweepStatus(ctx context.Context, swapHash lntypes.Hash) ( GetSweepStatus(ctx context.Context, swapHash lntypes.Hash) (bool, error)
bool, error)
// GetParentBatch returns the parent batch of a (completed) sweep.
GetParentBatch(ctx context.Context, swapHash lntypes.Hash) (*dbBatch,
error)
// TotalSweptAmount returns the total amount swept by a (confirmed)
// batch.
TotalSweptAmount(ctx context.Context, id int32) (btcutil.Amount, error)
}
// SweepInfo stores any data related to sweeping a specific outpoint.
type SweepInfo struct {
// ConfTarget is the confirmation target of the sweep.
ConfTarget int32
// Timeout is the timeout of the swap that the sweep belongs to.
Timeout int32
// InitiationHeight is the height at which the swap was initiated.
InitiationHeight int32
// HTLC is the HTLC that is being swept.
HTLC swap.Htlc
// Preimage is the preimage of the HTLC that is being swept.
Preimage lntypes.Preimage
// SwapInvoicePaymentAddr is the payment address of the swap invoice.
SwapInvoicePaymentAddr [32]byte
// HTLCKeys is the set of keys used to sign the HTLC.
HTLCKeys loopdb.HtlcKeys
// HTLCSuccessEstimator is a function that estimates the weight of the
// HTLC success script.
HTLCSuccessEstimator func(*input.TxWeightEstimator) error
// ProtocolVersion is the protocol version of the swap that the sweep
// belongs to.
ProtocolVersion loopdb.ProtocolVersion
// IsExternalAddr is true if the sweep spends to a non-wallet address.
IsExternalAddr bool
// DestAddr is the destination address of the sweep.
DestAddr btcutil.Address
}
// SweepFetcher is used to get details of a sweep.
type SweepFetcher interface {
// FetchSweep returns details of the sweep with the given hash.
FetchSweep(ctx context.Context, hash lntypes.Hash) (*SweepInfo, error)
} }
// MuSig2SignSweep is a function that can be used to sign a sweep transaction // MuSig2SignSweep is a function that can be used to sign a sweep transaction
@ -102,11 +156,22 @@ type SweepRequest struct {
Notifier *SpendNotifier Notifier *SpendNotifier
} }
type SpendDetail struct {
// Tx is the transaction that spent the outpoint.
Tx *wire.MsgTx
// OnChainFeePortion is the fee portion that was paid to get this sweep
// confirmed on chain. This is the difference between the value of the
// outpoint and the value of all sweeps that were included in the batch
// divided by the number of sweeps.
OnChainFeePortion btcutil.Amount
}
// SpendNotifier is a notifier that is used to notify the requester of a sweep // SpendNotifier is a notifier that is used to notify the requester of a sweep
// that the sweep was successful. // that the sweep was successful.
type SpendNotifier struct { type SpendNotifier struct {
// SpendChan is a channel where the spend details are received. // SpendChan is a channel where the spend details are received.
SpendChan chan *wire.MsgTx SpendChan chan *SpendDetail
// SpendErrChan is a channel where spend errors are received. // SpendErrChan is a channel where spend errors are received.
SpendErrChan chan error SpendErrChan chan error
@ -116,7 +181,7 @@ type SpendNotifier struct {
} }
var ( var (
ErrBatcherShuttingDown = fmt.Errorf("batcher shutting down") ErrBatcherShuttingDown = errors.New("batcher shutting down")
) )
// Batcher is a system that is responsible for accepting sweep requests and // Batcher is a system that is responsible for accepting sweep requests and
@ -134,6 +199,10 @@ type Batcher struct {
// quit signals that the batch must stop. // quit signals that the batch must stop.
quit chan struct{} quit chan struct{}
// initDone is a channel that is closed when the batcher has been
// initialized.
initDone chan struct{}
// wallet is the wallet kit client that is used by batches. // wallet is the wallet kit client that is used by batches.
wallet lndclient.WalletKitClient wallet lndclient.WalletKitClient
@ -160,9 +229,8 @@ type Batcher struct {
// batcher and the batches. // batcher and the batches.
store BatcherStore store BatcherStore
// swapStore includes all the database interactions that are needed for // sweepStore is used to load sweeps from the database.
// interacting with swaps. sweepStore SweepFetcher
swapStore loopdb.SwapStore
// wg is a waitgroup that is used to wait for all the goroutines to // wg is a waitgroup that is used to wait for all the goroutines to
// exit. // exit.
@ -174,13 +242,14 @@ func NewBatcher(wallet lndclient.WalletKitClient,
chainNotifier lndclient.ChainNotifierClient, chainNotifier lndclient.ChainNotifierClient,
signerClient lndclient.SignerClient, musig2ServerSigner MuSig2SignSweep, signerClient lndclient.SignerClient, musig2ServerSigner MuSig2SignSweep,
verifySchnorrSig VerifySchnorrSig, chainparams *chaincfg.Params, verifySchnorrSig VerifySchnorrSig, chainparams *chaincfg.Params,
store BatcherStore, swapStore loopdb.SwapStore) *Batcher { store BatcherStore, sweepStore SweepFetcher) *Batcher {
return &Batcher{ return &Batcher{
batches: make(map[int32]*batch), batches: make(map[int32]*batch),
sweepReqs: make(chan SweepRequest), sweepReqs: make(chan SweepRequest),
errChan: make(chan error, 1), errChan: make(chan error, 1),
quit: make(chan struct{}), quit: make(chan struct{}),
initDone: make(chan struct{}),
wallet: wallet, wallet: wallet,
chainNotifier: chainNotifier, chainNotifier: chainNotifier,
signerClient: signerClient, signerClient: signerClient,
@ -188,7 +257,7 @@ func NewBatcher(wallet lndclient.WalletKitClient,
VerifySchnorrSig: verifySchnorrSig, VerifySchnorrSig: verifySchnorrSig,
chainParams: chainparams, chainParams: chainparams,
store: store, store: store,
swapStore: swapStore, sweepStore: sweepStore,
} }
} }
@ -197,6 +266,7 @@ func (b *Batcher) Run(ctx context.Context) error {
runCtx, cancel := context.WithCancel(ctx) runCtx, cancel := context.WithCancel(ctx)
defer func() { defer func() {
cancel() cancel()
close(b.quit)
for _, batch := range b.batches { for _, batch := range b.batches {
batch.Wait() batch.Wait()
@ -219,6 +289,9 @@ func (b *Batcher) Run(ctx context.Context) error {
} }
} }
// Signal that the batcher has been initialized.
close(b.initDone)
for { for {
select { select {
case sweepReq := <-b.sweepReqs: case sweepReq := <-b.sweepReqs:
@ -270,8 +343,7 @@ func (b *Batcher) handleSweep(ctx context.Context, sweep *sweep,
// can't attach its notifier to the batch as that is no longer running. // can't attach its notifier to the batch as that is no longer running.
// Instead we directly detect and return the spend here. // Instead we directly detect and return the spend here.
if completed && *notifier != (SpendNotifier{}) { if completed && *notifier != (SpendNotifier{}) {
go b.monitorSpendAndNotify(ctx, sweep, notifier) return b.monitorSpendAndNotify(ctx, sweep, notifier)
return nil
} }
sweep.notifier = notifier sweep.notifier = notifier
@ -288,22 +360,25 @@ func (b *Batcher) handleSweep(ctx context.Context, sweep *sweep,
if batch.sweepExists(sweep.swapHash) { if batch.sweepExists(sweep.swapHash) {
accepted, err := batch.addSweep(ctx, sweep) accepted, err := batch.addSweep(ctx, sweep)
if err != nil { if err != nil && !errors.Is(err, ErrBatchShuttingDown) {
return err return err
} }
if !accepted { if !accepted {
return fmt.Errorf("existing sweep %x was not "+ return fmt.Errorf("existing sweep %x was not "+
"accepted by batch %d", sweep.swapHash[:6], "accepted by batch %d",
batch.id) sweep.swapHash[:6], batch.id)
} }
// The sweep was updated in the batch, our job is done.
return nil
} }
} }
// If one of the batches accepts the sweep, we provide it to that batch. // If one of the batches accepts the sweep, we provide it to that batch.
for _, batch := range b.batches { for _, batch := range b.batches {
accepted, err := batch.addSweep(ctx, sweep) accepted, err := batch.addSweep(ctx, sweep)
if err != nil && err != ErrBatchShuttingDown { if err != nil && !errors.Is(err, ErrBatchShuttingDown) {
return err return err
} }
@ -341,7 +416,6 @@ func (b *Batcher) handleSweep(ctx context.Context, sweep *sweep,
func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) { func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
cfg := batchConfig{ cfg := batchConfig{
maxTimeoutDistance: defaultMaxTimeoutDistance, maxTimeoutDistance: defaultMaxTimeoutDistance,
batchConfTarget: defaultBatchConfTarget,
} }
switch b.chainParams { switch b.chainParams {
@ -361,6 +435,7 @@ func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
verifySchnorrSig: b.VerifySchnorrSig, verifySchnorrSig: b.VerifySchnorrSig,
purger: b.AddSweep, purger: b.AddSweep,
store: b.store, store: b.store,
quit: b.quit,
} }
batch := NewBatch(cfg, batchKit) batch := NewBatch(cfg, batchKit)
@ -389,23 +464,23 @@ func (b *Batcher) spinUpBatch(ctx context.Context) (*batch, error) {
// spinUpBatchDB spins up a batch that already existed in storage, then // spinUpBatchDB spins up a batch that already existed in storage, then
// returns it. // returns it.
func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error { func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
cfg := batchConfig{
maxTimeoutDistance: batch.cfg.maxTimeoutDistance,
batchConfTarget: defaultBatchConfTarget,
}
rbfCache := rbfCache{
LastHeight: batch.rbfCache.LastHeight,
FeeRate: batch.rbfCache.FeeRate,
}
dbSweeps, err := b.store.FetchBatchSweeps(ctx, batch.id) dbSweeps, err := b.store.FetchBatchSweeps(ctx, batch.id)
if err != nil { if err != nil {
return err return err
} }
if len(dbSweeps) == 0 { if len(dbSweeps) == 0 {
return fmt.Errorf("batch %d has no sweeps", batch.id) log.Infof("skipping restored batch %d as it has no sweeps",
batch.id)
// It is safe to drop this empty batch as it has no sweeps.
err := b.store.DropBatch(ctx, batch.id)
if err != nil {
log.Warnf("unable to drop empty batch %d: %v",
batch.id, err)
}
return nil
} }
primarySweep := dbSweeps[0] primarySweep := dbSweeps[0]
@ -413,7 +488,7 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
sweeps := make(map[lntypes.Hash]sweep) sweeps := make(map[lntypes.Hash]sweep)
for _, dbSweep := range dbSweeps { for _, dbSweep := range dbSweeps {
sweep, err := b.convertSweep(dbSweep) sweep, err := b.convertSweep(ctx, dbSweep)
if err != nil { if err != nil {
return err return err
} }
@ -421,6 +496,13 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
sweeps[sweep.swapHash] = *sweep sweeps[sweep.swapHash] = *sweep
} }
rbfCache := rbfCache{
LastHeight: batch.rbfCache.LastHeight,
FeeRate: batch.rbfCache.FeeRate,
}
logger := batchPrefixLogger(fmt.Sprintf("%d", batch.id))
batchKit := batchKit{ batchKit := batchKit{
id: batch.id, id: batch.id,
batchTxid: batch.batchTxid, batchTxid: batch.batchTxid,
@ -437,10 +519,18 @@ func (b *Batcher) spinUpBatchFromDB(ctx context.Context, batch *batch) error {
verifySchnorrSig: b.VerifySchnorrSig, verifySchnorrSig: b.VerifySchnorrSig,
purger: b.AddSweep, purger: b.AddSweep,
store: b.store, store: b.store,
log: batchPrefixLogger(fmt.Sprintf("%d", batch.id)), log: logger,
quit: b.quit,
} }
newBatch := NewBatchFromDB(cfg, batchKit) cfg := batchConfig{
maxTimeoutDistance: batch.cfg.maxTimeoutDistance,
}
newBatch, err := NewBatchFromDB(cfg, batchKit)
if err != nil {
return fmt.Errorf("failed in NewBatchFromDB: %w", err)
}
// We add the batch to our map of batches and start it. // We add the batch to our map of batches and start it.
b.batches[batch.id] = newBatch b.batches[batch.id] = newBatch
@ -509,57 +599,88 @@ func (b *Batcher) FetchUnconfirmedBatches(ctx context.Context) ([]*batch,
// monitorSpendAndNotify monitors the spend of a specific outpoint and writes // monitorSpendAndNotify monitors the spend of a specific outpoint and writes
// the response back to the response channel. // the response back to the response channel.
func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep, func (b *Batcher) monitorSpendAndNotify(ctx context.Context, sweep *sweep,
notifier *SpendNotifier) { notifier *SpendNotifier) error {
b.wg.Add(1)
defer b.wg.Done()
spendCtx, cancel := context.WithCancel(ctx) spendCtx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
// First get the batch that completed the sweep.
parentBatch, err := b.store.GetParentBatch(ctx, sweep.swapHash)
if err != nil {
return err
}
// Then we get the total amount that was swept by the batch.
totalSwept, err := b.store.TotalSweptAmount(ctx, parentBatch.ID)
if err != nil {
return err
}
spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn( spendChan, spendErr, err := b.chainNotifier.RegisterSpendNtfn(
spendCtx, &sweep.outpoint, sweep.htlc.PkScript, spendCtx, &sweep.outpoint, sweep.htlc.PkScript,
sweep.initiationHeight, sweep.initiationHeight,
) )
if err != nil { if err != nil {
select { return err
case notifier.SpendErrChan <- err:
case <-ctx.Done():
}
_ = b.writeToErrChan(ctx, err)
return
} }
log.Infof("Batcher monitoring spend for swap %x", sweep.swapHash[:6]) b.wg.Add(1)
go func() {
defer b.wg.Done()
log.Infof("Batcher monitoring spend for swap %x",
sweep.swapHash[:6])
for { for {
select {
case spend := <-spendChan:
select { select {
case notifier.SpendChan <- spend.SpendingTx: case spend := <-spendChan:
case <-ctx.Done(): spendTx := spend.SpendingTx
} // Calculate the fee portion that each sweep
// should pay for the batch.
feePortionPerSweep, roundingDifference :=
getFeePortionForSweep(
spendTx, len(spendTx.TxIn),
totalSwept,
)
onChainFeePortion := getFeePortionPaidBySweep(
spendTx, feePortionPerSweep,
roundingDifference, sweep,
)
// Notify the requester of the spend
// with the spend details, including the fee
// portion for this particular sweep.
spendDetail := &SpendDetail{
Tx: spendTx,
OnChainFeePortion: onChainFeePortion,
}
select {
case notifier.SpendChan <- spendDetail:
case <-ctx.Done():
}
return
case err := <-spendErr:
select {
case notifier.SpendErrChan <- err:
case <-ctx.Done():
}
_ = b.writeToErrChan(ctx, err)
return
case <-notifier.QuitChan:
return
return
case err := <-spendErr:
select {
case notifier.SpendErrChan <- err:
case <-ctx.Done(): case <-ctx.Done():
return
} }
_ = b.writeToErrChan(ctx, err)
return
case <-notifier.QuitChan:
return
case <-ctx.Done():
return
} }
} }()
return nil
} }
func (b *Batcher) writeToErrChan(ctx context.Context, err error) error { func (b *Batcher) writeToErrChan(ctx context.Context, err error) error {
@ -573,85 +694,128 @@ func (b *Batcher) writeToErrChan(ctx context.Context, err error) error {
} }
// convertSweep converts a fetched sweep from the database to a sweep that is // convertSweep converts a fetched sweep from the database to a sweep that is
// ready to be processed by the batcher. // ready to be processed by the batcher. It loads swap from loopdb by calling
func (b *Batcher) convertSweep(dbSweep *dbSweep) (*sweep, error) { // method FetchLoopOutSwap.
swap := dbSweep.LoopOut func (b *Batcher) convertSweep(ctx context.Context, dbSweep *dbSweep) (
*sweep, error) {
htlc, err := utils.GetHtlc(
dbSweep.SwapHash, &swap.Contract.SwapContract, b.chainParams,
)
if err != nil {
return nil, err
}
swapPaymentAddr, err := utils.ObtainSwapPaymentAddr( s, err := b.sweepStore.FetchSweep(ctx, dbSweep.SwapHash)
swap.Contract.SwapInvoice, b.chainParams,
)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to fetch sweep data for %x: %w",
dbSweep.SwapHash[:6], err)
} }
return &sweep{ return &sweep{
swapHash: swap.Hash, swapHash: dbSweep.SwapHash,
outpoint: dbSweep.Outpoint, outpoint: dbSweep.Outpoint,
value: dbSweep.Amount, value: dbSweep.Amount,
confTarget: swap.Contract.SweepConfTarget, confTarget: s.ConfTarget,
timeout: swap.Contract.CltvExpiry, timeout: s.Timeout,
initiationHeight: swap.Contract.InitiationHeight, initiationHeight: s.InitiationHeight,
htlc: *htlc, htlc: s.HTLC,
preimage: swap.Contract.Preimage, preimage: s.Preimage,
swapInvoicePaymentAddr: *swapPaymentAddr, swapInvoicePaymentAddr: s.SwapInvoicePaymentAddr,
htlcKeys: swap.Contract.HtlcKeys, htlcKeys: s.HTLCKeys,
htlcSuccessEstimator: htlc.AddSuccessToEstimator, htlcSuccessEstimator: s.HTLCSuccessEstimator,
protocolVersion: swap.Contract.ProtocolVersion, protocolVersion: s.ProtocolVersion,
isExternalAddr: swap.Contract.IsExternalAddr, isExternalAddr: s.IsExternalAddr,
destAddr: swap.Contract.DestAddr, destAddr: s.DestAddr,
}, nil }, nil
} }
// fetchSweep fetches the sweep related information from the database. // LoopOutFetcher is used to load LoopOut swaps from the database.
func (b *Batcher) fetchSweep(ctx context.Context, // It is implemented by loopdb.SwapStore.
sweepReq SweepRequest) (*sweep, error) { type LoopOutFetcher interface {
// FetchLoopOutSwap returns the loop out swap with the given hash.
FetchLoopOutSwap(ctx context.Context,
hash lntypes.Hash) (*loopdb.LoopOut, error)
}
swapHash, err := lntypes.MakeHash(sweepReq.SwapHash[:]) // SwapStoreWrapper is LoopOutFetcher wrapper providing SweepFetcher interface.
if err != nil { type SwapStoreWrapper struct {
return nil, fmt.Errorf("failed to parse swapHash: %v", err) // swapStore is used to load LoopOut swaps from the database.
} swapStore LoopOutFetcher
// chainParams are the chain parameters of the chain that is used by
// batches.
chainParams *chaincfg.Params
}
swap, err := b.swapStore.FetchLoopOutSwap(ctx, swapHash) // FetchSweep returns details of the sweep with the given hash.
// Implements SweepFetcher interface.
func (f *SwapStoreWrapper) FetchSweep(ctx context.Context,
swapHash lntypes.Hash) (*SweepInfo, error) {
swap, err := f.swapStore.FetchLoopOutSwap(ctx, swapHash)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to fetch loop out for %x: %v", return nil, fmt.Errorf("failed to fetch loop out for %x: %w",
swapHash[:6], err) swapHash[:6], err)
} }
htlc, err := utils.GetHtlc( htlc, err := utils.GetHtlc(
swapHash, &swap.Contract.SwapContract, b.chainParams, swapHash, &swap.Contract.SwapContract, f.chainParams,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get htlc: %v", err) return nil, fmt.Errorf("failed to get htlc: %w", err)
} }
swapPaymentAddr, err := utils.ObtainSwapPaymentAddr( swapPaymentAddr, err := utils.ObtainSwapPaymentAddr(
swap.Contract.SwapInvoice, b.chainParams, swap.Contract.SwapInvoice, f.chainParams,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get payment addr: %v", err) return nil, fmt.Errorf("failed to get payment addr: %w", err)
}
return &SweepInfo{
ConfTarget: swap.Contract.SweepConfTarget,
Timeout: swap.Contract.CltvExpiry,
InitiationHeight: swap.Contract.InitiationHeight,
HTLC: *htlc,
Preimage: swap.Contract.Preimage,
SwapInvoicePaymentAddr: *swapPaymentAddr,
HTLCKeys: swap.Contract.HtlcKeys,
HTLCSuccessEstimator: htlc.AddSuccessToEstimator,
ProtocolVersion: swap.Contract.ProtocolVersion,
IsExternalAddr: swap.Contract.IsExternalAddr,
DestAddr: swap.Contract.DestAddr,
}, nil
}
// NewSweepFetcherFromSwapStore accepts swapStore (e.g. loopdb) and returns
// a wrapper implementing SweepFetcher interface (suitable for NewBatcher).
func NewSweepFetcherFromSwapStore(swapStore LoopOutFetcher,
chainParams *chaincfg.Params) (*SwapStoreWrapper, error) {
return &SwapStoreWrapper{
swapStore: swapStore,
chainParams: chainParams,
}, nil
}
// fetchSweep fetches the sweep related information from the database.
func (b *Batcher) fetchSweep(ctx context.Context,
sweepReq SweepRequest) (*sweep, error) {
s, err := b.sweepStore.FetchSweep(ctx, sweepReq.SwapHash)
if err != nil {
return nil, fmt.Errorf("failed to fetch sweep data for %x: %w",
sweepReq.SwapHash[:6], err)
} }
return &sweep{ return &sweep{
swapHash: swap.Hash, swapHash: sweepReq.SwapHash,
outpoint: sweepReq.Outpoint, outpoint: sweepReq.Outpoint,
value: sweepReq.Value, value: sweepReq.Value,
confTarget: swap.Contract.SweepConfTarget, confTarget: s.ConfTarget,
timeout: swap.Contract.CltvExpiry, timeout: s.Timeout,
initiationHeight: swap.Contract.InitiationHeight, initiationHeight: s.InitiationHeight,
htlc: *htlc, htlc: s.HTLC,
preimage: swap.Contract.Preimage, preimage: s.Preimage,
swapInvoicePaymentAddr: *swapPaymentAddr, swapInvoicePaymentAddr: s.SwapInvoicePaymentAddr,
htlcKeys: swap.Contract.HtlcKeys, htlcKeys: s.HTLCKeys,
htlcSuccessEstimator: htlc.AddSuccessToEstimator, htlcSuccessEstimator: s.HTLCSuccessEstimator,
protocolVersion: swap.Contract.ProtocolVersion, protocolVersion: s.ProtocolVersion,
isExternalAddr: swap.Contract.IsExternalAddr, isExternalAddr: s.IsExternalAddr,
destAddr: swap.Contract.DestAddr, destAddr: s.DestAddr,
}, nil }, nil
} }

File diff suppressed because it is too large Load Diff

@ -278,11 +278,24 @@ func (h *mockLightningClient) ListInvoices(_ context.Context,
// ListPayments makes a paginated call to our list payments endpoint. // ListPayments makes a paginated call to our list payments endpoint.
func (h *mockLightningClient) ListPayments(_ context.Context, func (h *mockLightningClient) ListPayments(_ context.Context,
_ lndclient.ListPaymentsRequest) (*lndclient.ListPaymentsResponse, req lndclient.ListPaymentsRequest) (*lndclient.ListPaymentsResponse,
error) { error) {
if req.Offset >= uint64(len(h.lnd.Payments)) {
return &lndclient.ListPaymentsResponse{}, nil
}
lastIndexOffset := req.Offset + req.MaxPayments
if lastIndexOffset > uint64(len(h.lnd.Payments)) {
lastIndexOffset = uint64(len(h.lnd.Payments))
}
result := h.lnd.Payments[req.Offset:lastIndexOffset]
return &lndclient.ListPaymentsResponse{ return &lndclient.ListPaymentsResponse{
Payments: h.lnd.Payments, Payments: result,
FirstIndexOffset: req.Offset,
LastIndexOffset: lastIndexOffset - 1,
}, nil }, nil
} }

@ -70,7 +70,7 @@ func mockMuSig2SignSweep(ctx context.Context,
return nil, nil, nil return nil, nil, nil
} }
func newSwapClient(config *clientConfig) *Client { func newSwapClient(t *testing.T, config *clientConfig) *Client {
sweeper := &sweep.Sweeper{ sweeper := &sweep.Sweeper{
Lnd: config.LndServices, Lnd: config.LndServices,
} }
@ -79,11 +79,16 @@ func newSwapClient(config *clientConfig) *Client {
batcherStore := sweepbatcher.NewStoreMock() batcherStore := sweepbatcher.NewStoreMock()
sweepStore, err := sweepbatcher.NewSweepFetcherFromSwapStore(
config.Store, config.LndServices.ChainParams,
)
require.NoError(t, err)
batcher := sweepbatcher.NewBatcher( batcher := sweepbatcher.NewBatcher(
config.LndServices.WalletKit, config.LndServices.ChainNotifier, config.LndServices.WalletKit, config.LndServices.ChainNotifier,
config.LndServices.Signer, mockMuSig2SignSweep, config.LndServices.Signer, mockMuSig2SignSweep,
mockVerifySchnorrSigSuccess, config.LndServices.ChainParams, mockVerifySchnorrSigSuccess, config.LndServices.ChainParams,
batcherStore, config.Store, batcherStore, sweepStore,
) )
executor := newExecutor(&executorConfig{ executor := newExecutor(&executorConfig{
@ -128,7 +133,7 @@ func createClientTestContext(t *testing.T,
return expiryChan return expiryChan
} }
swapClient := newSwapClient(&clientConfig{ swapClient := newSwapClient(t, &clientConfig{
LndServices: &clientLnd.LndServices, LndServices: &clientLnd.LndServices,
Server: serverMock, Server: serverMock,
Store: store, Store: store,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save