Merge pull request #1156 from starius/lnd-21-improvements
Some checks are pending
CI / RPC compilation check (push) Waiting to run
CI / SQL compilation check (push) Waiting to run
CI / go mod check (push) Waiting to run
CI / build and lint code (push) Waiting to run
CI / verify that auto-generated documentation is up-to-date (push) Waiting to run
CI / run unit-test sqlite3 race (push) Waiting to run
CI / run unit-test postgres race (push) Waiting to run
CI / run LiT itests (push) Waiting to run
CI / run LiT unit tests (push) Waiting to run

Use new features of LND 0.21
This commit is contained in:
Boris Nagaev 2026-06-23 09:56:53 -05:00 committed by GitHub
commit 375c02e0f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 400 additions and 97 deletions

View file

@ -55,14 +55,16 @@ linters:
- wsl_v5
- noinlineerr
settings:
staticcheck:
checks:
- all
- -QF*
- -ST*
gosec:
excludes:
- G402
- G306
- G115
staticcheck:
checks:
- -SA1019
tagliatelle:
case:
rules:

View file

@ -207,7 +207,7 @@ func loopIn(ctx context.Context, cmd *cli.Command) error {
}
fmt.Printf("Swap initiated\n")
fmt.Printf("ID: %v\n", resp.Id)
fmt.Printf("ID: %x\n", resp.IdBytes)
if resp.HtlcAddressP2Tr != "" {
fmt.Printf("HTLC address (P2TR): %v\n", resp.HtlcAddressP2Tr)

View file

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
@ -646,7 +645,7 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (daemonConn,
// gRPC dial options from it.
func readMacaroon(macPath string) (grpc.DialOption, error) {
// Load the specified macaroon file.
macBytes, err := ioutil.ReadFile(macPath)
macBytes, err := os.ReadFile(macPath)
if err != nil {
return nil, fmt.Errorf("unable to read macaroon path : %v", err)
}

View file

@ -13,13 +13,11 @@ import (
)
const (
defaultUtxoMinConf = 1
)
var (
defaultUtxoMinConf = 1
channelTypeTweakless = "tweakless"
channelTypeAnchors = "anchors"
channelTypeSimpleTaproot = "taproot"
channelTypeSimpleTaproot = "simple-taproot"
channelTypeTaproot = "taproot"
)
var openChannelCommand = &cli.Command{
@ -137,9 +135,9 @@ var openChannelCommand = &cli.Command{
&cli.StringFlag{
Name: "channel_type",
Usage: fmt.Sprintf("(optional) the type of channel to "+
"propose to the remote peer (%q, %q, %q)",
"propose to the remote peer (%q, %q, %q, %q)",
channelTypeTweakless, channelTypeAnchors,
channelTypeSimpleTaproot),
channelTypeSimpleTaproot, channelTypeTaproot),
},
&cli.BoolFlag{
Name: "zero_conf",
@ -322,6 +320,7 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
switch channelType {
case "":
break
case channelTypeTweakless:
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
@ -330,6 +329,10 @@ func openChannel(ctx context.Context, cmd *cli.Command) error {
case channelTypeSimpleTaproot:
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
case channelTypeTaproot:
req.CommitmentType = lnrpc.CommitmentType_TAPROOT
default:
return fmt.Errorf("unsupported channel type %v", channelType)
}

View file

@ -67,7 +67,7 @@
" --max_local_csv uint (optional) the maximum number of blocks that we will allow the remote peer to require we wait before accessing our funds in the case of a unilateral close. (default: 0)\n",
" --close_address string (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will *not* be able to cooperatively close to a different address.\n",
" --remote_max_value_in_flight_msat uint (optional) the maximum value in msat that can be pending within the channel at any given time (default: 0)\n",
" --channel_type string (optional) the type of channel to propose to the remote peer (\"tweakless\", \"anchors\", \"taproot\")\n",
" --channel_type string (optional) the type of channel to propose to the remote peer (\"tweakless\", \"anchors\", \"simple-taproot\", \"taproot\")\n",
" --zero_conf (optional) whether a zero-conf channel open should be attempted. (default: false)\n",
" --scid_alias (optional) whether a scid-alias channel type should be negotiated. (default: false)\n",
" --remote_reserve_sats uint (optional) the minimum number of satoshis we require the remote node to keep as a direct payment. If not specified, a default of 1% of the channel capacity will be used. (default: 0)\n",

View file

@ -0,0 +1,104 @@
{
"metadata": {
"args": [
"/home/user/bin/loop",
"--rpcserver=localhost:11010",
"--loopdir=/redacted/loop",
"--tlscertpath=/redacted/loop/regtest/tls.cert",
"--macaroonpath=/redacted/loop/regtest/loop.macaroon",
"static",
"openchannel",
"--node_key",
"03465f68fd39358667678f8353a31e0d99475e3fd2fb4e58daf7dbfabe04c011f4",
"--fundmax",
"--utxo",
"89f6fd2ee96445c6e48278e7eaaca7de8e342904f20609ce72c0d27afb443e2d:1",
"--channel_type",
"taproot",
"--private",
"--network",
"regtest"
],
"env": {},
"version": "0.33.2-beta commit=v0.33.2-beta-bump-lnd-21-a-7-g21019e684ed06e2267382f473dfeaafb81a33310 commit_hash=21019e684ed06e2267382f473dfeaafb81a33310",
"duration": 295879956,
"clock_start_unix": 1782002722
},
"events": [
{
"time_ms": 2,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "request",
"message_type": "looprpc.StaticOpenChannelRequest",
"payload": {
"open_channel_request": {
"sat_per_vbyte": "0",
"node_pubkey": "A0ZfaP05NYZnZ4+DU6MeDZlHXj/S+05Y2vfb+r4EwBH0",
"node_pubkey_string": "",
"local_funding_amount": "0",
"push_sat": "0",
"target_conf": 0,
"sat_per_byte": "0",
"private": true,
"min_htlc_msat": "0",
"remote_csv_delay": 0,
"min_confs": 1,
"spend_unconfirmed": false,
"close_address": "",
"funding_shim": null,
"remote_max_value_in_flight_msat": "0",
"remote_max_htlcs": 0,
"max_local_csv": 0,
"commitment_type": "TAPROOT",
"zero_conf": false,
"scid_alias": false,
"base_fee": "0",
"fee_rate": "0",
"use_base_fee": false,
"use_fee_rate": false,
"remote_chan_reserve_sat": "0",
"fund_max": true,
"memo": "",
"outpoints": [
{
"txid_bytes": "",
"txid_str": "89f6fd2ee96445c6e48278e7eaaca7de8e342904f20609ce72c0d27afb443e2d",
"output_index": 1
}
]
}
}
}
},
{
"time_ms": 295,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "response",
"message_type": "looprpc.StaticOpenChannelResponse",
"payload": {
"channel_open_outpoint": "302a41b53946494306e415bdf4724e88b2cf2b37ad2f36dc075db00cc6499e9f:0"
}
}
},
{
"time_ms": 295,
"kind": "stdout",
"data": {
"lines": [
"{\n",
" \"channel_open_outpoint\": \"302a41b53946494306e415bdf4724e88b2cf2b37ad2f36dc075db00cc6499e9f:0\"\n",
"}\n"
]
}
},
{
"time_ms": 295,
"kind": "exit",
"data": {}
}
]
}

View file

@ -0,0 +1,106 @@
{
"metadata": {
"args": [
"/home/user/bin/loop",
"--rpcserver=localhost:11010",
"--loopdir=/redacted/loop",
"--tlscertpath=/redacted/loop/regtest/tls.cert",
"--macaroonpath=/redacted/loop/regtest/loop.macaroon",
"static",
"openchannel",
"--node_key",
"026eca9330cc3e589505c4a240ea1f7f551e5765b1aae9040c6667141da657eae6",
"--fundmax",
"--utxo",
"dc942b3252ded50abc30ef4e8614d392ab0f091acab75fdd09e18a07f6ac36e6:0",
"--channel_type",
"taproot",
"--private",
"--network",
"regtest"
],
"env": {},
"version": "0.33.3-beta commit= commit_hash=",
"run_error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7",
"duration": 39639744,
"clock_start_unix": 1782158339
},
"events": [
{
"time_ms": 7,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "request",
"message_type": "looprpc.StaticOpenChannelRequest",
"payload": {
"open_channel_request": {
"sat_per_vbyte": "0",
"node_pubkey": "Am7KkzDMPliVBcSiQOoff1UeV2WxqukEDGZnFB2mV+rm",
"node_pubkey_string": "",
"local_funding_amount": "0",
"push_sat": "0",
"target_conf": 0,
"sat_per_byte": "0",
"private": true,
"min_htlc_msat": "0",
"remote_csv_delay": 0,
"min_confs": 1,
"spend_unconfirmed": false,
"close_address": "",
"funding_shim": null,
"remote_max_value_in_flight_msat": "0",
"remote_max_htlcs": 0,
"max_local_csv": 0,
"commitment_type": "TAPROOT",
"zero_conf": false,
"scid_alias": false,
"base_fee": "0",
"fee_rate": "0",
"use_base_fee": false,
"use_fee_rate": false,
"remote_chan_reserve_sat": "0",
"fund_max": true,
"memo": "",
"outpoints": [
{
"txid_bytes": "",
"txid_str": "dc942b3252ded50abc30ef4e8614d392ab0f091acab75fdd09e18a07f6ac36e6",
"output_index": 0
}
]
}
}
}
},
{
"time_ms": 39,
"kind": "grpc",
"data": {
"method": "/looprpc.SwapClient/StaticOpenChannel",
"event": "error",
"error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7",
"status": {
"code": 2,
"message": "channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7"
}
}
},
{
"time_ms": 39,
"kind": "stderr",
"data": {
"lines": [
"[loop] rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7\n"
]
}
},
{
"time_ms": 39,
"kind": "exit",
"data": {
"run_error": "rpc error: code = Unknown desc = channel_type=taproot is not supported by the connected lnd; update LND to v0.21.0-beta or later to use this channel type: got error from server: rpc error: code = Unknown desc = unhandled request channel type 7"
}
}
]
}

View file

@ -142,6 +142,7 @@ func MigrateLoopOutCosts(ctx context.Context, lnd lndclient.LndServices,
ctx, lndclient.ListPaymentsRequest{
Offset: offset,
MaxPayments: uint64(paymentBatchSize),
OmitHops: true,
},
)
if err != nil {

View file

@ -163,6 +163,11 @@ func TestCostMigration(t *testing.T) {
// Now we can run the migration.
err = MigrateLoopOutCosts(context.Background(), lnd.LndServices, 1, store)
require.NoError(t, err)
listPaymentsRequests := lnd.ListPaymentsRequestsSnapshot()
require.NotEmpty(t, listPaymentsRequests)
for _, req := range listPaymentsRequests {
require.True(t, req.OmitHops)
}
// Finally check that the swap cost has been updated correctly.
swap, err := store.FetchLoopOutSwap(

View file

@ -589,7 +589,7 @@ Open a channel to an existing peer.
\fB--base_fee_msat\fP="": the base fee in milli-satoshis that will be charged for each forwarded HTLC, regardless of payment size (default: 0)
.PP
\fB--channel_type\fP="": (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "taproot")
\fB--channel_type\fP="": (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "simple-taproot", "taproot")
.PP
\fB--close_address\fP="": (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will \fInot\fP be able to cooperatively close to a different address.

View file

@ -715,7 +715,7 @@ The following flags are supported:
| `--max_local_csv="…"` | (optional) the maximum number of blocks that we will allow the remote peer to require we wait before accessing our funds in the case of a unilateral close | uint | `0` |
| `--close_address="…"` | (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will *not* be able to cooperatively close to a different address | string |
| `--remote_max_value_in_flight_msat="…"` | (optional) the maximum value in msat that can be pending within the channel at any given time | uint | `0` |
| `--channel_type="…"` | (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "taproot") | string |
| `--channel_type="…"` | (optional) the type of channel to propose to the remote peer ("tweakless", "anchors", "simple-taproot", "taproot") | string |
| `--zero_conf` | (optional) whether a zero-conf channel open should be attempted | bool | `false` |
| `--scid_alias` | (optional) whether a scid-alias channel type should be negotiated | bool | `false` |
| `--remote_reserve_sats="…"` | (optional) the minimum number of satoshis we require the remote node to keep as a direct payment. If not specified, a default of 1% of the channel capacity will be used | uint | `0` |

2
go.mod
View file

@ -20,7 +20,7 @@ require (
github.com/jessevdk/go-flags v1.6.1
github.com/lib/pq v1.10.9
github.com/lightninglabs/aperture v0.4.0
github.com/lightninglabs/lndclient v0.21.0-1
github.com/lightninglabs/lndclient v0.21.0-2
github.com/lightninglabs/loop/looprpc v1.0.7
github.com/lightninglabs/loop/swapserverrpc v1.0.14
github.com/lightninglabs/taproot-assets v0.8.0

4
go.sum
View file

@ -1116,8 +1116,8 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.4-0.20250610182311-2f1d46ef18b7 h1:373o5lNr1udAdhcf5+zq/0dYpRtkvYLl8Lk6wG7I0DY=
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.4-0.20250610182311-2f1d46ef18b7/go.mod h1:bDnEKRN1u13NFBuy/C+bFLhxA5bfd3clT25y76QY0AM=
github.com/lightninglabs/lndclient v0.21.0-1 h1:NuyccCK7tbMaH7hhqtewcx+qeBel4/RLJrlnQ/lMkkY=
github.com/lightninglabs/lndclient v0.21.0-1/go.mod h1:RUIcfPr82HrvZr3pu9f8nbD5v6VFbm+KgExqNNp5bE4=
github.com/lightninglabs/lndclient v0.21.0-2 h1:gA1utFMoKV6OZfHpYAQ1TX0wVs5cAtgTORRVavHR/ls=
github.com/lightninglabs/lndclient v0.21.0-2/go.mod h1:RUIcfPr82HrvZr3pu9f8nbD5v6VFbm+KgExqNNp5bE4=
github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789 h1:7kX7vUgHUazAHcCJ6uzBDa4/2MEGEbMEfa01GtfqmTQ=
github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2.0.20251211093704-71c1eef09789/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
github.com/lightninglabs/neutrino v0.17.1 h1:lNhgq7ix/N81R6oATroP/kHMzH1qzVVF2dEGcTlN2t4=

View file

@ -559,8 +559,11 @@ func RpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
req.AutoloopBudgetSat != 0 {
params.AutoFeeRefreshPeriod = InfiniteDuration
// Keep reading the legacy start field so old stored
// liquidity parameters migrate to the refresh-period model.
budgetStartSec := req.AutoloopBudgetStartSec //nolint:staticcheck
params.AutoloopBudgetLastRefresh = time.Unix(
int64(req.AutoloopBudgetStartSec), 0)
int64(budgetStartSec), 0)
}
for _, rule := range req.Rules {

View file

@ -33,7 +33,7 @@ import (
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/macaroons"
"go.etcd.io/bbolt"
bbolterrors "go.etcd.io/bbolt/errors"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
"gopkg.in/macaroon-bakery.v2/bakery"
@ -161,7 +161,7 @@ func (d *Daemon) Start() error {
// and error handlers. If this fails, then nothing has been started yet,
// and we can just return the error.
err = d.initialize(true)
if errors.Is(err, bbolt.ErrTimeout) {
if errors.Is(err, bbolterrors.ErrTimeout) {
// We're trying to be started as a standalone Loop daemon, most
// likely LiT is already running and blocking the DB
return fmt.Errorf("%v: make sure no other loop daemon process "+
@ -211,7 +211,7 @@ func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices,
// handlers. If this fails, then nothing has been started yet, and we
// can just return the error.
err := d.initialize(withMacaroonService)
if errors.Is(err, bbolt.ErrTimeout) {
if errors.Is(err, bbolterrors.ErrTimeout) {
// We're trying to be started inside LiT so there most likely is
// another standalone Loop process blocking the DB.
return fmt.Errorf("%v: make sure no other loop daemon "+
@ -994,7 +994,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
d.wg.Go(func() {
infof("Starting static address open channel manager")
err := openChannelManager.Run(d.mainCtx)
if err != nil && !errors.Is(context.Canceled, err) {
if err != nil && !errors.Is(err, context.Canceled) {
d.internalErrChan <- err
}
infof("Static address open channel manager stopped")

View file

@ -251,6 +251,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
req.AssetSwapRfqId = in.AssetRfqInfo.SwapRfqId
}
// Keep accepting the deprecated single-channel field for older clients.
switch {
case in.LoopOutChannel != 0 && len(in.OutgoingChanSet) > 0: // nolint:staticcheck
return nil, errors.New("loop_out_channel and outgoing_" +
@ -273,7 +274,7 @@ func (s *swapClientServer) LoopOut(ctx context.Context,
resp := &looprpc.SwapResponse{
Id: info.SwapHash.String(),
IdBytes: info.SwapHash[:],
HtlcAddress: htlcAddress,
HtlcAddress: htlcAddress, //nolint:staticcheck
ServerMessage: info.ServerMessage,
}
@ -1182,11 +1183,11 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
if loopdb.CurrentProtocolVersion() < loopdb.ProtocolVersionHtlcV3 {
p2wshAddr := swapInfo.HtlcAddressP2WSH.String()
response.HtlcAddress = p2wshAddr
response.HtlcAddress = p2wshAddr //nolint:staticcheck
response.HtlcAddressP2Wsh = p2wshAddr
} else {
p2trAddr := swapInfo.HtlcAddressP2TR.String()
response.HtlcAddress = p2trAddr
response.HtlcAddress = p2trAddr //nolint:staticcheck
response.HtlcAddressP2Tr = p2trAddr
}

View file

@ -2,8 +2,6 @@ package loopdb
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -48,9 +46,7 @@ func TestMigrationUpdates(t *testing.T) {
ctxb := context.Background()
// Restore a legacy database.
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err)
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
tempPath := filepath.Join(tempDirName, dbFileName)
db, err := bbolt.Open(tempPath, 0600, nil)

View file

@ -15,6 +15,7 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lntypes"
"go.etcd.io/bbolt"
bbolterrors "go.etcd.io/bbolt/errors"
)
var (
@ -197,9 +198,9 @@ func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) (
bdb, err := bboltOpen(path, 0600, &bbolt.Options{
Timeout: DefaultLoopDBTimeout,
})
if errors.Is(err, bbolt.ErrTimeout) {
if errors.Is(err, bbolterrors.ErrTimeout) {
return nil, fmt.Errorf("%w: couldn't obtain exclusive lock on "+
"%s, timed out after %v", bbolt.ErrTimeout, path,
"%s, timed out after %v", bbolterrors.ErrTimeout, path,
DefaultLoopDBTimeout)
}
if err != nil {

View file

@ -4,7 +4,6 @@ import (
"context"
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -18,6 +17,7 @@ import (
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
"go.etcd.io/bbolt"
bbolterrors "go.etcd.io/bbolt/errors"
)
var (
@ -62,7 +62,7 @@ func TestNewBoltSwapStoreTimeout(t *testing.T) {
bboltOpen = origOpen
})
wrappedErr := fmt.Errorf("wrapped: %w", bbolt.ErrTimeout)
wrappedErr := fmt.Errorf("wrapped: %w", bbolterrors.ErrTimeout)
bboltOpen = func(path string, mode os.FileMode,
options *bbolt.Options) (*bbolt.DB, error) {
@ -74,7 +74,7 @@ func TestNewBoltSwapStoreTimeout(t *testing.T) {
store, err := NewBoltSwapStore(tempDir, &chaincfg.MainNetParams)
require.Nil(t, store)
require.ErrorIs(t, err, bbolt.ErrTimeout)
require.ErrorIs(t, err, bbolterrors.ErrTimeout)
require.ErrorContains(t, err, "couldn't obtain exclusive lock")
}
@ -142,10 +142,7 @@ func TestLoopOutStore(t *testing.T) {
// testLoopOutStore tests the basic functionality of the current bbolt
// swap store for specific swap parameters.
func testLoopOutStore(t *testing.T, pendingSwap *LoopOutContract) {
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err)
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
require.NoError(t, err)
@ -284,9 +281,7 @@ func TestLoopInStore(t *testing.T) {
}
func testLoopInStore(t *testing.T, pendingSwap LoopInContract) {
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err)
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
require.NoError(t, err)
@ -366,11 +361,7 @@ func testLoopInStore(t *testing.T, pendingSwap LoopInContract) {
// TestVersionNew tests that a new database is initialized with the current
// version.
func TestVersionNew(t *testing.T) {
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
store, err := NewBoltSwapStore(tempDirName, &chaincfg.MainNetParams)
if err != nil {
@ -390,11 +381,7 @@ func TestVersionNew(t *testing.T) {
// TestVersionMigrated tests that an existing version zero database is migrated
// to the latest version.
func TestVersionMigrated(t *testing.T) {
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
createVersionZeroDb(t, tempDirName)
@ -459,11 +446,7 @@ func TestLegacyOutgoingChannel(t *testing.T) {
}
// Restore a legacy database.
tempDirName, err := ioutil.TempDir("", "clientstore")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
tempPath := filepath.Join(tempDirName, dbFileName)
db, err := bbolt.Open(tempPath, 0600, nil)
@ -498,9 +481,7 @@ func TestLegacyOutgoingChannel(t *testing.T) {
// TestLiquidityParams checks that reading and writing to liquidty bucket are
// as expected.
func TestLiquidityParams(t *testing.T) {
tempDirName, err := ioutil.TempDir("", "clientstore")
require.NoError(t, err, "failed to db")
defer os.RemoveAll(tempDirName)
tempDirName := t.TempDir()
ctxb := context.Background()

View file

@ -66,6 +66,10 @@ func (m *mockStaticAddressClient) PushStaticAddressHtlcSigs(ctx context.Context,
args.Error(1)
}
// ServerWithdrawDeposits implements the deprecated RPC required by the
// generated client interface. Production code uses ServerPsbtWithdrawDeposits.
//
//nolint:staticcheck
func (m *mockStaticAddressClient) ServerWithdrawDeposits(ctx context.Context,
in *swapserverrpc.ServerWithdrawRequest,
opts ...grpc.CallOption) (*swapserverrpc.ServerWithdrawResponse,

View file

@ -71,6 +71,10 @@ func (m *mockStaticAddressClient) PushStaticAddressHtlcSigs(ctx context.Context,
args.Error(1)
}
// ServerWithdrawDeposits implements the deprecated RPC required by the
// generated client interface. Production code uses ServerPsbtWithdrawDeposits.
//
//nolint:staticcheck
func (m *mockStaticAddressClient) ServerWithdrawDeposits(ctx context.Context,
in *swapserverrpc.ServerWithdrawRequest,
opts ...grpc.CallOption) (*swapserverrpc.ServerWithdrawResponse,

View file

@ -310,6 +310,8 @@ func (m *Manager) OpenChannel(ctx context.Context,
return nil, err
}
// If a local funding amount is set, coin-select deposits to
// cover it. Otherwise fundmax uses all available deposits.
if req.LocalFundingAmount != 0 {
deposits, err = staticutil.SelectDeposits(
deposits, req.LocalFundingAmount,
@ -319,9 +321,6 @@ func (m *Manager) OpenChannel(ctx context.Context,
return nil, fmt.Errorf("error selecting "+
"deposits: %w", err)
}
} else {
// The fundmax flag is set, hence we select all deposits
// for funding the channel.
}
}
@ -369,6 +368,7 @@ func (m *Manager) OpenChannel(ctx context.Context,
if err == nil {
return chanOutpoint, nil
}
err = maybeWrapTaprootUnsupportedError(reqClone, err)
log.Infof("error opening channel: %v", err)
@ -773,6 +773,9 @@ func resolveCommitmentType(commitmentType lnrpc.CommitmentType) (
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
return lnrpc.CommitmentType_SIMPLE_TAPROOT, nil
case lnrpc.CommitmentType_TAPROOT:
return lnrpc.CommitmentType_TAPROOT, nil
default:
return lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE, fmt.Errorf(
"unsupported commitment type %v", commitmentType,
@ -780,6 +783,30 @@ func resolveCommitmentType(commitmentType lnrpc.CommitmentType) (
}
}
// maybeWrapTaprootUnsupportedError turns lnd's generic unknown channel type
// error into a user-actionable message for Loop's production taproot channel
// type.
func maybeWrapTaprootUnsupportedError(req *lnrpc.OpenChannelRequest,
err error) error {
if err == nil || req.CommitmentType != lnrpc.CommitmentType_TAPROOT {
return err
}
errMsg := strings.ToLower(err.Error())
switch {
case strings.Contains(errMsg, "unhandled request channel type"),
strings.Contains(errMsg, "unknown channel type"),
strings.Contains(errMsg, "unsupported channel type"):
return fmt.Errorf("channel_type=taproot is not supported "+
"by the connected lnd; update LND to v0.21.0-beta "+
"or later to use this channel type: %w", err)
}
return err
}
// checkPsbtFlags make sure a request to open a channel doesn't set any
// parameters that are incompatible with the PSBT funding flow.
func checkPsbtFlags(req *lnrpc.OpenChannelRequest) error {

View file

@ -573,6 +573,11 @@ func TestResolveCommitmentType(t *testing.T) {
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
expectedType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
},
{
name: "production taproot supported",
commitmentType: lnrpc.CommitmentType_TAPROOT,
expectedType: lnrpc.CommitmentType_TAPROOT,
},
{
name: "legacy rejected",
commitmentType: lnrpc.CommitmentType_LEGACY,
@ -596,6 +601,33 @@ func TestResolveCommitmentType(t *testing.T) {
}
}
// TestMaybeWrapTaprootUnsupportedError verifies that generic old-lnd channel
// type rejections become actionable for Loop users selecting production
// taproot channels.
func TestMaybeWrapTaprootUnsupportedError(t *testing.T) {
t.Parallel()
baseErr := errors.New("got error from server: rpc error: " +
"code = Unknown desc = unhandled request channel type 7")
req := &lnrpc.OpenChannelRequest{
CommitmentType: lnrpc.CommitmentType_TAPROOT,
}
err := maybeWrapTaprootUnsupportedError(req, baseErr)
require.ErrorContains(
t, err, "channel_type=taproot is not supported",
)
require.ErrorContains(
t, err, "update LND to v0.21.0-beta or later",
)
require.ErrorIs(t, err, baseErr)
req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT
err = maybeWrapTaprootUnsupportedError(req, baseErr)
require.ErrorIs(t, err, baseErr)
require.NotContains(t, err.Error(), "update LND")
}
// ---------------------------------------------------------------------------
// Mock types for PSBT channel open flow tests.
// ---------------------------------------------------------------------------

View file

@ -232,8 +232,10 @@ func estimateFee(numInputs int, feeRate chainfee.SatPerKWeight,
// Add the funding output based on commitment type.
switch commitmentType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
case lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_TAPROOT:
we.AddP2TROutput()
default:
we.AddP2WSHOutput()
}

View file

@ -271,9 +271,6 @@ func TestSelectDeposits(t *testing.T) {
// High fee rate: 100 sat/vbyte = 25000 sat/kw.
highFeeRate := chainfee.SatPerKVByte(100_000).FeePerKWeight()
anchors := lnrpc.CommitmentType_ANCHORS
taproot := lnrpc.CommitmentType_SIMPLE_TAPROOT
tests := []struct {
name string
deposits []*deposit.Deposit
@ -290,7 +287,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(1_000, 2_000),
amount: 1_000_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -298,7 +295,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(100_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -310,7 +307,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(51_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantErr: "insufficient funds",
},
{
@ -327,7 +324,7 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 400_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
validate: func(t *testing.T, selected []*deposit.Deposit) {
require.Equal(
@ -345,7 +342,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
},
{
@ -353,7 +350,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(60_000, 60_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
},
{
@ -361,7 +358,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(10_000, 200_000, 50_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
// Should pick the 200k deposit.
@ -388,13 +385,13 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(35_500, 35_500, 10_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 3,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), highFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -407,7 +404,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(40_000, 40_000, 40_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 3,
},
{
@ -415,7 +412,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(100_000, 50_000),
amount: 99_000,
feeRate: 0,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
// With zero fee, 100k covers 99k + 0 + dust.
@ -430,12 +427,12 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(200_000, 100_000, 50_000),
amount: 100_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), highFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -448,7 +445,15 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: taproot,
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
wantCount: 1,
},
{
name: "production taproot commitment type",
deposits: makeDeposits(500_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: lnrpc.CommitmentType_TAPROOT,
wantCount: 1,
},
{
@ -459,12 +464,12 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 50_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), lowFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
require.GreaterOrEqual(
t, total,
@ -484,12 +489,12 @@ func TestSelectDeposits(t *testing.T) {
),
amount: 150_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
validate: func(t *testing.T, selected []*deposit.Deposit) {
total := depositSum(selected)
fee := estimateFee(
len(selected), lowFeeRate,
anchors,
lnrpc.CommitmentType_ANCHORS,
)
// Core invariant: selected amount covers
// requested amount + fee + dust.
@ -506,7 +511,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(10_000, 20_000, 300_000),
amount: 100_000,
feeRate: lowFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 1,
validate: func(t *testing.T, selected []*deposit.Deposit) {
require.Equal(
@ -525,7 +530,7 @@ func TestSelectDeposits(t *testing.T) {
deposits: makeDeposits(60_000, 60_000),
amount: 50_000,
feeRate: highFeeRate,
commitmentType: anchors,
commitmentType: lnrpc.CommitmentType_ANCHORS,
wantCount: 2,
},
}

View file

@ -235,6 +235,11 @@ func TestCalculateWithdrawalTxValuesCommitmentTypeParity(t *testing.T) {
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
addr: taprootAddr,
},
{
name: "production taproot and p2tr",
commitmentType: lnrpc.CommitmentType_TAPROOT,
addr: taprootAddr,
},
}
selectedAmounts := []btcutil.Amount{

View file

@ -1108,7 +1108,8 @@ func WithdrawalTxWeight(numInputs int, sweepAddress btcutil.Address,
if commitmentType != lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE {
switch commitmentType {
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
case lnrpc.CommitmentType_SIMPLE_TAPROOT,
lnrpc.CommitmentType_TAPROOT:
weightEstimator.AddP2TROutput()
default:

View file

@ -266,6 +266,11 @@ func (h *mockLightningClient) ListPayments(_ context.Context,
req lndclient.ListPaymentsRequest) (*lndclient.ListPaymentsResponse,
error) {
h.lnd.lock.Lock()
defer h.lnd.lock.Unlock()
h.lnd.ListPaymentsRequests = append(h.lnd.ListPaymentsRequests, req)
if req.Offset >= uint64(len(h.lnd.Payments)) {
return &lndclient.ListPaymentsResponse{}, nil
}
@ -273,7 +278,8 @@ func (h *mockLightningClient) ListPayments(_ context.Context,
lastIndexOffset := req.Offset + req.MaxPayments
lastIndexOffset = min(lastIndexOffset, uint64(len(h.lnd.Payments)))
result := h.lnd.Payments[req.Offset:lastIndexOffset]
result := make([]lndclient.Payment, lastIndexOffset-req.Offset)
copy(result, h.lnd.Payments[req.Offset:lastIndexOffset])
return &lndclient.ListPaymentsResponse{
Payments: result,

View file

@ -162,12 +162,13 @@ type LndMockServices struct {
// keyed by hash string.
Invoices map[lntypes.Hash]*lndclient.Invoice
Channels []lndclient.ChannelInfo
ChannelEdges map[uint64]*lndclient.ChannelEdge
ClosedChannels []lndclient.ClosedChannel
ForwardingEvents []lndclient.ForwardingEvent
Payments []lndclient.Payment
MissionControlState []lndclient.MissionControlEntry
Channels []lndclient.ChannelInfo
ChannelEdges map[uint64]*lndclient.ChannelEdge
ClosedChannels []lndclient.ClosedChannel
ForwardingEvents []lndclient.ForwardingEvent
Payments []lndclient.Payment
ListPaymentsRequests []lndclient.ListPaymentsRequest
MissionControlState []lndclient.MissionControlEntry
WaitForFinished func()
@ -185,6 +186,20 @@ func (s *LndMockServices) EpochSubscribers() int32 {
return int32(len(s.blockHeightListeners))
}
// ListPaymentsRequestsSnapshot returns a copy of all ListPayments requests
// recorded by the mock.
func (s *LndMockServices) ListPaymentsRequestsSnapshot() []lndclient.ListPaymentsRequest {
s.lock.Lock()
defer s.lock.Unlock()
requests := make(
[]lndclient.ListPaymentsRequest, len(s.ListPaymentsRequests),
)
copy(requests, s.ListPaymentsRequests)
return requests
}
// NotifyHeight notifies a new block height.
func (s *LndMockServices) NotifyHeight(height int32) error {
s.lock.Lock()