• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 17774622768

16 Sep 2025 05:57PM UTC coverage: 66.505% (-0.2%) from 66.657%
17774622768

Pull #10067

github

web-flow
Merge 4ec7abb62 into cbed86e21
Pull Request #10067: add sats_per_kweight option when crafting a transaction (continue)

72 of 233 new or added lines in 13 files covered. (30.9%)

355 existing lines in 32 files now uncovered.

136113 of 204666 relevant lines covered (66.5%)

21396.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.97
/lnrpc/walletrpc/walletkit_server.go
1
//go:build walletrpc
2
// +build walletrpc
3

4
package walletrpc
5

6
import (
7
        "bytes"
8
        "context"
9
        "encoding/base64"
10
        "encoding/binary"
11
        "errors"
12
        "fmt"
13
        "maps"
14
        "math"
15
        "os"
16
        "path/filepath"
17
        "slices"
18
        "sort"
19
        "time"
20

21
        "github.com/btcsuite/btcd/btcec/v2"
22
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
23
        "github.com/btcsuite/btcd/btcec/v2/schnorr"
24
        "github.com/btcsuite/btcd/btcutil"
25
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
26
        "github.com/btcsuite/btcd/btcutil/psbt"
27
        "github.com/btcsuite/btcd/chaincfg/chainhash"
28
        "github.com/btcsuite/btcd/txscript"
29
        "github.com/btcsuite/btcd/wire"
30
        "github.com/btcsuite/btcwallet/waddrmgr"
31
        base "github.com/btcsuite/btcwallet/wallet"
32
        "github.com/btcsuite/btcwallet/wtxmgr"
33
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
34
        "github.com/lightningnetwork/lnd/channeldb"
35
        "github.com/lightningnetwork/lnd/contractcourt"
36
        "github.com/lightningnetwork/lnd/fn/v2"
37
        "github.com/lightningnetwork/lnd/input"
38
        "github.com/lightningnetwork/lnd/keychain"
39
        "github.com/lightningnetwork/lnd/labels"
40
        "github.com/lightningnetwork/lnd/lnrpc"
41
        "github.com/lightningnetwork/lnd/lnrpc/signrpc"
42
        "github.com/lightningnetwork/lnd/lnwallet"
43
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
44
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
45
        "github.com/lightningnetwork/lnd/lnwallet/chanfunding"
46
        "github.com/lightningnetwork/lnd/macaroons"
47
        "github.com/lightningnetwork/lnd/sweep"
48
        "google.golang.org/grpc"
49
        "gopkg.in/macaroon-bakery.v2/bakery"
50
)
51

52
var (
53
        // macaroonOps are the set of capabilities that our minted macaroon (if
54
        // it doesn't already exist) will have.
55
        macaroonOps = []bakery.Op{
56
                {
57
                        Entity: "address",
58
                        Action: "write",
59
                },
60
                {
61
                        Entity: "address",
62
                        Action: "read",
63
                },
64
                {
65
                        Entity: "onchain",
66
                        Action: "write",
67
                },
68
                {
69
                        Entity: "onchain",
70
                        Action: "read",
71
                },
72
        }
73

74
        // macPermissions maps RPC calls to the permissions they require.
75
        macPermissions = map[string][]bakery.Op{
76
                "/walletrpc.WalletKit/DeriveNextKey": {{
77
                        Entity: "address",
78
                        Action: "read",
79
                }},
80
                "/walletrpc.WalletKit/DeriveKey": {{
81
                        Entity: "address",
82
                        Action: "read",
83
                }},
84
                "/walletrpc.WalletKit/NextAddr": {{
85
                        Entity: "address",
86
                        Action: "read",
87
                }},
88
                "/walletrpc.WalletKit/GetTransaction": {{
89
                        Entity: "onchain",
90
                        Action: "read",
91
                }},
92
                "/walletrpc.WalletKit/PublishTransaction": {{
93
                        Entity: "onchain",
94
                        Action: "write",
95
                }},
96
                "/walletrpc.WalletKit/SendOutputs": {{
97
                        Entity: "onchain",
98
                        Action: "write",
99
                }},
100
                "/walletrpc.WalletKit/EstimateFee": {{
101
                        Entity: "onchain",
102
                        Action: "read",
103
                }},
104
                "/walletrpc.WalletKit/PendingSweeps": {{
105
                        Entity: "onchain",
106
                        Action: "read",
107
                }},
108
                "/walletrpc.WalletKit/BumpFee": {{
109
                        Entity: "onchain",
110
                        Action: "write",
111
                }},
112
                "/walletrpc.WalletKit/BumpForceCloseFee": {{
113
                        Entity: "onchain",
114
                        Action: "write",
115
                }},
116
                "/walletrpc.WalletKit/ListSweeps": {{
117
                        Entity: "onchain",
118
                        Action: "read",
119
                }},
120
                "/walletrpc.WalletKit/LabelTransaction": {{
121
                        Entity: "onchain",
122
                        Action: "write",
123
                }},
124
                "/walletrpc.WalletKit/LeaseOutput": {{
125
                        Entity: "onchain",
126
                        Action: "write",
127
                }},
128
                "/walletrpc.WalletKit/ReleaseOutput": {{
129
                        Entity: "onchain",
130
                        Action: "write",
131
                }},
132
                "/walletrpc.WalletKit/ListLeases": {{
133
                        Entity: "onchain",
134
                        Action: "read",
135
                }},
136
                "/walletrpc.WalletKit/ListUnspent": {{
137
                        Entity: "onchain",
138
                        Action: "read",
139
                }},
140
                "/walletrpc.WalletKit/ListAddresses": {{
141
                        Entity: "onchain",
142
                        Action: "read",
143
                }},
144
                "/walletrpc.WalletKit/SignMessageWithAddr": {{
145
                        Entity: "onchain",
146
                        Action: "write",
147
                }},
148
                "/walletrpc.WalletKit/VerifyMessageWithAddr": {{
149
                        Entity: "onchain",
150
                        Action: "write",
151
                }},
152
                "/walletrpc.WalletKit/FundPsbt": {{
153
                        Entity: "onchain",
154
                        Action: "write",
155
                }},
156
                "/walletrpc.WalletKit/SignPsbt": {{
157
                        Entity: "onchain",
158
                        Action: "write",
159
                }},
160
                "/walletrpc.WalletKit/FinalizePsbt": {{
161
                        Entity: "onchain",
162
                        Action: "write",
163
                }},
164
                "/walletrpc.WalletKit/ListAccounts": {{
165
                        Entity: "onchain",
166
                        Action: "read",
167
                }},
168
                "/walletrpc.WalletKit/RequiredReserve": {{
169
                        Entity: "onchain",
170
                        Action: "read",
171
                }},
172
                "/walletrpc.WalletKit/ImportAccount": {{
173
                        Entity: "onchain",
174
                        Action: "write",
175
                }},
176
                "/walletrpc.WalletKit/ImportPublicKey": {{
177
                        Entity: "onchain",
178
                        Action: "write",
179
                }},
180
                "/walletrpc.WalletKit/ImportTapscript": {{
181
                        Entity: "onchain",
182
                        Action: "write",
183
                }},
184
                "/walletrpc.WalletKit/RemoveTransaction": {{
185
                        Entity: "onchain",
186
                        Action: "write",
187
                }},
188
        }
189

190
        // DefaultWalletKitMacFilename is the default name of the wallet kit
191
        // macaroon that we expect to find via a file handle within the main
192
        // configuration file in this package.
193
        DefaultWalletKitMacFilename = "walletkit.macaroon"
194

195
        // allWitnessTypes is a mapping between the witness types defined in the
196
        // `input` package, and the witness types in the protobuf definition.
197
        // This map is necessary because the native enum and the protobuf enum
198
        // are numbered differently. The protobuf enum cannot be renumbered
199
        // because this would break backwards compatibility with older clients,
200
        // and the native enum cannot be renumbered because it is stored in the
201
        // watchtower and BreachArbitrator databases.
202
        //
203
        //nolint:ll
204
        allWitnessTypes = map[input.WitnessType]WitnessType{
205
                input.CommitmentTimeLock:                           WitnessType_COMMITMENT_TIME_LOCK,
206
                input.CommitmentNoDelay:                            WitnessType_COMMITMENT_NO_DELAY,
207
                input.CommitmentRevoke:                             WitnessType_COMMITMENT_REVOKE,
208
                input.HtlcOfferedRevoke:                            WitnessType_HTLC_OFFERED_REVOKE,
209
                input.HtlcAcceptedRevoke:                           WitnessType_HTLC_ACCEPTED_REVOKE,
210
                input.HtlcOfferedTimeoutSecondLevel:                WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
211
                input.HtlcAcceptedSuccessSecondLevel:               WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
212
                input.HtlcOfferedRemoteTimeout:                     WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT,
213
                input.HtlcAcceptedRemoteSuccess:                    WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS,
214
                input.HtlcSecondLevelRevoke:                        WitnessType_HTLC_SECOND_LEVEL_REVOKE,
215
                input.WitnessKeyHash:                               WitnessType_WITNESS_KEY_HASH,
216
                input.NestedWitnessKeyHash:                         WitnessType_NESTED_WITNESS_KEY_HASH,
217
                input.CommitmentAnchor:                             WitnessType_COMMITMENT_ANCHOR,
218
                input.HtlcOfferedTimeoutSecondLevelInputConfirmed:  WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED,
219
                input.HtlcAcceptedSuccessSecondLevelInputConfirmed: WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED,
220
                input.CommitSpendNoDelayTweakless:                  WitnessType_COMMITMENT_NO_DELAY_TWEAKLESS,
221
                input.CommitmentToRemoteConfirmed:                  WitnessType_COMMITMENT_TO_REMOTE_CONFIRMED,
222
                input.LeaseCommitmentTimeLock:                      WitnessType_LEASE_COMMITMENT_TIME_LOCK,
223
                input.LeaseCommitmentToRemoteConfirmed:             WitnessType_LEASE_COMMITMENT_TO_REMOTE_CONFIRMED,
224
                input.LeaseHtlcOfferedTimeoutSecondLevel:           WitnessType_LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
225
                input.LeaseHtlcAcceptedSuccessSecondLevel:          WitnessType_LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
226
                input.TaprootPubKeySpend:                           WitnessType_TAPROOT_PUB_KEY_SPEND,
227
                input.TaprootLocalCommitSpend:                      WitnessType_TAPROOT_LOCAL_COMMIT_SPEND,
228
                input.TaprootRemoteCommitSpend:                     WitnessType_TAPROOT_REMOTE_COMMIT_SPEND,
229
                input.TaprootAnchorSweepSpend:                      WitnessType_TAPROOT_ANCHOR_SWEEP_SPEND,
230
                input.TaprootHtlcOfferedTimeoutSecondLevel:         WitnessType_TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL,
231
                input.TaprootHtlcAcceptedSuccessSecondLevel:        WitnessType_TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL,
232
                input.TaprootHtlcSecondLevelRevoke:                 WitnessType_TAPROOT_HTLC_SECOND_LEVEL_REVOKE,
233
                input.TaprootHtlcAcceptedRevoke:                    WitnessType_TAPROOT_HTLC_ACCEPTED_REVOKE,
234
                input.TaprootHtlcOfferedRevoke:                     WitnessType_TAPROOT_HTLC_OFFERED_REVOKE,
235
                input.TaprootHtlcOfferedRemoteTimeout:              WitnessType_TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT,
236
                input.TaprootHtlcLocalOfferedTimeout:               WitnessType_TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT,
237
                input.TaprootHtlcAcceptedRemoteSuccess:             WitnessType_TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS,
238
                input.TaprootHtlcAcceptedLocalSuccess:              WitnessType_TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS,
239
                input.TaprootCommitmentRevoke:                      WitnessType_TAPROOT_COMMITMENT_REVOKE,
240
        }
241
)
242

243
// ServerShell is a shell struct holding a reference to the actual sub-server.
244
// It is used to register the gRPC sub-server with the root server before we
245
// have the necessary dependencies to populate the actual sub-server.
246
type ServerShell struct {
247
        WalletKitServer
248
}
249

250
// WalletKit is a sub-RPC server that exposes a tool kit which allows clients
251
// to execute common wallet operations. This includes requesting new addresses,
252
// keys (for contracts!), and publishing transactions.
253
type WalletKit struct {
254
        // Required by the grpc-gateway/v2 library for forward compatibility.
255
        UnimplementedWalletKitServer
256

257
        cfg *Config
258
}
259

260
// A compile time check to ensure that WalletKit fully implements the
261
// WalletKitServer gRPC service.
262
var _ WalletKitServer = (*WalletKit)(nil)
263

264
// New creates a new instance of the WalletKit sub-RPC server.
265
func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) {
2✔
266
        // If the path of the wallet kit macaroon wasn't specified, then we'll
2✔
267
        // assume that it's found at the default network directory.
2✔
268
        if cfg.WalletKitMacPath == "" {
4✔
269
                cfg.WalletKitMacPath = filepath.Join(
2✔
270
                        cfg.NetworkDir, DefaultWalletKitMacFilename,
2✔
271
                )
2✔
272
        }
2✔
273

274
        // Now that we know the full path of the wallet kit macaroon, we can
275
        // check to see if we need to create it or not. If stateless_init is set
276
        // then we don't write the macaroons.
277
        macFilePath := cfg.WalletKitMacPath
2✔
278
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
2✔
279
                !lnrpc.FileExists(macFilePath) {
4✔
280

2✔
281
                log.Infof("Baking macaroons for WalletKit RPC Server at: %v",
2✔
282
                        macFilePath)
2✔
283

2✔
284
                // At this point, we know that the wallet kit macaroon doesn't
2✔
285
                // yet, exist, so we need to create it with the help of the
2✔
286
                // main macaroon service.
2✔
287
                walletKitMac, err := cfg.MacService.NewMacaroon(
2✔
288
                        context.Background(), macaroons.DefaultRootKeyID,
2✔
289
                        macaroonOps...,
2✔
290
                )
2✔
291
                if err != nil {
2✔
292
                        return nil, nil, err
×
293
                }
×
294
                walletKitMacBytes, err := walletKitMac.M().MarshalBinary()
2✔
295
                if err != nil {
2✔
296
                        return nil, nil, err
×
297
                }
×
298
                err = os.WriteFile(macFilePath, walletKitMacBytes, 0644)
2✔
299
                if err != nil {
2✔
300
                        _ = os.Remove(macFilePath)
×
301
                        return nil, nil, err
×
302
                }
×
303
        }
304

305
        walletKit := &WalletKit{
2✔
306
                cfg: cfg,
2✔
307
        }
2✔
308

2✔
309
        return walletKit, macPermissions, nil
2✔
310
}
311

312
// Start launches any helper goroutines required for the sub-server to function.
313
//
314
// NOTE: This is part of the lnrpc.SubServer interface.
315
func (w *WalletKit) Start() error {
2✔
316
        return nil
2✔
317
}
2✔
318

319
// Stop signals any active goroutines for a graceful closure.
320
//
321
// NOTE: This is part of the lnrpc.SubServer interface.
322
func (w *WalletKit) Stop() error {
2✔
323
        return nil
2✔
324
}
2✔
325

326
// Name returns a unique string representation of the sub-server. This can be
327
// used to identify the sub-server and also de-duplicate them.
328
//
329
// NOTE: This is part of the lnrpc.SubServer interface.
330
func (w *WalletKit) Name() string {
2✔
331
        return SubServerName
2✔
332
}
2✔
333

334
// RegisterWithRootServer will be called by the root gRPC server to direct a
335
// sub RPC server to register itself with the main gRPC root server. Until this
336
// is called, each sub-server won't be able to have requests routed towards it.
337
//
338
// NOTE: This is part of the lnrpc.GrpcHandler interface.
339
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
2✔
340
        // We make sure that we register it with the main gRPC server to ensure
2✔
341
        // all our methods are routed properly.
2✔
342
        RegisterWalletKitServer(grpcServer, r)
2✔
343

2✔
344
        log.Debugf("WalletKit RPC server successfully registered with " +
2✔
345
                "root gRPC server")
2✔
346

2✔
347
        return nil
2✔
348
}
2✔
349

350
// RegisterWithRestServer will be called by the root REST mux to direct a sub
351
// RPC server to register itself with the main REST mux server. Until this is
352
// called, each sub-server won't be able to have requests routed towards it.
353
//
354
// NOTE: This is part of the lnrpc.GrpcHandler interface.
355
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
356
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
2✔
357

2✔
358
        // We make sure that we register it with the main REST server to ensure
2✔
359
        // all our methods are routed properly.
2✔
360
        err := RegisterWalletKitHandlerFromEndpoint(ctx, mux, dest, opts)
2✔
361
        if err != nil {
2✔
362
                log.Errorf("Could not register WalletKit REST server "+
×
363
                        "with root REST server: %v", err)
×
364
                return err
×
365
        }
×
366

367
        log.Debugf("WalletKit REST server successfully registered with " +
2✔
368
                "root REST server")
2✔
369
        return nil
2✔
370
}
371

372
// CreateSubServer populates the subserver's dependencies using the passed
373
// SubServerConfigDispatcher. This method should fully initialize the
374
// sub-server instance, making it ready for action. It returns the macaroon
375
// permissions that the sub-server wishes to pass on to the root server for all
376
// methods routed towards it.
377
//
378
// NOTE: This is part of the lnrpc.GrpcHandler interface.
379
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
380
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
2✔
381

2✔
382
        subServer, macPermissions, err := createNewSubServer(configRegistry)
2✔
383
        if err != nil {
2✔
384
                return nil, nil, err
×
385
        }
×
386

387
        r.WalletKitServer = subServer
2✔
388
        return subServer, macPermissions, nil
2✔
389
}
390

391
// internalScope returns the internal key scope.
392
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
2✔
393
        return waddrmgr.KeyScope{
2✔
394
                Purpose: keychain.BIP0043Purpose,
2✔
395
                Coin:    w.cfg.ChainParams.HDCoinType,
2✔
396
        }
2✔
397
}
2✔
398

399
// ListUnspent returns useful information about each unspent output owned by
400
// the wallet, as reported by the underlying `ListUnspentWitness`; the
401
// information returned is: outpoint, amount in satoshis, address, address
402
// type, scriptPubKey in hex and number of confirmations. The result is
403
// filtered to contain outputs whose number of confirmations is between a
404
// minimum and maximum number of confirmations specified by the user.
405
func (w *WalletKit) ListUnspent(ctx context.Context,
406
        req *ListUnspentRequest) (*ListUnspentResponse, error) {
2✔
407

2✔
408
        // Force min_confs and max_confs to be zero if unconfirmed_only is
2✔
409
        // true.
2✔
410
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
2✔
411
                return nil, fmt.Errorf("min_confs and max_confs must be zero " +
×
412
                        "if unconfirmed_only is true")
×
413
        }
×
414

415
        // When unconfirmed_only is inactive and max_confs is zero (default
416
        // values), we will override max_confs to be a MaxInt32, in order
417
        // to return all confirmed and unconfirmed utxos as a default response.
418
        if req.MaxConfs == 0 && !req.UnconfirmedOnly {
4✔
419
                req.MaxConfs = math.MaxInt32
2✔
420
        }
2✔
421

422
        // Validate the confirmation arguments.
423
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
2✔
424
        if err != nil {
2✔
425
                return nil, err
×
426
        }
×
427

428
        // With our arguments validated, we'll query the internal wallet for
429
        // the set of UTXOs that match our query.
430
        //
431
        // We'll acquire the global coin selection lock to ensure there aren't
432
        // any other concurrent processes attempting to lock any UTXOs which may
433
        // be shown available to us.
434
        var utxos []*lnwallet.Utxo
2✔
435
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
436
                utxos, err = w.cfg.Wallet.ListUnspentWitness(
2✔
437
                        minConfs, maxConfs, req.Account,
2✔
438
                )
2✔
439

2✔
440
                return err
2✔
441
        })
2✔
442
        if err != nil {
2✔
443
                return nil, err
×
444
        }
×
445

446
        rpcUtxos, err := lnrpc.MarshalUtxos(utxos, w.cfg.ChainParams)
2✔
447
        if err != nil {
2✔
448
                return nil, err
×
449
        }
×
450

451
        return &ListUnspentResponse{
2✔
452
                Utxos: rpcUtxos,
2✔
453
        }, nil
2✔
454
}
455

456
// LeaseOutput locks an output to the given ID, preventing it from being
457
// available for any future coin selection attempts. The absolute time of the
458
// lock's expiration is returned. The expiration of the lock can be extended by
459
// successive invocations of this call. Outputs can be unlocked before their
460
// expiration through `ReleaseOutput`.
461
//
462
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
463
// output has already been locked to a different ID, then
464
// wtxmgr.ErrOutputAlreadyLocked is returned.
465
func (w *WalletKit) LeaseOutput(ctx context.Context,
466
        req *LeaseOutputRequest) (*LeaseOutputResponse, error) {
×
467

×
468
        if len(req.Id) != 32 {
×
469
                return nil, errors.New("id must be 32 random bytes")
×
470
        }
×
471
        var lockID wtxmgr.LockID
×
472
        copy(lockID[:], req.Id)
×
473

×
474
        // Don't allow ID's of 32 bytes, but all zeros.
×
475
        if lockID == (wtxmgr.LockID{}) {
×
476
                return nil, errors.New("id must be 32 random bytes")
×
477
        }
×
478

479
        // Don't allow our internal ID to be used externally for locking. Only
480
        // unlocking is allowed.
481
        if lockID == chanfunding.LndInternalLockID {
×
482
                return nil, errors.New("reserved id cannot be used")
×
483
        }
×
484

485
        op, err := UnmarshallOutPoint(req.Outpoint)
×
486
        if err != nil {
×
487
                return nil, err
×
488
        }
×
489

490
        // Use the specified lock duration or fall back to the default.
491
        duration := chanfunding.DefaultLockDuration
×
492
        if req.ExpirationSeconds != 0 {
×
493
                duration = time.Duration(req.ExpirationSeconds) * time.Second
×
494
        }
×
495

496
        // Acquire the global coin selection lock to ensure there aren't any
497
        // other concurrent processes attempting to lease the same UTXO.
498
        var expiration time.Time
×
499
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
500
                expiration, err = w.cfg.Wallet.LeaseOutput(
×
501
                        lockID, *op, duration,
×
502
                )
×
503
                return err
×
504
        })
×
505
        if err != nil {
×
506
                return nil, err
×
507
        }
×
508

509
        return &LeaseOutputResponse{
×
510
                Expiration: uint64(expiration.Unix()),
×
511
        }, nil
×
512
}
513

514
// ReleaseOutput unlocks an output, allowing it to be available for coin
515
// selection if it remains unspent. The ID should match the one used to
516
// originally lock the output.
517
func (w *WalletKit) ReleaseOutput(ctx context.Context,
518
        req *ReleaseOutputRequest) (*ReleaseOutputResponse, error) {
×
519

×
520
        if len(req.Id) != 32 {
×
521
                return nil, errors.New("id must be 32 random bytes")
×
522
        }
×
523
        var lockID wtxmgr.LockID
×
524
        copy(lockID[:], req.Id)
×
525

×
526
        op, err := UnmarshallOutPoint(req.Outpoint)
×
527
        if err != nil {
×
528
                return nil, err
×
529
        }
×
530

531
        // Acquire the global coin selection lock to maintain consistency as
532
        // it's acquired when we initially leased the output.
533
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
534
                return w.cfg.Wallet.ReleaseOutput(lockID, *op)
×
535
        })
×
536
        if err != nil {
×
537
                return nil, err
×
538
        }
×
539

540
        return &ReleaseOutputResponse{
×
541
                Status: fmt.Sprintf("output %v released", op.String()),
×
542
        }, nil
×
543
}
544

545
// ListLeases returns a list of all currently locked utxos.
546
func (w *WalletKit) ListLeases(ctx context.Context,
547
        req *ListLeasesRequest) (*ListLeasesResponse, error) {
2✔
548

2✔
549
        leases, err := w.cfg.Wallet.ListLeasedOutputs()
2✔
550
        if err != nil {
2✔
551
                return nil, err
×
552
        }
×
553

554
        return &ListLeasesResponse{
2✔
555
                LockedUtxos: marshallLeases(leases),
2✔
556
        }, nil
2✔
557
}
558

559
// DeriveNextKey attempts to derive the *next* key within the key family
560
// (account in BIP43) specified. This method should return the next external
561
// child within this branch.
562
func (w *WalletKit) DeriveNextKey(ctx context.Context,
563
        req *KeyReq) (*signrpc.KeyDescriptor, error) {
2✔
564

2✔
565
        nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey(
2✔
566
                keychain.KeyFamily(req.KeyFamily),
2✔
567
        )
2✔
568
        if err != nil {
2✔
569
                return nil, err
×
570
        }
×
571

572
        return &signrpc.KeyDescriptor{
2✔
573
                KeyLoc: &signrpc.KeyLocator{
2✔
574
                        KeyFamily: int32(nextKeyDesc.Family),
2✔
575
                        KeyIndex:  int32(nextKeyDesc.Index),
2✔
576
                },
2✔
577
                RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(),
2✔
578
        }, nil
2✔
579
}
580

581
// DeriveKey attempts to derive an arbitrary key specified by the passed
582
// KeyLocator.
583
func (w *WalletKit) DeriveKey(ctx context.Context,
584
        req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
2✔
585

2✔
586
        keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{
2✔
587
                Family: keychain.KeyFamily(req.KeyFamily),
2✔
588
                Index:  uint32(req.KeyIndex),
2✔
589
        })
2✔
590
        if err != nil {
2✔
591
                return nil, err
×
592
        }
×
593

594
        return &signrpc.KeyDescriptor{
2✔
595
                KeyLoc: &signrpc.KeyLocator{
2✔
596
                        KeyFamily: int32(keyDesc.Family),
2✔
597
                        KeyIndex:  int32(keyDesc.Index),
2✔
598
                },
2✔
599
                RawKeyBytes: keyDesc.PubKey.SerializeCompressed(),
2✔
600
        }, nil
2✔
601
}
602

603
// NextAddr returns the next unused address within the wallet.
604
func (w *WalletKit) NextAddr(ctx context.Context,
605
        req *AddrRequest) (*AddrResponse, error) {
2✔
606

2✔
607
        account := lnwallet.DefaultAccountName
2✔
608
        if req.Account != "" {
2✔
609
                account = req.Account
×
610
        }
×
611

612
        addrType := lnwallet.WitnessPubKey
2✔
613
        switch req.Type {
2✔
614
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
615
                addrType = lnwallet.NestedWitnessPubKey
×
616

617
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
618
                return nil, fmt.Errorf("invalid address type for next "+
×
619
                        "address: %v", req.Type)
×
620

621
        case AddressType_TAPROOT_PUBKEY:
×
622
                addrType = lnwallet.TaprootPubkey
×
623
        }
624

625
        addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account)
2✔
626
        if err != nil {
2✔
627
                return nil, err
×
628
        }
×
629

630
        return &AddrResponse{
2✔
631
                Addr: addr.String(),
2✔
632
        }, nil
2✔
633
}
634

635
// GetTransaction returns a transaction from the wallet given its hash.
636
func (w *WalletKit) GetTransaction(_ context.Context,
637
        req *GetTransactionRequest) (*lnrpc.Transaction, error) {
2✔
638

2✔
639
        // If the client doesn't specify a hash, then there's nothing to
2✔
640
        // return.
2✔
641
        if req.Txid == "" {
2✔
642
                return nil, fmt.Errorf("must provide a transaction hash")
×
643
        }
×
644

645
        txHash, err := chainhash.NewHashFromStr(req.Txid)
2✔
646
        if err != nil {
2✔
647
                return nil, err
×
648
        }
×
649

650
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
2✔
651
        if err != nil {
2✔
652
                return nil, err
×
653
        }
×
654

655
        return lnrpc.RPCTransaction(res), nil
2✔
656
}
657

658
// Attempts to publish the passed transaction to the network. Once this returns
659
// without an error, the wallet will continually attempt to re-broadcast the
660
// transaction on start up, until it enters the chain.
661
func (w *WalletKit) PublishTransaction(ctx context.Context,
662
        req *Transaction) (*PublishResponse, error) {
2✔
663

2✔
664
        switch {
2✔
665
        // If the client doesn't specify a transaction, then there's nothing to
666
        // publish.
667
        case len(req.TxHex) == 0:
×
668
                return nil, fmt.Errorf("must provide a transaction to " +
×
669
                        "publish")
×
670
        }
671

672
        tx := &wire.MsgTx{}
2✔
673
        txReader := bytes.NewReader(req.TxHex)
2✔
674
        if err := tx.Deserialize(txReader); err != nil {
2✔
675
                return nil, err
×
676
        }
×
677

678
        label, err := labels.ValidateAPI(req.Label)
2✔
679
        if err != nil {
2✔
680
                return nil, err
×
681
        }
×
682

683
        err = w.cfg.Wallet.PublishTransaction(tx, label)
2✔
684
        if err != nil {
2✔
685
                return nil, err
×
686
        }
×
687

688
        return &PublishResponse{}, nil
2✔
689
}
690

691
// RemoveTransaction attempts to remove the transaction and all of its
692
// descendants resulting from further spends of the outputs of the provided
693
// transaction id.
694
// NOTE: We do not remove the transaction from the rebroadcaster which might
695
// run in the background rebroadcasting not yet confirmed transactions. We do
696
// not have access to the rebroadcaster here nor should we. This command is not
697
// a way to remove transactions from the network. It is a way to shortcircuit
698
// wallet utxo housekeeping while transactions are still unconfirmed and we know
699
// that a transaction will never confirm because a replacement already pays
700
// higher fees.
701
func (w *WalletKit) RemoveTransaction(_ context.Context,
702
        req *GetTransactionRequest) (*RemoveTransactionResponse, error) {
2✔
703

2✔
704
        // If the client doesn't specify a hash, then there's nothing to
2✔
705
        // return.
2✔
706
        if req.Txid == "" {
2✔
707
                return nil, fmt.Errorf("must provide a transaction hash")
×
708
        }
×
709

710
        txHash, err := chainhash.NewHashFromStr(req.Txid)
2✔
711
        if err != nil {
2✔
712
                return nil, err
×
713
        }
×
714

715
        // Query the tx store of our internal wallet for the specified
716
        // transaction.
717
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
2✔
718
        if err != nil {
2✔
719
                return nil, fmt.Errorf("transaction with txid=%v not found "+
×
720
                        "in the internal wallet store", txHash)
×
721
        }
×
722

723
        // Only allow unconfirmed transactions to be removed because as soon
724
        // as a transaction is confirmed it will be evaluated by the wallet
725
        // again and the wallet state would be updated in case the user had
726
        // removed the transaction accidentally.
727
        if res.NumConfirmations > 0 {
2✔
728
                return nil, fmt.Errorf("transaction with txid=%v is already "+
×
729
                        "confirmed (numConfs=%d) cannot be removed", txHash,
×
730
                        res.NumConfirmations)
×
731
        }
×
732

733
        tx := &wire.MsgTx{}
2✔
734
        txReader := bytes.NewReader(res.RawTx)
2✔
735
        if err := tx.Deserialize(txReader); err != nil {
2✔
736
                return nil, err
×
737
        }
×
738

739
        err = w.cfg.Wallet.RemoveDescendants(tx)
2✔
740
        if err != nil {
2✔
741
                return nil, err
×
742
        }
×
743

744
        return &RemoveTransactionResponse{
2✔
745
                Status: "Successfully removed transaction",
2✔
746
        }, nil
2✔
747
}
748

749
// SendOutputs is similar to the existing sendmany call in Bitcoind, and allows
750
// the caller to create a transaction that sends to several outputs at once.
751
// This is ideal when wanting to batch create a set of transactions.
752
func (w *WalletKit) SendOutputs(ctx context.Context,
753
        req *SendOutputsRequest) (*SendOutputsResponse, error) {
2✔
754

2✔
755
        switch {
2✔
756
        // If the client didn't specify any outputs to create, then  we can't
757
        // proceed .
758
        case len(req.Outputs) == 0:
×
759
                return nil, fmt.Errorf("must specify at least one output " +
×
760
                        "to create")
×
761
        }
762

763
        // Before we can request this transaction to be created, we'll need to
764
        // amp the protos back into the format that the internal wallet will
765
        // recognize.
766
        var totalOutputValue int64
2✔
767
        outputsToCreate := make([]*wire.TxOut, 0, len(req.Outputs))
2✔
768
        for _, output := range req.Outputs {
4✔
769
                outputsToCreate = append(outputsToCreate, &wire.TxOut{
2✔
770
                        Value:    output.Value,
2✔
771
                        PkScript: output.PkScript,
2✔
772
                })
2✔
773
                totalOutputValue += output.Value
2✔
774
        }
2✔
775

776
        // Then, we'll extract the minimum number of confirmations that each
777
        // output we use to fund the transaction should satisfy.
778
        minConfs, err := lnrpc.ExtractMinConfs(
2✔
779
                req.MinConfs, req.SpendUnconfirmed,
2✔
780
        )
2✔
781
        if err != nil {
2✔
782
                return nil, err
×
783
        }
×
784

785
        // Before sending out funds we need to ensure that the remainder of our
786
        // wallet funds would cover for the anchor reserve requirement. We'll
787
        // also take unconfirmed funds into account.
788
        walletBalance, err := w.cfg.Wallet.ConfirmedBalance(
2✔
789
                0, lnwallet.DefaultAccountName,
2✔
790
        )
2✔
791
        if err != nil {
2✔
792
                return nil, err
×
793
        }
×
794

795
        // We'll get the currently required reserve amount.
796
        reserve, err := w.RequiredReserve(ctx, &RequiredReserveRequest{})
2✔
797
        if err != nil {
2✔
798
                return nil, err
×
799
        }
×
800

801
        // Then we check if our current wallet balance undershoots the required
802
        // reserve if we'd send out the outputs specified in the request.
803
        if int64(walletBalance)-totalOutputValue < reserve.RequiredReserve {
4✔
804
                return nil, ErrInsufficientReserve
2✔
805
        }
2✔
806

807
        label, err := labels.ValidateAPI(req.Label)
2✔
808
        if err != nil {
2✔
809
                return nil, err
×
810
        }
×
811

812
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
2✔
813
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
2✔
814
        )
2✔
815
        if err != nil {
2✔
816
                return nil, err
×
817
        }
×
818

819
        // Now that we have the outputs mapped and checked for the reserve
820
        // requirement, we can request that the wallet attempts to create this
821
        // transaction.
822
        tx, err := w.cfg.Wallet.SendOutputs(
2✔
823
                nil, outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw),
2✔
824
                minConfs, label, coinSelectionStrategy,
2✔
825
        )
2✔
826
        if err != nil {
2✔
827
                return nil, err
×
828
        }
×
829

830
        var b bytes.Buffer
2✔
831
        if err := tx.Serialize(&b); err != nil {
2✔
832
                return nil, err
×
833
        }
×
834

835
        return &SendOutputsResponse{
2✔
836
                RawTx: b.Bytes(),
2✔
837
        }, nil
2✔
838
}
839

840
// EstimateFee attempts to query the internal fee estimator of the wallet to
841
// determine the fee (in sat/kw) to attach to a transaction in order to achieve
842
// the confirmation target.
843
func (w *WalletKit) EstimateFee(ctx context.Context,
844
        req *EstimateFeeRequest) (*EstimateFeeResponse, error) {
2✔
845

2✔
846
        // A confirmation target of zero or lower doesn't make any sense.
2✔
847
        if req.ConfTarget <= 0 {
4✔
848
                return nil, fmt.Errorf("confirmation target must be greater " +
2✔
849
                        "than 0")
2✔
850
        }
2✔
851

852
        satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW(
2✔
853
                uint32(req.ConfTarget),
2✔
854
        )
2✔
855
        if err != nil {
2✔
856
                return nil, err
×
857
        }
×
858

859
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
2✔
860

2✔
861
        return &EstimateFeeResponse{
2✔
862
                SatPerKw:            int64(satPerKw),
2✔
863
                MinRelayFeeSatPerKw: int64(relayFeePerKw),
2✔
864
        }, nil
2✔
865
}
866

867
// PendingSweeps returns lists of on-chain outputs that lnd is currently
868
// attempting to sweep within its central batching engine. Outputs with similar
869
// fee rates are batched together in order to sweep them within a single
870
// transaction. The fee rate of each sweeping transaction is determined by
871
// taking the average fee rate of all the outputs it's trying to sweep.
872
func (w *WalletKit) PendingSweeps(ctx context.Context,
873
        in *PendingSweepsRequest) (*PendingSweepsResponse, error) {
2✔
874

2✔
875
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
2✔
876
        // sweep.
2✔
877
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
2✔
878
        if err != nil {
2✔
879
                return nil, err
×
880
        }
×
881

882
        // Convert them into their respective RPC format.
883
        rpcPendingSweeps := make([]*PendingSweep, 0, len(inputsMap))
2✔
884
        for _, inp := range inputsMap {
4✔
885
                witnessType, ok := allWitnessTypes[inp.WitnessType]
2✔
886
                if !ok {
2✔
887
                        return nil, fmt.Errorf("unhandled witness type %v for "+
×
888
                                "input %v", inp.WitnessType, inp.OutPoint)
×
889
                }
×
890

891
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
2✔
892
                amountSat := uint32(inp.Amount)
2✔
893
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
2✔
894
                satPerKWeight := uint64(inp.LastFeeRate)
2✔
895
                broadcastAttempts := uint32(inp.BroadcastAttempts)
2✔
896

2✔
897
                // Get the requested starting fee rate, if set.
2✔
898
                startingFeeRate := fn.MapOptionZ(
2✔
899
                        inp.Params.StartingFeeRate,
2✔
900
                        func(feeRate chainfee.SatPerKWeight) uint64 {
4✔
901
                                return uint64(feeRate.FeePerVByte())
2✔
902
                        })
2✔
903

904
                startingFeeRateKw := fn.MapOptionZ(
2✔
905
                        inp.Params.StartingFeeRate,
2✔
906
                        func(feeRate chainfee.SatPerKWeight) uint64 {
4✔
907
                                return uint64(feeRate)
2✔
908
                        },
2✔
909
                )
910

911
                ps := &PendingSweep{
2✔
912
                        Outpoint:             op,
2✔
913
                        WitnessType:          witnessType,
2✔
914
                        AmountSat:            amountSat,
2✔
915
                        SatPerVbyte:          satPerVbyte,
2✔
916
                        SatPerKw:             satPerKWeight,
2✔
917
                        BroadcastAttempts:    broadcastAttempts,
2✔
918
                        Immediate:            inp.Params.Immediate,
2✔
919
                        Budget:               uint64(inp.Params.Budget),
2✔
920
                        DeadlineHeight:       inp.DeadlineHeight,
2✔
921
                        RequestedSatPerVbyte: startingFeeRate,
2✔
922
                        RequestedSatPerKw:    startingFeeRateKw,
2✔
923
                        MaturityHeight:       inp.MaturityHeight,
2✔
924
                }
2✔
925
                rpcPendingSweeps = append(rpcPendingSweeps, ps)
2✔
926
        }
927

928
        return &PendingSweepsResponse{
2✔
929
                PendingSweeps: rpcPendingSweeps,
2✔
930
        }, nil
2✔
931
}
932

933
// UnmarshallOutPoint converts an outpoint from its lnrpc type to its canonical
934
// type.
935
func UnmarshallOutPoint(op *lnrpc.OutPoint) (*wire.OutPoint, error) {
2✔
936
        if op == nil {
2✔
937
                return nil, fmt.Errorf("empty outpoint provided")
×
938
        }
×
939

940
        var hash chainhash.Hash
2✔
941
        switch {
2✔
942
        // Return an error if both txid fields are unpopulated.
943
        case len(op.TxidBytes) == 0 && len(op.TxidStr) == 0:
×
944
                return nil, fmt.Errorf("TxidBytes and TxidStr are both " +
×
945
                        "unspecified")
×
946

947
        // The hash was provided as raw bytes.
948
        case len(op.TxidBytes) != 0:
2✔
949
                copy(hash[:], op.TxidBytes)
2✔
950

951
        // The hash was provided as a hex-encoded string.
952
        case len(op.TxidStr) != 0:
2✔
953
                h, err := chainhash.NewHashFromStr(op.TxidStr)
2✔
954
                if err != nil {
2✔
955
                        return nil, err
×
956
                }
×
957
                hash = *h
2✔
958
        }
959

960
        return &wire.OutPoint{
2✔
961
                Hash:  hash,
2✔
962
                Index: op.OutputIndex,
2✔
963
        }, nil
2✔
964
}
965

966
// validateBumpFeeRequest makes sure the deprecated fields are not used when
967
// the new fields are set.
968
func validateBumpFeeRequest(in *BumpFeeRequest, estimator chainfee.Estimator) (
969
        fn.Option[chainfee.SatPerKWeight], bool, error) {
2✔
970

2✔
971
        // Get the specified fee rate if set.
2✔
972
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
2✔
973

2✔
974
        // We only allow using either the SatPerVbyte or SatPerKweight,
2✔
975
        // but not both.
2✔
976
        switch {
2✔
NEW
977
        case in.SatPerKw != 0 && in.SatPerVbyte != 0:
×
NEW
978
                return satPerKwOpt, false, fmt.Errorf("either SatPerVbyte or " +
×
NEW
979
                        "SatPerKweight should be set, but not both")
×
980

981
        case in.SatPerVbyte != 0:
2✔
982
                satPerKw := chainfee.SatPerVByte(
2✔
983
                        in.SatPerVbyte,
2✔
984
                ).FeePerKWeight()
2✔
985
                satPerKwOpt = fn.Some(satPerKw)
2✔
986

NEW
987
        case in.SatPerKw != 0:
×
NEW
988
                satPerKw := chainfee.SatPerKWeight(
×
NEW
989
                        in.SatPerKw,
×
NEW
990
                )
×
NEW
991
                satPerKwOpt = fn.Some(satPerKw)
×
992
        }
993

994
        // We make sure either the conf target or the exact fee rate is
995
        // specified for the starting fee of the fee function.
996
        if in.TargetConf != 0 && !satPerKwOpt.IsNone() {
2✔
997
                return satPerKwOpt, false,
×
998
                        fmt.Errorf("either TargetConf or SatPerVbyte should " +
×
999
                                "be set, to specify the starting fee rate of " +
×
1000
                                "the fee function")
×
1001
        }
×
1002

1003
        // In case the user specified a conf target, we estimate the fee rate
1004
        // for the given target using the provided estimator.
1005
        if in.TargetConf != 0 {
3✔
1006
                startingFeeRate, err := estimator.EstimateFeePerKW(
1✔
1007
                        in.TargetConf,
1✔
1008
                )
1✔
1009
                if err != nil {
1✔
1010
                        return satPerKwOpt, false, fmt.Errorf("unable to "+
×
1011
                                "estimate fee rate for target conf %d: %w",
×
1012
                                in.TargetConf, err)
×
1013
                }
×
1014

1015
                // Set the starting fee rate to the estimated fee rate.
1016
                satPerKwOpt = fn.Some(startingFeeRate)
1✔
1017
        }
1018

1019
        var immediate bool
2✔
1020
        switch {
2✔
1021
        case in.Force && in.Immediate:
×
1022
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
1023
                        "Immediate should be set, but not both")
×
1024

1025
        case in.Force:
×
1026
                immediate = in.Force
×
1027

1028
        case in.Immediate:
2✔
1029
                immediate = in.Immediate
2✔
1030
        }
1031

1032
        if in.DeadlineDelta != 0 && in.Budget == 0 {
2✔
1033
                return satPerKwOpt, immediate, fmt.Errorf("budget must be " +
×
1034
                        "set if deadline-delta is set")
×
1035
        }
×
1036

1037
        return satPerKwOpt, immediate, nil
2✔
1038
}
1039

1040
// prepareSweepParams creates the sweep params to be used for the sweeper. It
1041
// returns the new params and a bool indicating whether this is an existing
1042
// input.
1043
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1044
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
2✔
1045

2✔
1046
        // Return an error if the bump fee request is invalid.
2✔
1047
        feeRate, immediate, err := validateBumpFeeRequest(
2✔
1048
                in, w.cfg.FeeEstimator,
2✔
1049
        )
2✔
1050
        if err != nil {
2✔
1051
                return sweep.Params{}, false, err
×
1052
        }
×
1053

1054
        // Get the current pending inputs.
1055
        inputMap, err := w.cfg.Sweeper.PendingInputs()
2✔
1056
        if err != nil {
2✔
1057
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1058
                        "pending inputs: %w", err)
×
1059
        }
×
1060

1061
        // Find the pending input.
1062
        //
1063
        // TODO(yy): act differently based on the state of the input?
1064
        inp, ok := inputMap[op]
2✔
1065

2✔
1066
        if !ok {
4✔
1067
                // NOTE: if this input doesn't exist and the new budget is not
2✔
1068
                // specified, the params would have a zero budget.
2✔
1069
                params := sweep.Params{
2✔
1070
                        Immediate:       immediate,
2✔
1071
                        StartingFeeRate: feeRate,
2✔
1072
                        Budget:          btcutil.Amount(in.Budget),
2✔
1073
                }
2✔
1074

2✔
1075
                if in.DeadlineDelta != 0 {
4✔
1076
                        params.DeadlineHeight = fn.Some(
2✔
1077
                                int32(in.DeadlineDelta) + currentHeight,
2✔
1078
                        )
2✔
1079
                }
2✔
1080

1081
                return params, ok, nil
2✔
1082
        }
1083

1084
        // Find the existing budget used for this input. Note that this value
1085
        // must be greater than zero.
1086
        budget := inp.Params.Budget
2✔
1087

2✔
1088
        // Set the new budget if specified. If a new deadline delta is
2✔
1089
        // specified we also require the budget value which is checked in the
2✔
1090
        // validateBumpFeeRequest function.
2✔
1091
        if in.Budget != 0 {
4✔
1092
                budget = btcutil.Amount(in.Budget)
2✔
1093
        }
2✔
1094

1095
        // For an existing input, we assign it first, then overwrite it if
1096
        // a deadline is requested.
1097
        deadline := inp.Params.DeadlineHeight
2✔
1098

2✔
1099
        // Set the deadline if it was specified.
2✔
1100
        //
2✔
1101
        // TODO(yy): upgrade `falafel` so we can make this field optional. Atm
2✔
1102
        // we cannot distinguish between user's not setting the field and
2✔
1103
        // setting it to 0.
2✔
1104
        if in.DeadlineDelta != 0 {
4✔
1105
                deadline = fn.Some(int32(in.DeadlineDelta) + currentHeight)
2✔
1106
        }
2✔
1107

1108
        startingFeeRate := inp.Params.StartingFeeRate
2✔
1109

2✔
1110
        // We only set the starting fee rate if it was specified else we keep
2✔
1111
        // the existing one.
2✔
1112
        if feeRate.IsSome() {
4✔
1113
                startingFeeRate = feeRate
2✔
1114
        }
2✔
1115

1116
        // Prepare the new sweep params.
1117
        //
1118
        // NOTE: if this input doesn't exist and the new budget is not
1119
        // specified, the params would have a zero budget.
1120
        params := sweep.Params{
2✔
1121
                Immediate:       immediate,
2✔
1122
                DeadlineHeight:  deadline,
2✔
1123
                StartingFeeRate: startingFeeRate,
2✔
1124
                Budget:          budget,
2✔
1125
        }
2✔
1126

2✔
1127
        log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
2✔
1128
                "params=%v, new params=%v", op, inp.Params, params)
2✔
1129

2✔
1130
        return params, ok, nil
2✔
1131
}
1132

1133
// BumpFee allows bumping the fee rate of an arbitrary input. A fee preference
1134
// can be expressed either as a specific fee rate or a delta of blocks in which
1135
// the output should be swept on-chain within. If a fee preference is not
1136
// explicitly specified, then an error is returned. The status of the input
1137
// sweep can be checked through the PendingSweeps RPC.
1138
func (w *WalletKit) BumpFee(ctx context.Context,
1139
        in *BumpFeeRequest) (*BumpFeeResponse, error) {
2✔
1140

2✔
1141
        // Parse the outpoint from the request.
2✔
1142
        op, err := UnmarshallOutPoint(in.Outpoint)
2✔
1143
        if err != nil {
2✔
1144
                return nil, err
×
1145
        }
×
1146

1147
        // Get the current height so we can calculate the deadline height.
1148
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1149
        if err != nil {
2✔
1150
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1151
                        err)
×
1152
        }
×
1153

1154
        // We now create a new sweeping params and update it in the sweeper.
1155
        // This will complicate the RBF conditions if this input has already
1156
        // been offered to sweeper before and it has already been included in a
1157
        // tx with other inputs. If this is the case, two results are possible:
1158
        // - either this input successfully RBFed the existing tx, or,
1159
        // - the budget of this input was not enough to RBF the existing tx.
1160
        params, existing, err := w.prepareSweepParams(in, *op, currentHeight)
2✔
1161
        if err != nil {
2✔
1162
                return nil, err
×
1163
        }
×
1164

1165
        // If this input exists, we will update its params.
1166
        if existing {
4✔
1167
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
2✔
1168
                if err != nil {
2✔
1169
                        return nil, err
×
1170
                }
×
1171

1172
                return &BumpFeeResponse{
2✔
1173
                        Status: "Successfully registered rbf-tx with sweeper",
2✔
1174
                }, nil
2✔
1175
        }
1176

1177
        // Otherwise, create a new sweeping request for this input.
1178
        err = w.sweepNewInput(op, uint32(currentHeight), params)
2✔
1179
        if err != nil {
4✔
1180
                return nil, err
2✔
1181
        }
2✔
1182

1183
        return &BumpFeeResponse{
2✔
1184
                Status: "Successfully registered CPFP-tx with the sweeper",
2✔
1185
        }, nil
2✔
1186
}
1187

1188
// getWaitingCloseChannel returns the waiting close channel in case it does
1189
// exist in the underlying channel state database.
1190
func (w *WalletKit) getWaitingCloseChannel(
1191
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
1✔
1192

1✔
1193
        // Fetch all channels, which still have their commitment transaction not
1✔
1194
        // confirmed (waiting close channels).
1✔
1195
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
1✔
1196
        if err != nil {
1✔
1197
                return nil, err
×
1198
        }
×
1199

1200
        channel := fn.Find(chans, func(c *channeldb.OpenChannel) bool {
2✔
1201
                return c.FundingOutpoint == chanPoint
1✔
1202
        })
1✔
1203

1204
        return channel.UnwrapOrErr(errors.New("channel not found"))
1✔
1205
}
1206

1207
// BumpForceCloseFee bumps the fee rate of an unconfirmed anchor channel. It
1208
// updates the new fee rate parameters with the sweeper subsystem. Additionally
1209
// it will try to create anchor cpfp transactions for all possible commitment
1210
// transactions (local, remote, remote-dangling) so depending on which
1211
// commitment is in the local mempool only one of them will succeed in being
1212
// broadcasted.
1213
func (w *WalletKit) BumpForceCloseFee(_ context.Context,
1214
        in *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
1✔
1215

1✔
1216
        if in.ChanPoint == nil {
1✔
1217
                return nil, fmt.Errorf("no chan_point provided")
×
1218
        }
×
1219

1220
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
1✔
1221
        if err != nil {
1✔
1222
                return nil, err
×
1223
        }
×
1224

1225
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
1✔
1226
        if err != nil {
1✔
1227
                return nil, err
×
1228
        }
×
1229

1230
        // Get the relevant channel if it is in the waiting close state.
1231
        channel, err := w.getWaitingCloseChannel(*outPoint)
1✔
1232
        if err != nil {
1✔
1233
                return nil, err
×
1234
        }
×
1235

1236
        if !channel.ChanType.HasAnchors() {
1✔
1237
                return nil, fmt.Errorf("not able to bump the fee of a " +
×
1238
                        "non-anchor channel")
×
1239
        }
×
1240

1241
        // Match pending sweeps with commitments of the channel for which a bump
1242
        // is requested. Depending on the commitment state when force closing
1243
        // the channel we might have up to 3 commitments to consider when
1244
        // bumping the fee.
1245
        commitSet := fn.NewSet[chainhash.Hash]()
1✔
1246

1✔
1247
        if channel.LocalCommitment.CommitTx != nil {
2✔
1248
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
1✔
1249
                commitSet.Add(localTxID)
1✔
1250
        }
1✔
1251

1252
        if channel.RemoteCommitment.CommitTx != nil {
2✔
1253
                remoteTxID := channel.RemoteCommitment.CommitTx.TxHash()
1✔
1254
                commitSet.Add(remoteTxID)
1✔
1255
        }
1✔
1256

1257
        // Check whether there was a dangling commitment at the time the channel
1258
        // was force closed.
1259
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
1✔
1260
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
1✔
1261
                return nil, err
×
1262
        }
×
1263

1264
        if remoteCommitDiff != nil {
1✔
1265
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1266
                commitSet.Add(hash)
×
1267
        }
×
1268

1269
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1270
        // sweep.
1271
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
1✔
1272
        if err != nil {
1✔
1273
                return nil, err
×
1274
        }
×
1275

1276
        // Get the current height so we can calculate the deadline height.
1277
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
1✔
1278
        if err != nil {
1✔
1279
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1280
                        err)
×
1281
        }
×
1282

1283
        pendingSweeps := slices.Collect(maps.Values(inputsMap))
1✔
1284

1✔
1285
        // Discard everything except for the anchor sweeps.
1✔
1286
        anchors := fn.Filter(
1✔
1287
                pendingSweeps,
1✔
1288
                func(sweep *sweep.PendingInputResponse) bool {
2✔
1289
                        // Only filter for anchor inputs because these are the
1✔
1290
                        // only inputs which can be used to bump a closed
1✔
1291
                        // unconfirmed commitment transaction.
1✔
1292
                        isCommitAnchor := sweep.WitnessType ==
1✔
1293
                                input.CommitmentAnchor
1✔
1294
                        isTaprootSweepSpend := sweep.WitnessType ==
1✔
1295
                                input.TaprootAnchorSweepSpend
1✔
1296
                        if !isCommitAnchor && !isTaprootSweepSpend {
1✔
1297
                                return false
×
1298
                        }
×
1299

1300
                        return commitSet.Contains(sweep.OutPoint.Hash)
1✔
1301
                },
1302
        )
1303

1304
        if len(anchors) == 0 {
1✔
1305
                return nil, fmt.Errorf("unable to find pending anchor outputs")
×
1306
        }
×
1307

1308
        // Filter all relevant anchor sweeps and update the sweep request.
1309
        for _, anchor := range anchors {
2✔
1310
                // Anchor cpfp bump request are predictable because they are
1✔
1311
                // swept separately hence not batched with other sweeps (they
1✔
1312
                // are marked with the exclusive group flag). Bumping the fee
1✔
1313
                // rate does not create any conflicting fee bump conditions.
1✔
1314
                // Either the rbf requirements are met or the bump is rejected
1✔
1315
                // by the mempool rules.
1✔
1316
                params, existing, err := w.prepareSweepParams(
1✔
1317
                        &BumpFeeRequest{
1✔
1318
                                Outpoint:      lnrpcOutpoint,
1✔
1319
                                TargetConf:    in.TargetConf,
1✔
1320
                                SatPerVbyte:   in.StartingFeerate,
1✔
1321
                                Immediate:     in.Immediate,
1✔
1322
                                Budget:        in.Budget,
1✔
1323
                                DeadlineDelta: in.DeadlineDelta,
1✔
1324
                        }, anchor.OutPoint, currentHeight,
1✔
1325
                )
1✔
1326
                if err != nil {
1✔
1327
                        return nil, err
×
1328
                }
×
1329

1330
                // There might be the case when an anchor sweep is confirmed
1331
                // between fetching the pending sweeps and preparing the sweep
1332
                // params. We log this case and proceed.
1333
                if !existing {
1✔
1334
                        log.Errorf("Sweep anchor input(%v) not known to the " +
×
1335
                                "sweeper subsystem")
×
1336
                        continue
×
1337
                }
1338

1339
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
1✔
1340
                if err != nil {
1✔
1341
                        return nil, err
×
1342
                }
×
1343
        }
1344

1345
        return &BumpForceCloseFeeResponse{
1✔
1346
                Status: "Successfully registered anchor-cpfp transaction to" +
1✔
1347
                        "bump channel force close transaction",
1✔
1348
        }, nil
1✔
1349
}
1350

1351
// sweepNewInput handles the case where an input is seen the first time by the
1352
// sweeper. It will fetch the output from the wallet and construct an input and
1353
// offer it to the sweeper.
1354
//
1355
// NOTE: if the budget is not set, the default budget ratio is used.
1356
func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
1357
        params sweep.Params) error {
2✔
1358

2✔
1359
        log.Debugf("Attempting to sweep outpoint %s", op)
2✔
1360

2✔
1361
        // Since the sweeper is not aware of the input, we'll assume the user
2✔
1362
        // is attempting to bump an unconfirmed transaction's fee rate by
2✔
1363
        // sweeping an output within it under control of the wallet with a
2✔
1364
        // higher fee rate. In this case, this will be a CPFP.
2✔
1365
        //
2✔
1366
        // We'll gather all of the information required by the UtxoSweeper in
2✔
1367
        // order to sweep the output.
2✔
1368
        utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
2✔
1369
        if err != nil {
4✔
1370
                return err
2✔
1371
        }
2✔
1372

1373
        // We're only able to bump the fee of unconfirmed transactions.
1374
        if utxo.Confirmations > 0 {
2✔
1375
                return errors.New("unable to bump fee of a confirmed " +
×
1376
                        "transaction")
×
1377
        }
×
1378

1379
        // TODO(ziggie): The budget value should ideally only be set for CPFP
1380
        // requests because for RBF requests we should have already registered
1381
        // the input including the budget value in the first place. However it
1382
        // might not be set and then depending on the deadline delta fee
1383
        // estimations might become too aggressive. So need to evaluate whether
1384
        // we set a default value here, make it configurable or fail request
1385
        // in that case.
1386
        if params.Budget == 0 {
4✔
1387
                params.Budget = utxo.Value.MulF64(
2✔
1388
                        contractcourt.DefaultBudgetRatio,
2✔
1389
                )
2✔
1390

2✔
1391
                log.Warnf("[BumpFee]: setting default budget value of %v for "+
2✔
1392
                        "input=%v, which will be used for the maximum fee "+
2✔
1393
                        "rate estimation (budget was not specified)",
2✔
1394
                        params.Budget, op)
2✔
1395
        }
2✔
1396

1397
        signDesc := &input.SignDescriptor{
2✔
1398
                Output: &wire.TxOut{
2✔
1399
                        PkScript: utxo.PkScript,
2✔
1400
                        Value:    int64(utxo.Value),
2✔
1401
                },
2✔
1402
                HashType: txscript.SigHashAll,
2✔
1403
        }
2✔
1404

2✔
1405
        var witnessType input.WitnessType
2✔
1406
        switch utxo.AddressType {
2✔
1407
        case lnwallet.WitnessPubKey:
×
1408
                witnessType = input.WitnessKeyHash
×
1409
        case lnwallet.NestedWitnessPubKey:
×
1410
                witnessType = input.NestedWitnessKeyHash
×
1411
        case lnwallet.TaprootPubkey:
2✔
1412
                witnessType = input.TaprootPubKeySpend
2✔
1413
                signDesc.HashType = txscript.SigHashDefault
2✔
1414
        default:
×
1415
                return fmt.Errorf("unknown input witness %v", op)
×
1416
        }
1417

1418
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
2✔
1419
                params)
2✔
1420

2✔
1421
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
2✔
1422
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
2✔
1423
                return err
×
1424
        }
×
1425

1426
        return nil
2✔
1427
}
1428

1429
// ListSweeps returns a list of the sweeps that our node has published.
1430
func (w *WalletKit) ListSweeps(ctx context.Context,
1431
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
2✔
1432

2✔
1433
        sweeps, err := w.cfg.Sweeper.ListSweeps()
2✔
1434
        if err != nil {
2✔
1435
                return nil, err
×
1436
        }
×
1437

1438
        sweepTxns := make(map[string]bool)
2✔
1439
        for _, sweep := range sweeps {
4✔
1440
                sweepTxns[sweep.String()] = true
2✔
1441
        }
2✔
1442

1443
        // Some of our sweeps could have been replaced by fee, or dropped out
1444
        // of the mempool. Here, we lookup our wallet transactions so that we
1445
        // can match our list of sweeps against the list of transactions that
1446
        // the wallet is still tracking. Sweeps are currently always swept to
1447
        // the default wallet account.
1448
        txns, firstIdx, lastIdx, err := w.cfg.Wallet.ListTransactionDetails(
2✔
1449
                in.StartHeight, btcwallet.UnconfirmedHeight,
2✔
1450
                lnwallet.DefaultAccountName, 0, 0,
2✔
1451
        )
2✔
1452
        if err != nil {
2✔
1453
                return nil, err
×
1454
        }
×
1455

1456
        var (
2✔
1457
                txids     []string
2✔
1458
                txDetails []*lnwallet.TransactionDetail
2✔
1459
        )
2✔
1460

2✔
1461
        for _, tx := range txns {
4✔
1462
                _, ok := sweepTxns[tx.Hash.String()]
2✔
1463
                if !ok {
4✔
1464
                        continue
2✔
1465
                }
1466

1467
                // Add the txid or full tx details depending on whether we want
1468
                // verbose output or not.
1469
                if in.Verbose {
4✔
1470
                        txDetails = append(txDetails, tx)
2✔
1471
                } else {
4✔
1472
                        txids = append(txids, tx.Hash.String())
2✔
1473
                }
2✔
1474
        }
1475

1476
        if in.Verbose {
4✔
1477
                return &ListSweepsResponse{
2✔
1478
                        Sweeps: &ListSweepsResponse_TransactionDetails{
2✔
1479
                                TransactionDetails: lnrpc.RPCTransactionDetails(
2✔
1480
                                        txDetails, firstIdx, lastIdx,
2✔
1481
                                ),
2✔
1482
                        },
2✔
1483
                }, nil
2✔
1484
        }
2✔
1485

1486
        return &ListSweepsResponse{
2✔
1487
                Sweeps: &ListSweepsResponse_TransactionIds{
2✔
1488
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
2✔
1489
                                TransactionIds: txids,
2✔
1490
                        },
2✔
1491
                },
2✔
1492
        }, nil
2✔
1493
}
1494

1495
// LabelTransaction adds a label to a transaction.
1496
func (w *WalletKit) LabelTransaction(ctx context.Context,
1497
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
2✔
1498

2✔
1499
        // Check that the label provided in non-zero.
2✔
1500
        if len(req.Label) == 0 {
4✔
1501
                return nil, ErrZeroLabel
2✔
1502
        }
2✔
1503

1504
        // Validate the length of the non-zero label. We do not need to use the
1505
        // label returned here, because the original is non-zero so will not
1506
        // be replaced.
1507
        if _, err := labels.ValidateAPI(req.Label); err != nil {
2✔
1508
                return nil, err
×
1509
        }
×
1510

1511
        hash, err := chainhash.NewHash(req.Txid)
2✔
1512
        if err != nil {
2✔
1513
                return nil, err
×
1514
        }
×
1515

1516
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
2✔
1517

2✔
1518
        return &LabelTransactionResponse{
2✔
1519
                Status: fmt.Sprintf("transaction label '%s' added", req.Label),
2✔
1520
        }, err
2✔
1521
}
1522

1523
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
1524
// the outputs specified in the template. There are three ways a user can
1525
// specify what we call the template (a list of inputs and outputs to use in the
1526
// PSBT): Either as a PSBT packet directly with no coin selection (using the
1527
// legacy "psbt" field), a PSBT with advanced coin selection support (using the
1528
// new "coin_select" field) or as a raw RPC message (using the "raw" field).
1529
// The legacy "psbt" and "raw" modes, the following restrictions apply:
1530
//  1. If there are no inputs specified in the template, coin selection is
1531
//     performed automatically.
1532
//  2. If the template does contain any inputs, it is assumed that full coin
1533
//     selection happened externally and no additional inputs are added. If the
1534
//     specified inputs aren't enough to fund the outputs with the given fee
1535
//     rate, an error is returned.
1536
//
1537
// The new "coin_select" mode does not have these restrictions and allows the
1538
// user to specify a PSBT with inputs and outputs and still perform coin
1539
// selection on top of that.
1540
// For all modes this RPC requires any inputs that are specified to be locked by
1541
// the user (if they belong to this node in the first place).
1542
// After either selecting or verifying the inputs, all input UTXOs are locked
1543
// with an internal app ID. A custom address type for change can be specified
1544
// for default accounts and single imported public keys (only P2TR for now).
1545
// Otherwise, P2WPKH will be used by default. No custom address type should be
1546
// provided for custom accounts as we will always generate the change address
1547
// using the coin selection key scope.
1548
//
1549
// NOTE: If this method returns without an error, it is the caller's
1550
// responsibility to either spend the locked UTXOs (by finalizing and then
1551
// publishing the transaction) or to unlock/release the locked UTXOs in case of
1552
// an error on the caller's side.
1553
func (w *WalletKit) FundPsbt(_ context.Context,
1554
        req *FundPsbtRequest) (*FundPsbtResponse, error) {
2✔
1555

2✔
1556
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
2✔
1557
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
2✔
1558
        )
2✔
1559
        if err != nil {
2✔
1560
                return nil, err
×
1561
        }
×
1562

1563
        // Determine the desired transaction fee.
1564
        var feeSatPerKW chainfee.SatPerKWeight
2✔
1565
        switch {
2✔
1566
        // Estimate the fee by the target number of blocks to confirmation.
1567
        case req.GetTargetConf() != 0:
2✔
1568
                targetConf := req.GetTargetConf()
2✔
1569
                if targetConf < 1 {
2✔
1570
                        return nil, fmt.Errorf("confirmation target must be " +
×
1571
                                "greater than 0")
×
1572
                }
×
1573

1574
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
2✔
1575
                        targetConf,
2✔
1576
                )
2✔
1577
                if err != nil {
2✔
1578
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1579
                                err)
×
1580
                }
×
1581

1582
        // Convert the fee to sat/kW from the specified sat/vByte.
1583
        case req.GetSatPerVbyte() != 0:
2✔
1584
                feeSatPerKW = chainfee.SatPerKVByte(
2✔
1585
                        req.GetSatPerVbyte() * 1000,
2✔
1586
                ).FeePerKWeight()
2✔
1587

1588
        case req.GetSatPerKw() != 0:
2✔
1589
                feeSatPerKW = chainfee.SatPerKWeight(req.GetSatPerKw())
2✔
1590

1591
        default:
×
1592
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1593
                        "specify either target_conf, sat_per_vbyte or " +
×
1594
                        "sat_per_kw")
×
1595
        }
1596

1597
        // Then, we'll extract the minimum number of confirmations that each
1598
        // output we use to fund the transaction should satisfy.
1599
        minConfs, err := lnrpc.ExtractMinConfs(
2✔
1600
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
2✔
1601
        )
2✔
1602
        if err != nil {
2✔
1603
                return nil, err
×
1604
        }
×
1605

1606
        // We'll assume the PSBT will be funded by the default account unless
1607
        // otherwise specified.
1608
        account := lnwallet.DefaultAccountName
2✔
1609
        if req.Account != "" {
4✔
1610
                account = req.Account
2✔
1611
        }
2✔
1612

1613
        var customLockID *wtxmgr.LockID
2✔
1614
        if len(req.CustomLockId) > 0 {
4✔
1615
                lockID := wtxmgr.LockID{}
2✔
1616
                if len(req.CustomLockId) != len(lockID) {
2✔
1617
                        return nil, fmt.Errorf("custom lock ID must be " +
×
1618
                                "exactly 32 bytes")
×
1619
                }
×
1620

1621
                copy(lockID[:], req.CustomLockId)
2✔
1622
                customLockID = &lockID
2✔
1623
        }
1624

1625
        var customLockDuration time.Duration
2✔
1626
        if req.LockExpirationSeconds != 0 {
4✔
1627
                customLockDuration = time.Duration(req.LockExpirationSeconds) *
2✔
1628
                        time.Second
2✔
1629
        }
2✔
1630

1631
        // There are three ways a user can specify what we call the template (a
1632
        // list of inputs and outputs to use in the PSBT): Either as a PSBT
1633
        // packet directly with no coin selection, a PSBT with coin selection or
1634
        // as a special RPC message. Find out which one the user wants to use,
1635
        // they are mutually exclusive.
1636
        switch {
2✔
1637
        // The template is specified as a PSBT. All we have to do is parse it.
1638
        case req.GetPsbt() != nil:
2✔
1639
                r := bytes.NewReader(req.GetPsbt())
2✔
1640
                packet, err := psbt.NewFromRawBytes(r, false)
2✔
1641
                if err != nil {
2✔
1642
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1643
                }
×
1644

1645
                // Run the actual funding process now, using the internal
1646
                // wallet.
1647
                return w.fundPsbtInternalWallet(
2✔
1648
                        account, keyScopeFromChangeAddressType(req.ChangeType),
2✔
1649
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
2✔
1650
                        customLockID, customLockDuration,
2✔
1651
                )
2✔
1652

1653
        // The template is specified as a PSBT with the intention to perform
1654
        // coin selection even if inputs are already present.
1655
        case req.GetCoinSelect() != nil:
2✔
1656
                coinSelectRequest := req.GetCoinSelect()
2✔
1657
                r := bytes.NewReader(coinSelectRequest.Psbt)
2✔
1658
                packet, err := psbt.NewFromRawBytes(r, false)
2✔
1659
                if err != nil {
2✔
1660
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1661
                }
×
1662

1663
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
2✔
1664
                if numOutputs == 0 {
2✔
1665
                        return nil, fmt.Errorf("no outputs specified in " +
×
1666
                                "template")
×
1667
                }
×
1668

1669
                outputSum := int64(0)
2✔
1670
                for _, txOut := range packet.UnsignedTx.TxOut {
4✔
1671
                        outputSum += txOut.Value
2✔
1672
                }
2✔
1673
                if outputSum <= 0 {
2✔
1674
                        return nil, fmt.Errorf("output sum must be positive")
×
1675
                }
×
1676

1677
                var (
2✔
1678
                        changeIndex int32 = -1
2✔
1679
                        changeType  chanfunding.ChangeAddressType
2✔
1680
                )
2✔
1681
                switch t := coinSelectRequest.ChangeOutput.(type) {
2✔
1682
                // The user wants to use an existing output as change output.
1683
                case *PsbtCoinSelect_ExistingOutputIndex:
2✔
1684
                        if t.ExistingOutputIndex < 0 ||
2✔
1685
                                t.ExistingOutputIndex >= numOutputs {
2✔
1686

×
1687
                                return nil, fmt.Errorf("change output index "+
×
1688
                                        "out of range: %d",
×
1689
                                        t.ExistingOutputIndex)
×
1690
                        }
×
1691

1692
                        changeIndex = t.ExistingOutputIndex
2✔
1693

2✔
1694
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
1695
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
2✔
1696
                        if err != nil {
2✔
1697
                                return nil, fmt.Errorf("error parsing change "+
×
1698
                                        "script: %w", err)
×
1699
                        }
×
1700

1701
                        changeType = chanfunding.ExistingChangeAddress
2✔
1702

1703
                // The user wants to use a new output as change output.
1704
                case *PsbtCoinSelect_Add:
×
1705
                        // We already set the change index to -1 above to
×
1706
                        // indicate no change output should be used if possible
×
1707
                        // or a new one should be created if needed. So we only
×
1708
                        // need to parse the type of change output we want to
×
1709
                        // create.
×
1710
                        switch req.ChangeType {
×
1711
                        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
1712
                                changeType = chanfunding.P2TRChangeAddress
×
1713

1714
                        default:
×
1715
                                changeType = chanfunding.P2WKHChangeAddress
×
1716
                        }
1717

1718
                default:
×
1719
                        return nil, fmt.Errorf("unknown change output type")
×
1720
                }
1721

1722
                maxFeeRatio := chanfunding.DefaultMaxFeeRatio
2✔
1723

2✔
1724
                if req.MaxFeeRatio != 0 {
2✔
1725
                        maxFeeRatio = req.MaxFeeRatio
×
1726
                }
×
1727

1728
                // Run the actual funding process now, using the channel funding
1729
                // coin selection algorithm.
1730
                return w.fundPsbtCoinSelect(
2✔
1731
                        account, changeIndex, packet, minConfs, changeType,
2✔
1732
                        feeSatPerKW, coinSelectionStrategy, maxFeeRatio,
2✔
1733
                        customLockID, customLockDuration,
2✔
1734
                )
2✔
1735

1736
        // The template is specified as a RPC message. We need to create a new
1737
        // PSBT and copy the RPC information over.
1738
        case req.GetRaw() != nil:
2✔
1739
                tpl := req.GetRaw()
2✔
1740

2✔
1741
                txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
2✔
1742
                for addrStr, amt := range tpl.Outputs {
4✔
1743
                        addr, err := btcutil.DecodeAddress(
2✔
1744
                                addrStr, w.cfg.ChainParams,
2✔
1745
                        )
2✔
1746
                        if err != nil {
2✔
1747
                                return nil, fmt.Errorf("error parsing address "+
×
1748
                                        "%s for network %s: %v", addrStr,
×
1749
                                        w.cfg.ChainParams.Name, err)
×
1750
                        }
×
1751

1752
                        if !addr.IsForNet(w.cfg.ChainParams) {
2✔
1753
                                return nil, fmt.Errorf("address is not for %s",
×
1754
                                        w.cfg.ChainParams.Name)
×
1755
                        }
×
1756

1757
                        pkScript, err := txscript.PayToAddrScript(addr)
2✔
1758
                        if err != nil {
2✔
1759
                                return nil, fmt.Errorf("error getting pk "+
×
1760
                                        "script for address %s: %w", addrStr,
×
1761
                                        err)
×
1762
                        }
×
1763

1764
                        txOut = append(txOut, &wire.TxOut{
2✔
1765
                                Value:    int64(amt),
2✔
1766
                                PkScript: pkScript,
2✔
1767
                        })
2✔
1768
                }
1769

1770
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
2✔
1771
                for idx, in := range tpl.Inputs {
2✔
1772
                        op, err := UnmarshallOutPoint(in)
×
1773
                        if err != nil {
×
1774
                                return nil, fmt.Errorf("error parsing "+
×
1775
                                        "outpoint: %w", err)
×
1776
                        }
×
1777
                        txIn[idx] = op
×
1778
                }
1779

1780
                sequences := make([]uint32, len(txIn))
2✔
1781
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
2✔
1782
                if err != nil {
2✔
1783
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1784
                }
×
1785

1786
                // Run the actual funding process now, using the internal
1787
                // wallet.
1788
                return w.fundPsbtInternalWallet(
2✔
1789
                        account, keyScopeFromChangeAddressType(req.ChangeType),
2✔
1790
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
2✔
1791
                        customLockID, customLockDuration,
2✔
1792
                )
2✔
1793

1794
        default:
×
1795
                return nil, fmt.Errorf("transaction template missing, need " +
×
1796
                        "to specify either PSBT or raw TX template")
×
1797
        }
1798
}
1799

1800
// fundPsbtInternalWallet uses the "old" PSBT funding method of the internal
1801
// wallet that does not allow specifying custom inputs while selecting coins.
1802
func (w *WalletKit) fundPsbtInternalWallet(account string,
1803
        keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
1804
        feeSatPerKW chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy,
1805
        customLockID *wtxmgr.LockID, customLockDuration time.Duration) (
1806
        *FundPsbtResponse, error) {
2✔
1807

2✔
1808
        // The RPC parsing part is now over. Several of the following operations
2✔
1809
        // require us to hold the global coin selection lock, so we do the rest
2✔
1810
        // of the tasks while holding the lock. The result is a list of locked
2✔
1811
        // UTXOs.
2✔
1812
        var response *FundPsbtResponse
2✔
1813
        err := w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
1814
                // In case the user did specify inputs, we need to make sure
2✔
1815
                // they are known to us, still unspent and not yet locked.
2✔
1816
                if len(packet.UnsignedTx.TxIn) > 0 {
4✔
1817
                        // Get a list of all unspent witness outputs.
2✔
1818
                        utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
1819
                                minConfs, defaultMaxConf, account,
2✔
1820
                        )
2✔
1821
                        if err != nil {
2✔
1822
                                return err
×
1823
                        }
×
1824

1825
                        // filterFn makes sure utxos which are unconfirmed and
1826
                        // still used by the sweeper are not used.
1827
                        filterFn := func(u *lnwallet.Utxo) bool {
4✔
1828
                                // Confirmed utxos are always allowed.
2✔
1829
                                if u.Confirmations > 0 {
4✔
1830
                                        return true
2✔
1831
                                }
2✔
1832

1833
                                // Unconfirmed utxos in use by the sweeper are
1834
                                // not stable to use because they can be
1835
                                // replaced.
1836
                                if w.cfg.Sweeper.IsSweeperOutpoint(u.OutPoint) {
4✔
1837
                                        log.Warnf("Cannot use unconfirmed "+
2✔
1838
                                                "utxo=%v because it is "+
2✔
1839
                                                "unstable and could be "+
2✔
1840
                                                "replaced", u.OutPoint)
2✔
1841

2✔
1842
                                        return false
2✔
1843
                                }
2✔
1844

1845
                                return true
×
1846
                        }
1847

1848
                        eligibleUtxos := fn.Filter(utxos, filterFn)
2✔
1849

2✔
1850
                        // Validate all inputs against our known list of UTXOs
2✔
1851
                        // now.
2✔
1852
                        err = verifyInputsUnspent(
2✔
1853
                                packet.UnsignedTx.TxIn, eligibleUtxos,
2✔
1854
                        )
2✔
1855
                        if err != nil {
4✔
1856
                                return err
2✔
1857
                        }
2✔
1858
                }
1859

1860
                // currentHeight is needed to determine whether the internal
1861
                // wallet utxo is still unconfirmed.
1862
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
2✔
1863
                if err != nil {
2✔
1864
                        return fmt.Errorf("unable to retrieve current "+
×
1865
                                "height: %v", err)
×
1866
                }
×
1867

1868
                // restrictUnstableUtxos is a filter function which disallows
1869
                // the usage of unconfirmed outputs published (still in use) by
1870
                // the sweeper.
1871
                restrictUnstableUtxos := func(utxo wtxmgr.Credit) bool {
4✔
1872
                        // Wallet utxos which are unmined have a height
2✔
1873
                        // of -1.
2✔
1874
                        if utxo.Height != -1 && utxo.Height <= currentHeight {
4✔
1875
                                // Confirmed utxos are always allowed.
2✔
1876
                                return true
2✔
1877
                        }
2✔
1878

1879
                        // Utxos used by the sweeper are not used for
1880
                        // channel openings.
1881
                        allowed := !w.cfg.Sweeper.IsSweeperOutpoint(
2✔
1882
                                utxo.OutPoint,
2✔
1883
                        )
2✔
1884
                        if !allowed {
4✔
1885
                                log.Warnf("Cannot use unconfirmed "+
2✔
1886
                                        "utxo=%v because it is "+
2✔
1887
                                        "unstable and could be "+
2✔
1888
                                        "replaced", utxo.OutPoint)
2✔
1889
                        }
2✔
1890

1891
                        return allowed
2✔
1892
                }
1893

1894
                // We made sure the input from the user is as sane as possible.
1895
                // We can now ask the wallet to fund the TX. This will not yet
1896
                // lock any coins but might still change the wallet DB by
1897
                // generating a new change address.
1898
                changeIndex, err := w.cfg.Wallet.FundPsbt(
2✔
1899
                        packet, minConfs, feeSatPerKW, account, keyScope,
2✔
1900
                        strategy, restrictUnstableUtxos,
2✔
1901
                )
2✔
1902
                if err != nil {
4✔
1903
                        return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
2✔
1904
                }
2✔
1905

1906
                // Now we have obtained a set of coins that can be used to fund
1907
                // the TX. Let's lock them to be sure they aren't spent by the
1908
                // time the PSBT is published. This is the action we do here
1909
                // that could cause an error. Therefore, if some of the UTXOs
1910
                // cannot be locked, the rollback of the other's locks also
1911
                // happens in this function. If we ever need to do more after
1912
                // this function, we need to extract the rollback needs to be
1913
                // extracted into a defer.
1914
                outpoints := make([]wire.OutPoint, len(packet.UnsignedTx.TxIn))
2✔
1915
                for i, txIn := range packet.UnsignedTx.TxIn {
4✔
1916
                        outpoints[i] = txIn.PreviousOutPoint
2✔
1917
                }
2✔
1918

1919
                response, err = w.lockAndCreateFundingResponse(
2✔
1920
                        packet, outpoints, changeIndex, customLockID,
2✔
1921
                        customLockDuration,
2✔
1922
                )
2✔
1923

2✔
1924
                return err
2✔
1925
        })
1926
        if err != nil {
4✔
1927
                return nil, err
2✔
1928
        }
2✔
1929

1930
        return response, nil
2✔
1931
}
1932

1933
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
1934
// funding coin selection algorithm that allows specifying custom inputs while
1935
// selecting coins.
1936
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
1937
        packet *psbt.Packet, minConfs int32,
1938
        changeType chanfunding.ChangeAddressType,
1939
        feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy,
1940
        maxFeeRatio float64, customLockID *wtxmgr.LockID,
1941
        customLockDuration time.Duration) (*FundPsbtResponse, error) {
2✔
1942

2✔
1943
        // We want to make sure we don't select any inputs that are already
2✔
1944
        // specified in the template. To do that, we require those inputs to
2✔
1945
        // either not belong to this lnd at all or to be already locked through
2✔
1946
        // a manual lock call by the user. Either way, they should not appear in
2✔
1947
        // the list of unspent outputs.
2✔
1948
        err := w.assertNotAvailable(packet.UnsignedTx.TxIn, minConfs, account)
2✔
1949
        if err != nil {
2✔
1950
                return nil, err
×
1951
        }
×
1952

1953
        // In case the user just specified the input outpoints of UTXOs we own,
1954
        // the fee estimation below will error out because the UTXO information
1955
        // is missing. We need to fetch the UTXO information from the wallet
1956
        // and add it to the PSBT. We ignore inputs we don't actually know as
1957
        // they could belong to another wallet.
1958
        err = w.cfg.Wallet.DecorateInputs(packet, false)
2✔
1959
        if err != nil {
2✔
1960
                return nil, fmt.Errorf("error decorating inputs: %w", err)
×
1961
        }
×
1962

1963
        // Before we select anything, we need to calculate the input, output and
1964
        // current weight amounts. While doing that we also ensure the PSBT has
1965
        // all the required information we require at this step.
1966
        var (
2✔
1967
                inputSum, outputSum btcutil.Amount
2✔
1968
                estimator           input.TxWeightEstimator
2✔
1969
        )
2✔
1970
        for i := range packet.Inputs {
4✔
1971
                in := packet.Inputs[i]
2✔
1972

2✔
1973
                err := btcwallet.EstimateInputWeight(&in, &estimator)
2✔
1974
                if err != nil {
2✔
1975
                        return nil, fmt.Errorf("error estimating input "+
×
1976
                                "weight: %w", err)
×
1977
                }
×
1978

1979
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
2✔
1980
        }
1981
        for i := range packet.UnsignedTx.TxOut {
4✔
1982
                out := packet.UnsignedTx.TxOut[i]
2✔
1983

2✔
1984
                estimator.AddOutput(out.PkScript)
2✔
1985
                outputSum += btcutil.Amount(out.Value)
2✔
1986
        }
2✔
1987

1988
        // The amount we want to fund is the total output sum plus the current
1989
        // fee estimate, minus the sum of any already specified inputs. Since we
1990
        // pass the estimator of the current transaction into the coin selection
1991
        // algorithm, we don't need to subtract the fees here.
1992
        fundingAmount := outputSum - inputSum
2✔
1993

2✔
1994
        var changeDustLimit btcutil.Amount
2✔
1995
        switch changeType {
2✔
1996
        case chanfunding.P2TRChangeAddress:
×
1997
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
×
1998

1999
        case chanfunding.P2WKHChangeAddress:
×
2000
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
×
2001

2002
        case chanfunding.ExistingChangeAddress:
2✔
2003
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
2004
                changeDustLimit = lnwallet.DustLimitForSize(
2✔
2005
                        len(changeOut.PkScript),
2✔
2006
                )
2✔
2007
        }
2008

2009
        // Do we already have enough inputs specified to pay for the TX as it
2010
        // is? In that case we only need to allocate any change, if there is
2011
        // any.
2012
        packetFeeNoChange := feeRate.FeeForWeight(estimator.Weight())
2✔
2013
        if inputSum >= outputSum+packetFeeNoChange {
4✔
2014
                // Calculate the packet's fee with a change output so, so we can
2✔
2015
                // let the coin selection algorithm decide whether to use a
2✔
2016
                // change output or not.
2✔
2017
                switch changeType {
2✔
2018
                case chanfunding.P2TRChangeAddress:
×
2019
                        estimator.AddP2TROutput()
×
2020

2021
                case chanfunding.P2WKHChangeAddress:
×
2022
                        estimator.AddP2WKHOutput()
×
2023
                }
2024
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
2✔
2025

2✔
2026
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
2✔
2027
                        inputSum, outputSum, packetFeeNoChange,
2✔
2028
                        packetFeeWithChange, changeDustLimit, changeType,
2✔
2029
                        maxFeeRatio,
2✔
2030
                )
2✔
2031
                if err != nil {
2✔
2032
                        return nil, fmt.Errorf("error calculating change "+
×
2033
                                "amount: %w", err)
×
2034
                }
×
2035

2036
                // We shouldn't get into this branch if the input sum isn't
2037
                // enough to pay for the current package without a change
2038
                // output. So this should never be non-zero.
2039
                if needMore != 0 {
2✔
2040
                        return nil, fmt.Errorf("internal error with change " +
×
2041
                                "amount calculation")
×
2042
                }
×
2043

2044
                if changeAmt > 0 {
4✔
2045
                        changeIndex, err = w.handleChange(
2✔
2046
                                packet, changeIndex, int64(changeAmt),
2✔
2047
                                changeType, account,
2✔
2048
                        )
2✔
2049
                        if err != nil {
2✔
2050
                                return nil, fmt.Errorf("error handling change "+
×
2051
                                        "amount: %w", err)
×
2052
                        }
×
2053
                }
2054

2055
                // We're done. Let's serialize and return the updated package.
2056
                return w.lockAndCreateFundingResponse(
2✔
2057
                        packet, nil, changeIndex, customLockID,
2✔
2058
                        customLockDuration,
2✔
2059
                )
2✔
2060
        }
2061

2062
        // The RPC parsing part is now over. Several of the following operations
2063
        // require us to hold the global coin selection lock, so we do the rest
2064
        // of the tasks while holding the lock. The result is a list of locked
2065
        // UTXOs.
2066
        var response *FundPsbtResponse
2✔
2067
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
2068
                // Get a list of all unspent witness outputs.
2✔
2069
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
2070
                        minConfs, defaultMaxConf, account,
2✔
2071
                )
2✔
2072
                if err != nil {
2✔
2073
                        return err
×
2074
                }
×
2075

2076
                coins := make([]base.Coin, len(utxos))
2✔
2077
                for i, utxo := range utxos {
4✔
2078
                        coins[i] = base.Coin{
2✔
2079
                                TxOut: wire.TxOut{
2✔
2080
                                        Value:    int64(utxo.Value),
2✔
2081
                                        PkScript: utxo.PkScript,
2✔
2082
                                },
2✔
2083
                                OutPoint: utxo.OutPoint,
2✔
2084
                        }
2✔
2085
                }
2✔
2086

2087
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
2✔
2088
                        feeRate, fundingAmount, changeDustLimit, coins,
2✔
2089
                        strategy, estimator, changeType, maxFeeRatio,
2✔
2090
                )
2✔
2091
                if err != nil {
2✔
2092
                        return fmt.Errorf("error selecting coins: %w", err)
×
2093
                }
×
2094

2095
                if changeAmount > 0 {
4✔
2096
                        changeIndex, err = w.handleChange(
2✔
2097
                                packet, changeIndex, int64(changeAmount),
2✔
2098
                                changeType, account,
2✔
2099
                        )
2✔
2100
                        if err != nil {
2✔
2101
                                return fmt.Errorf("error handling change "+
×
2102
                                        "amount: %w", err)
×
2103
                        }
×
2104
                }
2105

2106
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
2✔
2107
                for i := range selectedCoins {
4✔
2108
                        coin := selectedCoins[i]
2✔
2109
                        addedOutpoints[i] = coin.OutPoint
2✔
2110

2✔
2111
                        packet.UnsignedTx.TxIn = append(
2✔
2112
                                packet.UnsignedTx.TxIn, &wire.TxIn{
2✔
2113
                                        PreviousOutPoint: coin.OutPoint,
2✔
2114
                                },
2✔
2115
                        )
2✔
2116
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
2✔
2117
                                WitnessUtxo: &coin.TxOut,
2✔
2118
                        })
2✔
2119
                }
2✔
2120

2121
                // Now that we've added the bare TX inputs, we also need to add
2122
                // the more verbose input information to the packet, so a future
2123
                // signer doesn't need to do any lookups. We skip any inputs
2124
                // that our wallet doesn't own.
2125
                err = w.cfg.Wallet.DecorateInputs(packet, false)
2✔
2126
                if err != nil {
2✔
2127
                        return fmt.Errorf("error decorating inputs: %w", err)
×
2128
                }
×
2129

2130
                response, err = w.lockAndCreateFundingResponse(
2✔
2131
                        packet, addedOutpoints, changeIndex, customLockID,
2✔
2132
                        customLockDuration,
2✔
2133
                )
2✔
2134

2✔
2135
                return err
2✔
2136
        })
2137
        if err != nil {
2✔
2138
                return nil, err
×
2139
        }
×
2140

2141
        return response, nil
2✔
2142
}
2143

2144
// assertNotAvailable makes sure the specified inputs either don't belong to
2145
// this node or are already locked by the user.
2146
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2147
        account string) error {
2✔
2148

2✔
2149
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
4✔
2150
                // Get a list of all unspent witness outputs.
2✔
2151
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
2✔
2152
                        minConfs, defaultMaxConf, account,
2✔
2153
                )
2✔
2154
                if err != nil {
2✔
2155
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2156
                }
×
2157

2158
                // We'll now check that none of the inputs specified in the
2159
                // template are available to us. That means they either don't
2160
                // belong to us or are already locked by the user.
2161
                for _, txIn := range inputs {
4✔
2162
                        for _, utxo := range utxos {
4✔
2163
                                if txIn.PreviousOutPoint == utxo.OutPoint {
2✔
2164
                                        return fmt.Errorf("input %v is not "+
×
2165
                                                "locked", txIn.PreviousOutPoint)
×
2166
                                }
×
2167
                        }
2168
                }
2169

2170
                return nil
2✔
2171
        })
2172
}
2173

2174
// lockAndCreateFundingResponse locks the given outpoints and creates a funding
2175
// response with the serialized PSBT, the change index and the locked UTXOs.
2176
func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet,
2177
        newOutpoints []wire.OutPoint, changeIndex int32,
2178
        customLockID *wtxmgr.LockID, customLockDuration time.Duration) (
2179
        *FundPsbtResponse, error) {
2✔
2180

2✔
2181
        // Make sure we can properly serialize the packet. If this goes wrong
2✔
2182
        // then something isn't right with the inputs, and we probably shouldn't
2✔
2183
        // try to lock any of them.
2✔
2184
        var buf bytes.Buffer
2✔
2185
        err := packet.Serialize(&buf)
2✔
2186
        if err != nil {
2✔
2187
                return nil, fmt.Errorf("error serializing funded PSBT: %w", err)
×
2188
        }
×
2189

2190
        locks, err := lockInputs(
2✔
2191
                w.cfg.Wallet, newOutpoints, customLockID, customLockDuration,
2✔
2192
        )
2✔
2193
        if err != nil {
2✔
2194
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2195
        }
×
2196

2197
        // Convert the lock leases to the RPC format.
2198
        rpcLocks := marshallLeases(locks)
2✔
2199

2✔
2200
        return &FundPsbtResponse{
2✔
2201
                FundedPsbt:        buf.Bytes(),
2✔
2202
                ChangeOutputIndex: changeIndex,
2✔
2203
                LockedUtxos:       rpcLocks,
2✔
2204
        }, nil
2✔
2205
}
2206

2207
// handleChange is a closure that either adds the non-zero change amount to an
2208
// existing output or creates a change output. The function returns the new
2209
// change output index if a new change output was added.
2210
func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
2211
        changeAmount int64, changeType chanfunding.ChangeAddressType,
2212
        changeAccount string) (int32, error) {
2✔
2213

2✔
2214
        // Does an existing output get the change?
2✔
2215
        if changeIndex >= 0 {
4✔
2216
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
2✔
2217
                changeOut.Value += changeAmount
2✔
2218

2✔
2219
                return changeIndex, nil
2✔
2220
        }
2✔
2221

2222
        // The user requested a new change output.
2223
        addrType := addrTypeFromChangeAddressType(changeType)
×
2224
        changeAddr, err := w.cfg.Wallet.NewAddress(
×
2225
                addrType, true, changeAccount,
×
2226
        )
×
2227
        if err != nil {
×
2228
                return 0, fmt.Errorf("could not derive change address: %w", err)
×
2229
        }
×
2230

2231
        changeScript, err := txscript.PayToAddrScript(changeAddr)
×
2232
        if err != nil {
×
2233
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2234
        }
×
2235

2236
        // We need to add the derivation info for the change address in case it
2237
        // is a P2TR address. This is mostly to prove it's a bare BIP-0086
2238
        // address, which is required for some protocols (such as Taproot
2239
        // Assets).
2240
        pOut := psbt.POutput{}
×
2241
        _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
×
2242
        if isTaprootChangeAddr {
×
2243
                changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
×
2244
                if err != nil {
×
2245
                        return 0, fmt.Errorf("could not get address info: %w",
×
2246
                                err)
×
2247
                }
×
2248

2249
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2250
                        changeAddrInfo,
×
2251
                )
×
2252
                if err != nil {
×
2253
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2254
                                "%w", err)
×
2255
                }
×
2256

2257
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2258
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2259
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2260
                        trDeriv,
×
2261
                }
×
2262
        }
2263

2264
        newChangeIndex := int32(len(packet.Outputs))
×
2265
        packet.UnsignedTx.TxOut = append(
×
2266
                packet.UnsignedTx.TxOut, &wire.TxOut{
×
2267
                        Value:    changeAmount,
×
2268
                        PkScript: changeScript,
×
2269
                },
×
2270
        )
×
2271
        packet.Outputs = append(packet.Outputs, pOut)
×
2272

×
2273
        return newChangeIndex, nil
×
2274
}
2275

2276
// marshallLeases converts the lock leases to the RPC format.
2277
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
2✔
2278
        rpcLocks := make([]*UtxoLease, len(locks))
2✔
2279
        for idx, lock := range locks {
4✔
2280
                lock := lock
2✔
2281

2✔
2282
                rpcLocks[idx] = &UtxoLease{
2✔
2283
                        Id:         lock.LockID[:],
2✔
2284
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
2✔
2285
                        Expiration: uint64(lock.Expiration.Unix()),
2✔
2286
                        PkScript:   lock.PkScript,
2✔
2287
                        Value:      uint64(lock.Value),
2✔
2288
                }
2✔
2289
        }
2✔
2290

2291
        return rpcLocks
2✔
2292
}
2293

2294
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2295
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2296
// it returns nil.
2297
func keyScopeFromChangeAddressType(
2298
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
2✔
2299

2✔
2300
        switch changeAddressType {
2✔
2301
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
2✔
2302
                return &waddrmgr.KeyScopeBIP0086
2✔
2303

2304
        default:
2✔
2305
                return nil
2✔
2306
        }
2307
}
2308

2309
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2310
// lnwallet.AddressType.
2311
func addrTypeFromChangeAddressType(
2312
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
×
2313

×
2314
        switch changeAddressType {
×
2315
        case chanfunding.P2TRChangeAddress:
×
2316
                return lnwallet.TaprootPubkey
×
2317

2318
        default:
×
2319
                return lnwallet.WitnessPubKey
×
2320
        }
2321
}
2322

2323
// SignPsbt expects a partial transaction with all inputs and outputs fully
2324
// declared and tries to sign all unsigned inputs that have all required fields
2325
// (UTXO information, BIP32 derivation information, witness or sig scripts)
2326
// set.
2327
// If no error is returned, the PSBT is ready to be given to the next signer or
2328
// to be finalized if lnd was the last signer.
2329
//
2330
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
2331
// perform any other tasks (such as coin selection, UTXO locking or
2332
// input/output/fee value validation, PSBT finalization). Any input that is
2333
// incomplete will be skipped.
2334
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
2335
        *SignPsbtResponse, error) {
2✔
2336

2✔
2337
        packet, err := psbt.NewFromRawBytes(
2✔
2338
                bytes.NewReader(req.FundedPsbt), false,
2✔
2339
        )
2✔
2340
        if err != nil {
2✔
2341
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2342
                        req.FundedPsbt)
×
2343
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2344
        }
×
2345

2346
        // Before we attempt to sign the packet, ensure that every input either
2347
        // has a witness UTXO, or a non witness UTXO.
2348
        for idx := range packet.UnsignedTx.TxIn {
4✔
2349
                in := packet.Inputs[idx]
2✔
2350

2✔
2351
                // Doesn't have either a witness or non witness UTXO so we need
2✔
2352
                // to exit here as otherwise signing will fail.
2✔
2353
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
4✔
2354
                        return nil, fmt.Errorf("input (index=%v) doesn't "+
2✔
2355
                                "specify any UTXO info", idx)
2✔
2356
                }
2✔
2357
        }
2358

2359
        // Let the wallet do the heavy lifting. This will sign all inputs that
2360
        // we have the UTXO for. If some inputs can't be signed and don't have
2361
        // witness data attached, they will just be skipped.
2362
        signedInputs, err := w.cfg.Wallet.SignPsbt(packet)
2✔
2363
        if err != nil {
2✔
2364
                return nil, fmt.Errorf("error signing PSBT: %w", err)
×
2365
        }
×
2366

2367
        // Serialize the signed PSBT in both the packet and wire format.
2368
        var signedPsbtBytes bytes.Buffer
2✔
2369
        err = packet.Serialize(&signedPsbtBytes)
2✔
2370
        if err != nil {
2✔
2371
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2372
        }
×
2373

2374
        return &SignPsbtResponse{
2✔
2375
                SignedPsbt:   signedPsbtBytes.Bytes(),
2✔
2376
                SignedInputs: signedInputs,
2✔
2377
        }, nil
2✔
2378
}
2379

2380
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
2381
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
2382
// the last signer of the transaction. That means, if there are any unsigned
2383
// non-witness inputs or inputs without UTXO information attached or inputs
2384
// without witness data that do not belong to lnd's wallet, this method will
2385
// fail. If no error is returned, the PSBT is ready to be extracted and the
2386
// final TX within to be broadcast.
2387
//
2388
// NOTE: This method does NOT publish the transaction once finalized. It is the
2389
// caller's responsibility to either publish the transaction on success or
2390
// unlock/release any locked UTXOs in case of an error in this method.
2391
func (w *WalletKit) FinalizePsbt(_ context.Context,
2392
        req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
2✔
2393

2✔
2394
        // We'll assume the PSBT was funded by the default account unless
2✔
2395
        // otherwise specified.
2✔
2396
        account := lnwallet.DefaultAccountName
2✔
2397
        if req.Account != "" {
2✔
2398
                account = req.Account
×
2399
        }
×
2400

2401
        // Parse the funded PSBT.
2402
        packet, err := psbt.NewFromRawBytes(
2✔
2403
                bytes.NewReader(req.FundedPsbt), false,
2✔
2404
        )
2✔
2405
        if err != nil {
2✔
2406
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2407
        }
×
2408

2409
        // The only check done at this level is to validate that the PSBT is
2410
        // not complete. The wallet performs all other checks.
2411
        if packet.IsComplete() {
2✔
2412
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2413
        }
×
2414

2415
        // Let the wallet do the heavy lifting. This will sign all inputs that
2416
        // we have the UTXO for. If some inputs can't be signed and don't have
2417
        // witness data attached, this will fail.
2418
        err = w.cfg.Wallet.FinalizePsbt(packet, account)
2✔
2419
        if err != nil {
2✔
2420
                return nil, fmt.Errorf("error finalizing PSBT: %w", err)
×
2421
        }
×
2422

2423
        var (
2✔
2424
                finalPsbtBytes bytes.Buffer
2✔
2425
                finalTxBytes   bytes.Buffer
2✔
2426
        )
2✔
2427

2✔
2428
        // Serialize the finalized PSBT in both the packet and wire format.
2✔
2429
        err = packet.Serialize(&finalPsbtBytes)
2✔
2430
        if err != nil {
2✔
2431
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2432
        }
×
2433
        finalTx, err := psbt.Extract(packet)
2✔
2434
        if err != nil {
2✔
2435
                return nil, fmt.Errorf("unable to extract final TX: %w", err)
×
2436
        }
×
2437
        err = finalTx.Serialize(&finalTxBytes)
2✔
2438
        if err != nil {
2✔
2439
                return nil, fmt.Errorf("error serializing final TX: %w", err)
×
2440
        }
×
2441

2442
        return &FinalizePsbtResponse{
2✔
2443
                SignedPsbt: finalPsbtBytes.Bytes(),
2✔
2444
                RawFinalTx: finalTxBytes.Bytes(),
2✔
2445
        }, nil
2✔
2446
}
2447

2448
// marshalWalletAccount converts the properties of an account into its RPC
2449
// representation.
2450
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2451
        account *waddrmgr.AccountProperties) (*Account, error) {
2✔
2452

2✔
2453
        var addrType AddressType
2✔
2454
        switch account.KeyScope {
2✔
2455
        case waddrmgr.KeyScopeBIP0049Plus:
2✔
2456
                // No address schema present represents the traditional BIP-0049
2✔
2457
                // address derivation scheme.
2✔
2458
                if account.AddrSchema == nil {
4✔
2459
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
2✔
2460
                        break
2✔
2461
                }
2462

2463
                switch *account.AddrSchema {
2✔
2464
                case waddrmgr.KeyScopeBIP0049AddrSchema:
2✔
2465
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
2✔
2466

2467
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
2✔
2468
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
2✔
2469

2470
                default:
×
2471
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2472
                                *account.AddrSchema)
×
2473
                }
2474

2475
        case waddrmgr.KeyScopeBIP0084:
2✔
2476
                addrType = AddressType_WITNESS_PUBKEY_HASH
2✔
2477

2478
        case waddrmgr.KeyScopeBIP0086:
2✔
2479
                addrType = AddressType_TAPROOT_PUBKEY
2✔
2480

2481
        case internalScope:
2✔
2482
                addrType = AddressType_WITNESS_PUBKEY_HASH
2✔
2483

2484
        default:
×
2485
                return nil, fmt.Errorf("account %v has unsupported "+
×
2486
                        "key scope %v", account.AccountName, account.KeyScope)
×
2487
        }
2488

2489
        rpcAccount := &Account{
2✔
2490
                Name:             account.AccountName,
2✔
2491
                AddressType:      addrType,
2✔
2492
                ExternalKeyCount: account.ExternalKeyCount,
2✔
2493
                InternalKeyCount: account.InternalKeyCount,
2✔
2494
                WatchOnly:        account.IsWatchOnly,
2✔
2495
        }
2✔
2496

2✔
2497
        // The remaining fields can only be done on accounts other than the
2✔
2498
        // default imported one existing within each key scope.
2✔
2499
        if account.AccountName != waddrmgr.ImportedAddrAccountName {
4✔
2500
                nonHardenedIndex := account.AccountPubKey.ChildIndex() -
2✔
2501
                        hdkeychain.HardenedKeyStart
2✔
2502
                rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
2✔
2503
                if account.MasterKeyFingerprint != 0 {
2✔
2504
                        var mkfp [4]byte
×
2505
                        binary.BigEndian.PutUint32(
×
2506
                                mkfp[:], account.MasterKeyFingerprint,
×
2507
                        )
×
2508
                        rpcAccount.MasterKeyFingerprint = mkfp[:]
×
2509
                }
×
2510
                rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
2✔
2511
                        account.KeyScope, nonHardenedIndex)
2✔
2512
        }
2513

2514
        return rpcAccount, nil
2✔
2515
}
2516

2517
// marshalWalletAddressList converts the list of address into its RPC
2518
// representation.
2519
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2520
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
2✔
2521

2✔
2522
        // Get the RPC representation of account.
2✔
2523
        rpcAccount, err := marshalWalletAccount(
2✔
2524
                w.internalScope(), account,
2✔
2525
        )
2✔
2526
        if err != nil {
2✔
2527
                return nil, err
×
2528
        }
×
2529

2530
        addresses := make([]*AddressProperty, len(addressList))
2✔
2531
        for idx, addr := range addressList {
4✔
2532
                var pubKeyBytes []byte
2✔
2533
                if addr.PublicKey != nil {
4✔
2534
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
2✔
2535
                }
2✔
2536
                addresses[idx] = &AddressProperty{
2✔
2537
                        Address:        addr.Address,
2✔
2538
                        IsInternal:     addr.Internal,
2✔
2539
                        Balance:        int64(addr.Balance),
2✔
2540
                        DerivationPath: addr.DerivationPath,
2✔
2541
                        PublicKey:      pubKeyBytes,
2✔
2542
                }
2✔
2543
        }
2544

2545
        rpcAddressList := &AccountWithAddresses{
2✔
2546
                Name:           rpcAccount.Name,
2✔
2547
                AddressType:    rpcAccount.AddressType,
2✔
2548
                DerivationPath: rpcAccount.DerivationPath,
2✔
2549
                Addresses:      addresses,
2✔
2550
        }
2✔
2551

2✔
2552
        return rpcAddressList, nil
2✔
2553
}
2554

2555
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2556
// name and key scope filter can be provided to filter through all of the wallet
2557
// accounts and return only those matching.
2558
func (w *WalletKit) ListAccounts(ctx context.Context,
2559
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
2✔
2560

2✔
2561
        // Map the supported address types into their corresponding key scope.
2✔
2562
        var keyScopeFilter *waddrmgr.KeyScope
2✔
2563
        switch req.AddressType {
2✔
2564
        case AddressType_UNKNOWN:
2✔
2565
                break
2✔
2566

2567
        case AddressType_WITNESS_PUBKEY_HASH:
2✔
2568
                keyScope := waddrmgr.KeyScopeBIP0084
2✔
2569
                keyScopeFilter = &keyScope
2✔
2570

2571
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2572
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
2✔
2573

2✔
2574
                keyScope := waddrmgr.KeyScopeBIP0049Plus
2✔
2575
                keyScopeFilter = &keyScope
2✔
2576

2577
        case AddressType_TAPROOT_PUBKEY:
2✔
2578
                keyScope := waddrmgr.KeyScopeBIP0086
2✔
2579
                keyScopeFilter = &keyScope
2✔
2580

2581
        default:
×
2582
                return nil, fmt.Errorf("unhandled address type %v",
×
2583
                        req.AddressType)
×
2584
        }
2585

2586
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
2✔
2587
        if err != nil {
2✔
2588
                return nil, err
×
2589
        }
×
2590

2591
        rpcAccounts := make([]*Account, 0, len(accounts))
2✔
2592
        for _, account := range accounts {
4✔
2593
                // Don't include the default imported accounts created by the
2✔
2594
                // wallet in the response if they don't have any keys imported.
2✔
2595
                if account.AccountName == waddrmgr.ImportedAddrAccountName &&
2✔
2596
                        account.ImportedKeyCount == 0 {
4✔
2597

2✔
2598
                        continue
2✔
2599
                }
2600

2601
                rpcAccount, err := marshalWalletAccount(
2✔
2602
                        w.internalScope(), account,
2✔
2603
                )
2✔
2604
                if err != nil {
2✔
2605
                        return nil, err
×
2606
                }
×
2607
                rpcAccounts = append(rpcAccounts, rpcAccount)
2✔
2608
        }
2609

2610
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
2✔
2611
}
2612

2613
// RequiredReserve returns the minimum amount of satoshis that should be
2614
// kept in the wallet in order to fee bump anchor channels if necessary.
2615
// The value scales with the number of public anchor channels but is
2616
// capped at a maximum.
2617
func (w *WalletKit) RequiredReserve(ctx context.Context,
2618
        req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
2✔
2619

2✔
2620
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
2✔
2621
        if err != nil {
2✔
2622
                return nil, err
×
2623
        }
×
2624

2625
        additionalChans := req.AdditionalPublicChannels
2✔
2626
        totalChans := uint32(numAnchorChans) + additionalChans
2✔
2627
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
2✔
2628

2✔
2629
        return &RequiredReserveResponse{
2✔
2630
                RequiredReserve: int64(reserved),
2✔
2631
        }, nil
2✔
2632
}
2633

2634
// ListAddresses retrieves all the addresses along with their balance. An
2635
// account name filter can be provided to filter through all of the
2636
// wallet accounts and return the addresses of only those matching.
2637
func (w *WalletKit) ListAddresses(ctx context.Context,
2638
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
2✔
2639

2✔
2640
        addressLists, err := w.cfg.Wallet.ListAddresses(
2✔
2641
                req.AccountName,
2✔
2642
                req.ShowCustomAccounts,
2✔
2643
        )
2✔
2644
        if err != nil {
2✔
2645
                return nil, err
×
2646
        }
×
2647

2648
        // Create a slice of accounts from addressLists map.
2649
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
2✔
2650
        for account := range addressLists {
4✔
2651
                accounts = append(accounts, account)
2✔
2652
        }
2✔
2653

2654
        // Sort the accounts by derivation path.
2655
        sort.Slice(accounts, func(i, j int) bool {
4✔
2656
                scopeI := accounts[i].KeyScope
2✔
2657
                scopeJ := accounts[j].KeyScope
2✔
2658
                if scopeI.Purpose == scopeJ.Purpose {
2✔
2659
                        if scopeI.Coin == scopeJ.Coin {
×
2660
                                acntNumI := accounts[i].AccountNumber
×
2661
                                acntNumJ := accounts[j].AccountNumber
×
2662
                                return acntNumI < acntNumJ
×
2663
                        }
×
2664

2665
                        return scopeI.Coin < scopeJ.Coin
×
2666
                }
2667

2668
                return scopeI.Purpose < scopeJ.Purpose
2✔
2669
        })
2670

2671
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
2✔
2672
        for _, account := range accounts {
4✔
2673
                addressList := addressLists[account]
2✔
2674
                rpcAddressList, err := marshalWalletAddressList(
2✔
2675
                        w, account, addressList,
2✔
2676
                )
2✔
2677
                if err != nil {
2✔
2678
                        return nil, err
×
2679
                }
×
2680

2681
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
2✔
2682
        }
2683

2684
        return &ListAddressesResponse{
2✔
2685
                AccountWithAddresses: rpcAddressLists,
2✔
2686
        }, nil
2✔
2687
}
2688

2689
// parseAddrType parses an address type from its RPC representation to a
2690
// *waddrmgr.AddressType.
2691
func parseAddrType(addrType AddressType,
2692
        required bool) (*waddrmgr.AddressType, error) {
2✔
2693

2✔
2694
        switch addrType {
2✔
2695
        case AddressType_UNKNOWN:
×
2696
                if required {
×
2697
                        return nil, fmt.Errorf("an address type must be " +
×
2698
                                "specified")
×
2699
                }
×
2700
                return nil, nil
×
2701

2702
        case AddressType_WITNESS_PUBKEY_HASH:
2✔
2703
                addrTyp := waddrmgr.WitnessPubKey
2✔
2704
                return &addrTyp, nil
2✔
2705

2706
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
2✔
2707
                addrTyp := waddrmgr.NestedWitnessPubKey
2✔
2708
                return &addrTyp, nil
2✔
2709

2710
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
2✔
2711
                addrTyp := waddrmgr.WitnessPubKey
2✔
2712
                return &addrTyp, nil
2✔
2713

2714
        case AddressType_TAPROOT_PUBKEY:
2✔
2715
                addrTyp := waddrmgr.TaprootPubKey
2✔
2716
                return &addrTyp, nil
2✔
2717

2718
        default:
×
2719
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2720
        }
2721
}
2722

2723
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2724
// transaction or a signature. It is prepended in front of the message and
2725
// follows the same standard as bitcoin core and btcd.
2726
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2727

2728
// SignMessageWithAddr signs a message with the private key of the provided
2729
// address. The address needs to belong to the lnd wallet.
2730
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2731
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
2✔
2732

2✔
2733
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
2✔
2734
        if err != nil {
2✔
2735
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2736
        }
×
2737

2738
        if !addr.IsForNet(w.cfg.ChainParams) {
2✔
2739
                return nil, fmt.Errorf("encoded address is for "+
×
2740
                        "the wrong network %s", req.Addr)
×
2741
        }
×
2742

2743
        // Fetch address infos from own wallet and check whether it belongs
2744
        // to the lnd wallet.
2745
        managedAddr, err := w.cfg.Wallet.AddressInfo(addr)
2✔
2746
        if err != nil {
2✔
2747
                return nil, fmt.Errorf("address could not be found in the "+
×
2748
                        "wallet database: %w", err)
×
2749
        }
×
2750

2751
        // Verifying by checking the interface type that the wallet knows about
2752
        // the public and private keys so it can sign the message with the
2753
        // private key of this address.
2754
        pubKey, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
2✔
2755
        if !ok {
2✔
2756
                return nil, fmt.Errorf("private key to address is unknown")
×
2757
        }
×
2758

2759
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
2✔
2760
        if err != nil {
2✔
2761
                return nil, err
×
2762
        }
×
2763

2764
        // For all address types (P2WKH, NP2WKH,P2TR) the ECDSA compact signing
2765
        // algorithm is used. For P2TR addresses this represents a special case.
2766
        // ECDSA is used to create a compact signature which makes the public
2767
        // key of the signature recoverable. For Schnorr no known compact
2768
        // signing algorithm exists yet.
2769
        privKey, err := pubKey.PrivKey()
2✔
2770
        if err != nil {
2✔
2771
                return nil, fmt.Errorf("no private key could be "+
×
2772
                        "fetched from wallet database: %w", err)
×
2773
        }
×
2774

2775
        sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
2✔
2776

2✔
2777
        // Bitcoin signatures are base64 encoded (being compatible with
2✔
2778
        // bitcoin-core and btcd).
2✔
2779
        sig := base64.StdEncoding.EncodeToString(sigBytes)
2✔
2780

2✔
2781
        return &SignMessageWithAddrResponse{
2✔
2782
                Signature: sig,
2✔
2783
        }, nil
2✔
2784
}
2785

2786
// VerifyMessageWithAddr verifies a signature on a message with a provided
2787
// address, it checks both the validity of the signature itself and then
2788
// verifies whether the signature corresponds to the public key of the
2789
// provided address. There is no dependence on the private key of the address
2790
// therefore also external addresses are allowed to verify signatures.
2791
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
2792
func (w *WalletKit) VerifyMessageWithAddr(_ context.Context,
2793
        req *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse,
2794
        error) {
2✔
2795

2✔
2796
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
2✔
2797
        if err != nil {
2✔
2798
                return nil, fmt.Errorf("malformed base64 encoding of "+
×
2799
                        "the signature: %w", err)
×
2800
        }
×
2801

2802
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
2✔
2803
        if err != nil {
2✔
2804
                return nil, err
×
2805
        }
×
2806

2807
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
2✔
2808
        if err != nil {
2✔
2809
                return nil, fmt.Errorf("unable to recover public key "+
×
2810
                        "from compact signature: %w", err)
×
2811
        }
×
2812

2813
        var serializedPubkey []byte
2✔
2814
        if wasCompressed {
4✔
2815
                serializedPubkey = pk.SerializeCompressed()
2✔
2816
        } else {
2✔
2817
                serializedPubkey = pk.SerializeUncompressed()
×
2818
        }
×
2819

2820
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
2✔
2821
        if err != nil {
2✔
2822
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2823
        }
×
2824

2825
        if !addr.IsForNet(w.cfg.ChainParams) {
2✔
2826
                return nil, fmt.Errorf("encoded address is for"+
×
2827
                        "the wrong network %s", req.Addr)
×
2828
        }
×
2829

2830
        var (
2✔
2831
                address    btcutil.Address
2✔
2832
                pubKeyHash = btcutil.Hash160(serializedPubkey)
2✔
2833
        )
2✔
2834

2✔
2835
        // Ensure the address is one of the supported types.
2✔
2836
        switch addr.(type) {
2✔
2837
        case *btcutil.AddressPubKeyHash:
2✔
2838
                address, err = btcutil.NewAddressPubKeyHash(
2✔
2839
                        pubKeyHash, w.cfg.ChainParams,
2✔
2840
                )
2✔
2841
                if err != nil {
2✔
2842
                        return nil, err
×
2843
                }
×
2844

2845
        case *btcutil.AddressWitnessPubKeyHash:
2✔
2846
                address, err = btcutil.NewAddressWitnessPubKeyHash(
2✔
2847
                        pubKeyHash, w.cfg.ChainParams,
2✔
2848
                )
2✔
2849
                if err != nil {
2✔
2850
                        return nil, err
×
2851
                }
×
2852

2853
        case *btcutil.AddressScriptHash:
2✔
2854
                // Check if address is a Nested P2WKH (NP2WKH).
2✔
2855
                address, err = btcutil.NewAddressWitnessPubKeyHash(
2✔
2856
                        pubKeyHash, w.cfg.ChainParams,
2✔
2857
                )
2✔
2858
                if err != nil {
2✔
2859
                        return nil, err
×
2860
                }
×
2861

2862
                witnessScript, err := txscript.PayToAddrScript(address)
2✔
2863
                if err != nil {
2✔
2864
                        return nil, err
×
2865
                }
×
2866

2867
                address, err = btcutil.NewAddressScriptHashFromHash(
2✔
2868
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
2✔
2869
                )
2✔
2870
                if err != nil {
2✔
2871
                        return nil, err
×
2872
                }
×
2873

2874
        case *btcutil.AddressTaproot:
2✔
2875
                // Only addresses without a tapscript are allowed because
2✔
2876
                // the verification is using the internal key.
2✔
2877
                tapKey := txscript.ComputeTaprootKeyNoScript(pk)
2✔
2878
                address, err = btcutil.NewAddressTaproot(
2✔
2879
                        schnorr.SerializePubKey(tapKey),
2✔
2880
                        w.cfg.ChainParams,
2✔
2881
                )
2✔
2882
                if err != nil {
2✔
2883
                        return nil, err
×
2884
                }
×
2885

2886
        default:
×
2887
                return nil, fmt.Errorf("unsupported address type")
×
2888
        }
2889

2890
        return &VerifyMessageWithAddrResponse{
2✔
2891
                Valid:  req.Addr == address.EncodeAddress(),
2✔
2892
                Pubkey: serializedPubkey,
2✔
2893
        }, nil
2✔
2894
}
2895

2896
// ImportAccount imports an account backed by an account extended public key.
2897
// The master key fingerprint denotes the fingerprint of the root key
2898
// corresponding to the account public key (also known as the key with
2899
// derivation path m/). This may be required by some hardware wallets for proper
2900
// identification and signing.
2901
//
2902
// The address type can usually be inferred from the key's version, but may be
2903
// required for certain keys to map them into the proper scope.
2904
//
2905
// For BIP-0044 keys, an address type must be specified as we intend to not
2906
// support importing BIP-0044 keys into the wallet using the legacy
2907
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
2908
// the standard BIP-0049 derivation scheme, while a witness address type will
2909
// force the standard BIP-0084 derivation scheme.
2910
//
2911
// For BIP-0049 keys, an address type must also be specified to make a
2912
// distinction between the standard BIP-0049 address schema (nested witness
2913
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
2914
// externally, witness pubkeys internally).
2915
func (w *WalletKit) ImportAccount(_ context.Context,
2916
        req *ImportAccountRequest) (*ImportAccountResponse, error) {
2✔
2917

2✔
2918
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
2✔
2919
        if err != nil {
2✔
2920
                return nil, err
×
2921
        }
×
2922

2923
        var mkfp uint32
2✔
2924
        switch len(req.MasterKeyFingerprint) {
2✔
2925
        // No master key fingerprint provided, which is fine as it's not
2926
        // required.
2927
        case 0:
2✔
2928
        // Expected length.
2929
        case 4:
×
2930
                mkfp = binary.BigEndian.Uint32(req.MasterKeyFingerprint)
×
2931
        default:
×
2932
                return nil, errors.New("invalid length for master key " +
×
2933
                        "fingerprint, expected 4 bytes in big-endian")
×
2934
        }
2935

2936
        addrType, err := parseAddrType(req.AddressType, false)
2✔
2937
        if err != nil {
2✔
2938
                return nil, err
×
2939
        }
×
2940

2941
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
2✔
2942
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
2✔
2943
        )
2✔
2944
        if err != nil {
4✔
2945
                return nil, err
2✔
2946
        }
2✔
2947

2948
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
2✔
2949
        if err != nil {
2✔
2950
                return nil, err
×
2951
        }
×
2952

2953
        resp := &ImportAccountResponse{Account: rpcAccount}
2✔
2954
        if !req.DryRun {
4✔
2955
                return resp, nil
2✔
2956
        }
2✔
2957

2958
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
2959
        for i := 0; i < len(extAddrs); i++ {
×
2960
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
2961
        }
×
2962
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
2963
        for i := 0; i < len(intAddrs); i++ {
×
2964
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
2965
        }
×
2966

2967
        return resp, nil
×
2968
}
2969

2970
// ImportPublicKey imports a single derived public key into the wallet. The
2971
// address type can usually be inferred from the key's version, but in the case
2972
// of legacy versions (xpub, tpub), an address type must be specified as we
2973
// intend to not support importing BIP-44 keys into the wallet using the legacy
2974
// pay-to-pubkey-hash (P2PKH) scheme. For Taproot keys, this will only watch
2975
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
2976
// spend or script spend outputs.
2977
func (w *WalletKit) ImportPublicKey(_ context.Context,
2978
        req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
2✔
2979

2✔
2980
        var (
2✔
2981
                pubKey *btcec.PublicKey
2✔
2982
                err    error
2✔
2983
        )
2✔
2984
        switch req.AddressType {
2✔
2985
        case AddressType_TAPROOT_PUBKEY:
2✔
2986
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
2✔
2987

2988
        default:
2✔
2989
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
2✔
2990
        }
2991
        if err != nil {
2✔
2992
                return nil, err
×
2993
        }
×
2994

2995
        addrType, err := parseAddrType(req.AddressType, true)
2✔
2996
        if err != nil {
2✔
2997
                return nil, err
×
2998
        }
×
2999

3000
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
2✔
3001
                return nil, err
×
3002
        }
×
3003

3004
        return &ImportPublicKeyResponse{
2✔
3005
                Status: fmt.Sprintf("public key %x imported",
2✔
3006
                        pubKey.SerializeCompressed()),
2✔
3007
        }, nil
2✔
3008
}
3009

3010
// ImportTapscript imports a Taproot script and internal key and adds the
3011
// resulting Taproot output key as a watch-only output script into the wallet.
3012
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
3013
// path) use ImportPublicKey.
3014
//
3015
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
3016
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
3017
func (w *WalletKit) ImportTapscript(_ context.Context,
3018
        req *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
2✔
3019

2✔
3020
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
2✔
3021
        if err != nil {
2✔
3022
                return nil, fmt.Errorf("error parsing internal key: %w", err)
×
3023
        }
×
3024

3025
        var tapscript *waddrmgr.Tapscript
2✔
3026
        switch {
2✔
3027
        case req.GetFullTree() != nil:
2✔
3028
                tree := req.GetFullTree()
2✔
3029
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
2✔
3030
                for idx, leaf := range tree.AllLeaves {
4✔
3031
                        leaves[idx] = txscript.TapLeaf{
2✔
3032
                                LeafVersion: txscript.TapscriptLeafVersion(
2✔
3033
                                        leaf.LeafVersion,
2✔
3034
                                ),
2✔
3035
                                Script: leaf.Script,
2✔
3036
                        }
2✔
3037
                }
2✔
3038

3039
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
2✔
3040

3041
        case req.GetPartialReveal() != nil:
2✔
3042
                partialReveal := req.GetPartialReveal()
2✔
3043
                if partialReveal.RevealedLeaf == nil {
2✔
3044
                        return nil, fmt.Errorf("missing revealed leaf")
×
3045
                }
×
3046

3047
                revealedLeaf := txscript.TapLeaf{
2✔
3048
                        LeafVersion: txscript.TapscriptLeafVersion(
2✔
3049
                                partialReveal.RevealedLeaf.LeafVersion,
2✔
3050
                        ),
2✔
3051
                        Script: partialReveal.RevealedLeaf.Script,
2✔
3052
                }
2✔
3053
                if len(partialReveal.FullInclusionProof)%32 != 0 {
2✔
3054
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
3055
                                "length, expected multiple of 32, got %d",
×
3056
                                len(partialReveal.FullInclusionProof)%32)
×
3057
                }
×
3058

3059
                tapscript = input.TapscriptPartialReveal(
2✔
3060
                        internalKey, revealedLeaf,
2✔
3061
                        partialReveal.FullInclusionProof,
2✔
3062
                )
2✔
3063

3064
        case req.GetRootHashOnly() != nil:
2✔
3065
                rootHash := req.GetRootHashOnly()
2✔
3066
                if len(rootHash) == 0 {
2✔
3067
                        return nil, fmt.Errorf("missing root hash")
×
3068
                }
×
3069

3070
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
2✔
3071

3072
        case req.GetFullKeyOnly():
2✔
3073
                tapscript = input.TapscriptFullKeyOnly(internalKey)
2✔
3074

3075
        default:
×
3076
                return nil, fmt.Errorf("invalid script")
×
3077
        }
3078

3079
        taprootScope := waddrmgr.KeyScopeBIP0086
2✔
3080
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
2✔
3081
        if err != nil {
2✔
3082
                return nil, fmt.Errorf("error importing script into wallet: %w",
×
3083
                        err)
×
3084
        }
×
3085

3086
        return &ImportTapscriptResponse{
2✔
3087
                P2TrAddress: addr.Address().String(),
2✔
3088
        }, nil
2✔
3089
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc