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

lightningnetwork / lnd / 11954082915

21 Nov 2024 01:20PM UTC coverage: 59.327% (+0.6%) from 58.776%
11954082915

Pull #8754

github

ViktorTigerstrom
itest: wrap deriveCustomScopeAccounts at 80 chars

This commit fixes that word wrapping for the deriveCustomScopeAccounts
function docs, and ensures that it wraps at 80 characters or less.
Pull Request #8754: Add `Outbound` Remote Signer implementation

1940 of 2984 new or added lines in 44 files covered. (65.01%)

226 existing lines in 37 files now uncovered.

135234 of 227947 relevant lines covered (59.33%)

19316.75 hits per line

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

66.53
/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
        "math"
14
        "os"
15
        "path/filepath"
16
        "sort"
17
        "sync"
18
        "sync/atomic"
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"
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
        "golang.org/x/exp/maps"
49
        "google.golang.org/grpc"
50
        "gopkg.in/macaroon-bakery.v2/bakery"
51
)
52

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

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

195
        // DefaultWalletKitMacFilename is the default name of the wallet kit
196
        // macaroon that we expect to find via a file handle within the main
197
        // configuration file in this package.
198
        DefaultWalletKitMacFilename = "walletkit.macaroon"
199

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

248
// RemoteSigner is an interface that mimics a subset of the rpcwallet
249
// RemoteSigner interface to avoid circular dependencies.
250
type RemoteSigner interface {
251
        // Run feeds lnd with the incoming stream that an outbound remote signer
252
        // has set up, and then blocks until the stream is closed. Lnd can then
253
        // proceed to send any requests to the remote signer through the stream.
254
        Run(stream WalletKit_SignCoordinatorStreamsServer) error
255
}
256

257
// ServerShell is a shell struct holding a reference to the actual sub-server.
258
// It is used to register the gRPC sub-server with the root server before we
259
// have the necessary dependencies to populate the actual sub-server.
260
type ServerShell struct {
261
        WalletKitServer
262
}
263

264
// WalletKit is a sub-RPC server that exposes a tool kit which allows clients
265
// to execute common wallet operations. This includes requesting new addresses,
266
// keys (for contracts!), and publishing transactions.
267
type WalletKit struct {
268
        injected int32 // To be used atomically.
269

270
        // Required by the grpc-gateway/v2 library for forward compatibility.
271
        UnimplementedWalletKitServer
272

273
        cfg *Config
274

275
        // As we allow rpc requests into the server before InjectDependencies
276
        // has been executed, the read lock should be held when accessing values
277
        // from the cfg.
278
        // The write lock should be held when setting the cfg.
279
        sync.RWMutex
280
}
281

282
// A compile time check to ensure that WalletKit fully implements the
283
// WalletKitServer gRPC service.
284
var _ WalletKitServer = (*WalletKit)(nil)
285

286
// New creates a new instance of the WalletKit sub-RPC server.
287
func New() (*WalletKit, lnrpc.MacaroonPerms, error) {
4✔
288
        return &WalletKit{cfg: &Config{}}, macPermissions, nil
4✔
289
}
4✔
290

291
// Stop signals any active goroutines for a graceful closure.
292
//
293
// NOTE: This is part of the lnrpc.SubServer interface.
294
func (w *WalletKit) Stop() error {
4✔
295
        return nil
4✔
296
}
4✔
297

298
// InjectDependencies populates the sub-server's dependencies. If the
299
// finalizeDependencies boolean is true, then the sub-server will finalize its
300
// dependencies and return an error if any required dependencies are missing.
301
//
302
// NOTE: This is part of the lnrpc.SubServer interface.
303
func (w *WalletKit) InjectDependencies(
304
        configRegistry lnrpc.SubServerConfigDispatcher,
305
        finalizeDependencies bool) error {
4✔
306

4✔
307
        if finalizeDependencies && atomic.AddInt32(&w.injected, 1) != 1 {
4✔
NEW
308
                return lnrpc.ErrDependenciesFinalized
×
NEW
309
        }
×
310

311
        w.Lock()
4✔
312
        defer w.Unlock()
4✔
313

4✔
314
        cfg, err := getConfig(configRegistry, finalizeDependencies)
4✔
315
        if err != nil {
4✔
NEW
316
                return err
×
NEW
317
        }
×
318

319
        if finalizeDependencies {
8✔
320
                w.cfg = cfg
4✔
321

4✔
322
                return nil
4✔
323
        }
4✔
324

325
        // If the path of the wallet kit macaroon wasn't specified, then we'll
326
        // assume that it's found at the default network directory.
327
        if cfg.WalletKitMacPath == "" {
8✔
328
                cfg.WalletKitMacPath = filepath.Join(
4✔
329
                        cfg.NetworkDir, DefaultWalletKitMacFilename,
4✔
330
                )
4✔
331
        }
4✔
332

333
        // Now that we know the full path of the wallet kit macaroon, we can
334
        // check to see if we need to create it or not. If stateless_init is set
335
        // then we don't write the macaroons.
336
        macFilePath := cfg.WalletKitMacPath
4✔
337
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
4✔
338
                !lnrpc.FileExists(macFilePath) {
4✔
UNCOV
339

×
UNCOV
340
                log.Infof("Baking macaroons for WalletKit RPC Server at: %v",
×
UNCOV
341
                        macFilePath)
×
UNCOV
342

×
UNCOV
343
                // At this point, we know that the wallet kit macaroon doesn't
×
UNCOV
344
                // yet, exist, so we need to create it with the help of the
×
UNCOV
345
                // main macaroon service.
×
UNCOV
346
                walletKitMac, err := cfg.MacService.NewMacaroon(
×
UNCOV
347
                        context.Background(), macaroons.DefaultRootKeyID,
×
UNCOV
348
                        macaroonOps...,
×
UNCOV
349
                )
×
UNCOV
350
                if err != nil {
×
NEW
351
                        return err
×
352
                }
×
UNCOV
353
                walletKitMacBytes, err := walletKitMac.M().MarshalBinary()
×
UNCOV
354
                if err != nil {
×
NEW
355
                        return err
×
356
                }
×
UNCOV
357
                err = os.WriteFile(macFilePath, walletKitMacBytes, 0644)
×
UNCOV
358
                if err != nil {
×
359
                        _ = os.Remove(macFilePath)
×
NEW
360
                        return err
×
361
                }
×
362
        }
363

364
        w.cfg = cfg
4✔
365

4✔
366
        return nil
4✔
367
}
368

369
// Name returns a unique string representation of the sub-server. This can be
370
// used to identify the sub-server and also de-duplicate them.
371
//
372
// NOTE: This is part of the lnrpc.SubServer interface.
373
func (w *WalletKit) Name() string {
4✔
374
        return SubServerName
4✔
375
}
4✔
376

377
// RegisterWithRootServer will be called by the root gRPC server to direct a
378
// sub RPC server to register itself with the main gRPC root server. Until this
379
// is called, each sub-server won't be able to have requests routed towards it.
380
//
381
// NOTE: This is part of the lnrpc.GrpcHandler interface.
382
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
4✔
383
        // We make sure that we register it with the main gRPC server to ensure
4✔
384
        // all our methods are routed properly.
4✔
385
        RegisterWalletKitServer(grpcServer, r)
4✔
386

4✔
387
        log.Debugf("WalletKit RPC server successfully registered with " +
4✔
388
                "root gRPC server")
4✔
389

4✔
390
        return nil
4✔
391
}
4✔
392

393
// RegisterWithRestServer will be called by the root REST mux to direct a sub
394
// RPC server to register itself with the main REST mux server. Until this is
395
// called, each sub-server won't be able to have requests routed towards it.
396
//
397
// NOTE: This is part of the lnrpc.GrpcHandler interface.
398
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
399
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
4✔
400

4✔
401
        // We make sure that we register it with the main REST server to ensure
4✔
402
        // all our methods are routed properly.
4✔
403
        err := RegisterWalletKitHandlerFromEndpoint(ctx, mux, dest, opts)
4✔
404
        if err != nil {
4✔
405
                log.Errorf("Could not register WalletKit REST server "+
×
406
                        "with root REST server: %v", err)
×
407
                return err
×
408
        }
×
409

410
        log.Debugf("WalletKit REST server successfully registered with " +
4✔
411
                "root REST server")
4✔
412
        return nil
4✔
413
}
414

415
// CreateSubServer creates an instance of the sub-server, and returns the
416
// macaroon permissions that the sub-server wishes to pass on to the root server
417
// for all methods routed towards it.
418
//
419
// NOTE: This is part of the lnrpc.GrpcHandler interface.
420
func (r *ServerShell) CreateSubServer() (
421
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
4✔
422

4✔
423
        subServer, macPermissions, err := New()
4✔
424
        if err != nil {
4✔
425
                return nil, nil, err
×
426
        }
×
427

428
        r.WalletKitServer = subServer
4✔
429
        return subServer, macPermissions, nil
4✔
430
}
431

432
// internalScope returns the internal key scope.
433
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
4✔
434
        w.RLock()
4✔
435
        defer w.RUnlock()
4✔
436

4✔
437
        return waddrmgr.KeyScope{
4✔
438
                Purpose: keychain.BIP0043Purpose,
4✔
439
                Coin:    w.cfg.ChainParams.HDCoinType,
4✔
440
        }
4✔
441
}
4✔
442

443
// ListUnspent returns useful information about each unspent output owned by
444
// the wallet, as reported by the underlying `ListUnspentWitness`; the
445
// information returned is: outpoint, amount in satoshis, address, address
446
// type, scriptPubKey in hex and number of confirmations. The result is
447
// filtered to contain outputs whose number of confirmations is between a
448
// minimum and maximum number of confirmations specified by the user.
449
func (w *WalletKit) ListUnspent(ctx context.Context,
450
        req *ListUnspentRequest) (*ListUnspentResponse, error) {
4✔
451

4✔
452
        // Force min_confs and max_confs to be zero if unconfirmed_only is
4✔
453
        // true.
4✔
454
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
4✔
455
                return nil, fmt.Errorf("min_confs and max_confs must be zero " +
×
456
                        "if unconfirmed_only is true")
×
457
        }
×
458

459
        // When unconfirmed_only is inactive and max_confs is zero (default
460
        // values), we will override max_confs to be a MaxInt32, in order
461
        // to return all confirmed and unconfirmed utxos as a default response.
462
        if req.MaxConfs == 0 && !req.UnconfirmedOnly {
8✔
463
                req.MaxConfs = math.MaxInt32
4✔
464
        }
4✔
465

466
        // Validate the confirmation arguments.
467
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
4✔
468
        if err != nil {
4✔
469
                return nil, err
×
470
        }
×
471

472
        // With our arguments validated, we'll query the internal wallet for
473
        // the set of UTXOs that match our query.
474
        //
475
        // We'll acquire the global coin selection lock to ensure there aren't
476
        // any other concurrent processes attempting to lock any UTXOs which may
477
        // be shown available to us.
478
        var utxos []*lnwallet.Utxo
4✔
479

4✔
480
        w.RLock()
4✔
481
        defer w.RUnlock()
4✔
482

4✔
483
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
8✔
484
                utxos, err = w.cfg.Wallet.ListUnspentWitness(
4✔
485
                        minConfs, maxConfs, req.Account,
4✔
486
                )
4✔
487

4✔
488
                return err
4✔
489
        })
4✔
490
        if err != nil {
4✔
491
                return nil, err
×
492
        }
×
493

494
        rpcUtxos, err := lnrpc.MarshalUtxos(utxos, w.cfg.ChainParams)
4✔
495
        if err != nil {
4✔
496
                return nil, err
×
497
        }
×
498

499
        return &ListUnspentResponse{
4✔
500
                Utxos: rpcUtxos,
4✔
501
        }, nil
4✔
502
}
503

504
// SignCoordinatorStreams opens a bi-directional streaming RPC, which is used
505
// to allow a remote signer to process sign requests on behalf of the wallet.
506
func (w *WalletKit) SignCoordinatorStreams(
507
        stream WalletKit_SignCoordinatorStreamsServer) error {
4✔
508

4✔
509
        w.RLock()
4✔
510

4✔
511
        // Check that the user actually has configured that the reverse remote
4✔
512
        // signer functionality should be enabled.
4✔
513
        if w.cfg.RemoteSigner == nil {
4✔
NEW
514
                w.RUnlock()
×
NEW
515

×
NEW
516
                return fmt.Errorf("remote signer not set in config")
×
NEW
517
        }
×
518

519
        signer := w.cfg.RemoteSigner
4✔
520

4✔
521
        // Release the read lock as we will acquire the write in the
4✔
522
        // InjectDependencies function while the stream is still open.
4✔
523
        w.RUnlock()
4✔
524

4✔
525
        err := signer.Run(stream)
4✔
526
        if err != nil {
8✔
527
                log.Errorf("Remote signer stream error: %v", err)
4✔
528
        }
4✔
529

530
        return err
4✔
531
}
532

533
// LeaseOutput locks an output to the given ID, preventing it from being
534
// available for any future coin selection attempts. The absolute time of the
535
// lock's expiration is returned. The expiration of the lock can be extended by
536
// successive invocations of this call. Outputs can be unlocked before their
537
// expiration through `ReleaseOutput`.
538
//
539
// If the output is not known, wtxmgr.ErrUnknownOutput is returned. If the
540
// output has already been locked to a different ID, then
541
// wtxmgr.ErrOutputAlreadyLocked is returned.
542
func (w *WalletKit) LeaseOutput(ctx context.Context,
543
        req *LeaseOutputRequest) (*LeaseOutputResponse, error) {
×
544

×
545
        if len(req.Id) != 32 {
×
546
                return nil, errors.New("id must be 32 random bytes")
×
547
        }
×
548
        var lockID wtxmgr.LockID
×
549
        copy(lockID[:], req.Id)
×
550

×
551
        // Don't allow ID's of 32 bytes, but all zeros.
×
552
        if lockID == (wtxmgr.LockID{}) {
×
553
                return nil, errors.New("id must be 32 random bytes")
×
554
        }
×
555

556
        // Don't allow our internal ID to be used externally for locking. Only
557
        // unlocking is allowed.
558
        if lockID == chanfunding.LndInternalLockID {
×
559
                return nil, errors.New("reserved id cannot be used")
×
560
        }
×
561

562
        op, err := UnmarshallOutPoint(req.Outpoint)
×
563
        if err != nil {
×
564
                return nil, err
×
565
        }
×
566

567
        // Use the specified lock duration or fall back to the default.
568
        duration := chanfunding.DefaultLockDuration
×
569
        if req.ExpirationSeconds != 0 {
×
570
                duration = time.Duration(req.ExpirationSeconds) * time.Second
×
571
        }
×
572

NEW
573
        w.RLock()
×
NEW
574
        defer w.RUnlock()
×
NEW
575

×
UNCOV
576
        // Acquire the global coin selection lock to ensure there aren't any
×
UNCOV
577
        // other concurrent processes attempting to lease the same UTXO.
×
578
        var expiration time.Time
×
579
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
580
                expiration, err = w.cfg.Wallet.LeaseOutput(
×
581
                        lockID, *op, duration,
×
582
                )
×
583
                return err
×
584
        })
×
585
        if err != nil {
×
586
                return nil, err
×
587
        }
×
588

589
        return &LeaseOutputResponse{
×
590
                Expiration: uint64(expiration.Unix()),
×
591
        }, nil
×
592
}
593

594
// ReleaseOutput unlocks an output, allowing it to be available for coin
595
// selection if it remains unspent. The ID should match the one used to
596
// originally lock the output.
597
func (w *WalletKit) ReleaseOutput(ctx context.Context,
598
        req *ReleaseOutputRequest) (*ReleaseOutputResponse, error) {
×
599

×
600
        if len(req.Id) != 32 {
×
601
                return nil, errors.New("id must be 32 random bytes")
×
602
        }
×
603
        var lockID wtxmgr.LockID
×
604
        copy(lockID[:], req.Id)
×
605

×
606
        op, err := UnmarshallOutPoint(req.Outpoint)
×
607
        if err != nil {
×
608
                return nil, err
×
609
        }
×
610

NEW
611
        w.RLock()
×
NEW
612
        defer w.RUnlock()
×
NEW
613

×
UNCOV
614
        // Acquire the global coin selection lock to maintain consistency as
×
UNCOV
615
        // it's acquired when we initially leased the output.
×
616
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
×
617
                return w.cfg.Wallet.ReleaseOutput(lockID, *op)
×
618
        })
×
619
        if err != nil {
×
620
                return nil, err
×
621
        }
×
622

623
        return &ReleaseOutputResponse{
×
624
                Status: fmt.Sprintf("output %v released", op.String()),
×
625
        }, nil
×
626
}
627

628
// ListLeases returns a list of all currently locked utxos.
629
func (w *WalletKit) ListLeases(ctx context.Context,
630
        req *ListLeasesRequest) (*ListLeasesResponse, error) {
×
631

×
NEW
632
        w.RLock()
×
NEW
633
        defer w.RUnlock()
×
NEW
634

×
635
        leases, err := w.cfg.Wallet.ListLeasedOutputs()
×
636
        if err != nil {
×
637
                return nil, err
×
638
        }
×
639

640
        return &ListLeasesResponse{
×
641
                LockedUtxos: marshallLeases(leases),
×
642
        }, nil
×
643
}
644

645
// DeriveNextKey attempts to derive the *next* key within the key family
646
// (account in BIP43) specified. This method should return the next external
647
// child within this branch.
648
func (w *WalletKit) DeriveNextKey(ctx context.Context,
649
        req *KeyReq) (*signrpc.KeyDescriptor, error) {
4✔
650

4✔
651
        w.RLock()
4✔
652
        defer w.RUnlock()
4✔
653

4✔
654
        nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey(
4✔
655
                keychain.KeyFamily(req.KeyFamily),
4✔
656
        )
4✔
657
        if err != nil {
4✔
658
                return nil, err
×
659
        }
×
660

661
        return &signrpc.KeyDescriptor{
4✔
662
                KeyLoc: &signrpc.KeyLocator{
4✔
663
                        KeyFamily: int32(nextKeyDesc.Family),
4✔
664
                        KeyIndex:  int32(nextKeyDesc.Index),
4✔
665
                },
4✔
666
                RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(),
4✔
667
        }, nil
4✔
668
}
669

670
// DeriveKey attempts to derive an arbitrary key specified by the passed
671
// KeyLocator.
672
func (w *WalletKit) DeriveKey(ctx context.Context,
673
        req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
4✔
674

4✔
675
        w.RLock()
4✔
676
        defer w.RUnlock()
4✔
677

4✔
678
        keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{
4✔
679
                Family: keychain.KeyFamily(req.KeyFamily),
4✔
680
                Index:  uint32(req.KeyIndex),
4✔
681
        })
4✔
682
        if err != nil {
4✔
683
                return nil, err
×
684
        }
×
685

686
        return &signrpc.KeyDescriptor{
4✔
687
                KeyLoc: &signrpc.KeyLocator{
4✔
688
                        KeyFamily: int32(keyDesc.Family),
4✔
689
                        KeyIndex:  int32(keyDesc.Index),
4✔
690
                },
4✔
691
                RawKeyBytes: keyDesc.PubKey.SerializeCompressed(),
4✔
692
        }, nil
4✔
693
}
694

695
// NextAddr returns the next unused address within the wallet.
696
func (w *WalletKit) NextAddr(ctx context.Context,
697
        req *AddrRequest) (*AddrResponse, error) {
4✔
698

4✔
699
        account := lnwallet.DefaultAccountName
4✔
700
        if req.Account != "" {
4✔
701
                account = req.Account
×
702
        }
×
703

704
        addrType := lnwallet.WitnessPubKey
4✔
705
        switch req.Type {
4✔
706
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
707
                addrType = lnwallet.NestedWitnessPubKey
×
708

709
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
710
                return nil, fmt.Errorf("invalid address type for next "+
×
711
                        "address: %v", req.Type)
×
712

713
        case AddressType_TAPROOT_PUBKEY:
×
714
                addrType = lnwallet.TaprootPubkey
×
715
        }
716

717
        w.RLock()
4✔
718
        defer w.RUnlock()
4✔
719

4✔
720
        addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account)
4✔
721
        if err != nil {
4✔
722
                return nil, err
×
723
        }
×
724

725
        return &AddrResponse{
4✔
726
                Addr: addr.String(),
4✔
727
        }, nil
4✔
728
}
729

730
// GetTransaction returns a transaction from the wallet given its hash.
731
func (w *WalletKit) GetTransaction(_ context.Context,
732
        req *GetTransactionRequest) (*lnrpc.Transaction, error) {
4✔
733

4✔
734
        // If the client doesn't specify a hash, then there's nothing to
4✔
735
        // return.
4✔
736
        if req.Txid == "" {
4✔
737
                return nil, fmt.Errorf("must provide a transaction hash")
×
738
        }
×
739

740
        txHash, err := chainhash.NewHashFromStr(req.Txid)
4✔
741
        if err != nil {
4✔
742
                return nil, err
×
743
        }
×
744

745
        w.RLock()
4✔
746
        defer w.RUnlock()
4✔
747

4✔
748
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
4✔
749
        if err != nil {
4✔
750
                return nil, err
×
751
        }
×
752

753
        return lnrpc.RPCTransaction(res), nil
4✔
754
}
755

756
// Attempts to publish the passed transaction to the network. Once this returns
757
// without an error, the wallet will continually attempt to re-broadcast the
758
// transaction on start up, until it enters the chain.
759
func (w *WalletKit) PublishTransaction(ctx context.Context,
760
        req *Transaction) (*PublishResponse, error) {
4✔
761

4✔
762
        switch {
4✔
763
        // If the client doesn't specify a transaction, then there's nothing to
764
        // publish.
765
        case len(req.TxHex) == 0:
×
766
                return nil, fmt.Errorf("must provide a transaction to " +
×
767
                        "publish")
×
768
        }
769

770
        tx := &wire.MsgTx{}
4✔
771
        txReader := bytes.NewReader(req.TxHex)
4✔
772
        if err := tx.Deserialize(txReader); err != nil {
4✔
773
                return nil, err
×
774
        }
×
775

776
        label, err := labels.ValidateAPI(req.Label)
4✔
777
        if err != nil {
4✔
778
                return nil, err
×
779
        }
×
780

781
        w.RLock()
4✔
782
        defer w.RUnlock()
4✔
783

4✔
784
        err = w.cfg.Wallet.PublishTransaction(tx, label)
4✔
785
        if err != nil {
4✔
786
                return nil, err
×
787
        }
×
788

789
        return &PublishResponse{}, nil
4✔
790
}
791

792
// RemoveTransaction attempts to remove the transaction and all of its
793
// descendants resulting from further spends of the outputs of the provided
794
// transaction id.
795
// NOTE: We do not remove the transaction from the rebroadcaster which might
796
// run in the background rebroadcasting not yet confirmed transactions. We do
797
// not have access to the rebroadcaster here nor should we. This command is not
798
// a way to remove transactions from the network. It is a way to shortcircuit
799
// wallet utxo housekeeping while transactions are still unconfirmed and we know
800
// that a transaction will never confirm because a replacement already pays
801
// higher fees.
802
func (w *WalletKit) RemoveTransaction(_ context.Context,
803
        req *GetTransactionRequest) (*RemoveTransactionResponse, error) {
4✔
804

4✔
805
        // If the client doesn't specify a hash, then there's nothing to
4✔
806
        // return.
4✔
807
        if req.Txid == "" {
4✔
808
                return nil, fmt.Errorf("must provide a transaction hash")
×
809
        }
×
810

811
        txHash, err := chainhash.NewHashFromStr(req.Txid)
4✔
812
        if err != nil {
4✔
813
                return nil, err
×
814
        }
×
815

816
        w.RLock()
4✔
817
        defer w.RUnlock()
4✔
818

4✔
819
        // Query the tx store of our internal wallet for the specified
4✔
820
        // transaction.
4✔
821
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
4✔
822
        if err != nil {
4✔
823
                return nil, fmt.Errorf("transaction with txid=%v not found "+
×
824
                        "in the internal wallet store", txHash)
×
825
        }
×
826

827
        // Only allow unconfirmed transactions to be removed because as soon
828
        // as a transaction is confirmed it will be evaluated by the wallet
829
        // again and the wallet state would be updated in case the user had
830
        // removed the transaction accidentally.
831
        if res.NumConfirmations > 0 {
4✔
832
                return nil, fmt.Errorf("transaction with txid=%v is already "+
×
833
                        "confirmed (numConfs=%d) cannot be removed", txHash,
×
834
                        res.NumConfirmations)
×
835
        }
×
836

837
        tx := &wire.MsgTx{}
4✔
838
        txReader := bytes.NewReader(res.RawTx)
4✔
839
        if err := tx.Deserialize(txReader); err != nil {
4✔
840
                return nil, err
×
841
        }
×
842

843
        err = w.cfg.Wallet.RemoveDescendants(tx)
4✔
844
        if err != nil {
4✔
845
                return nil, err
×
846
        }
×
847

848
        return &RemoveTransactionResponse{
4✔
849
                Status: "Successfully removed transaction",
4✔
850
        }, nil
4✔
851
}
852

853
// SendOutputs is similar to the existing sendmany call in Bitcoind, and allows
854
// the caller to create a transaction that sends to several outputs at once.
855
// This is ideal when wanting to batch create a set of transactions.
856
func (w *WalletKit) SendOutputs(ctx context.Context,
857
        req *SendOutputsRequest) (*SendOutputsResponse, error) {
4✔
858

4✔
859
        switch {
4✔
860
        // If the client didn't specify any outputs to create, then  we can't
861
        // proceed .
862
        case len(req.Outputs) == 0:
×
863
                return nil, fmt.Errorf("must specify at least one output " +
×
864
                        "to create")
×
865
        }
866

867
        // Before we can request this transaction to be created, we'll need to
868
        // amp the protos back into the format that the internal wallet will
869
        // recognize.
870
        var totalOutputValue int64
4✔
871
        outputsToCreate := make([]*wire.TxOut, 0, len(req.Outputs))
4✔
872
        for _, output := range req.Outputs {
8✔
873
                outputsToCreate = append(outputsToCreate, &wire.TxOut{
4✔
874
                        Value:    output.Value,
4✔
875
                        PkScript: output.PkScript,
4✔
876
                })
4✔
877
                totalOutputValue += output.Value
4✔
878
        }
4✔
879

880
        // Then, we'll extract the minimum number of confirmations that each
881
        // output we use to fund the transaction should satisfy.
882
        minConfs, err := lnrpc.ExtractMinConfs(
4✔
883
                req.MinConfs, req.SpendUnconfirmed,
4✔
884
        )
4✔
885
        if err != nil {
4✔
886
                return nil, err
×
887
        }
×
888

889
        w.RLock()
4✔
890
        defer w.RUnlock()
4✔
891

4✔
892
        // Before sending out funds we need to ensure that the remainder of our
4✔
893
        // wallet funds would cover for the anchor reserve requirement. We'll
4✔
894
        // also take unconfirmed funds into account.
4✔
895
        walletBalance, err := w.cfg.Wallet.ConfirmedBalance(
4✔
896
                0, lnwallet.DefaultAccountName,
4✔
897
        )
4✔
898
        if err != nil {
4✔
899
                return nil, err
×
900
        }
×
901

902
        // We'll get the currently required reserve amount.
903
        reserve, err := w.RequiredReserve(ctx, &RequiredReserveRequest{})
4✔
904
        if err != nil {
4✔
905
                return nil, err
×
906
        }
×
907

908
        // Then we check if our current wallet balance undershoots the required
909
        // reserve if we'd send out the outputs specified in the request.
910
        if int64(walletBalance)-totalOutputValue < reserve.RequiredReserve {
8✔
911
                return nil, ErrInsufficientReserve
4✔
912
        }
4✔
913

914
        label, err := labels.ValidateAPI(req.Label)
4✔
915
        if err != nil {
4✔
916
                return nil, err
×
917
        }
×
918

919
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
4✔
920
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
4✔
921
        )
4✔
922
        if err != nil {
4✔
923
                return nil, err
×
924
        }
×
925

926
        // Now that we have the outputs mapped and checked for the reserve
927
        // requirement, we can request that the wallet attempts to create this
928
        // transaction.
929
        tx, err := w.cfg.Wallet.SendOutputs(
4✔
930
                nil, outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw),
4✔
931
                minConfs, label, coinSelectionStrategy,
4✔
932
        )
4✔
933
        if err != nil {
4✔
934
                return nil, err
×
935
        }
×
936

937
        var b bytes.Buffer
4✔
938
        if err := tx.Serialize(&b); err != nil {
4✔
939
                return nil, err
×
940
        }
×
941

942
        return &SendOutputsResponse{
4✔
943
                RawTx: b.Bytes(),
4✔
944
        }, nil
4✔
945
}
946

947
// EstimateFee attempts to query the internal fee estimator of the wallet to
948
// determine the fee (in sat/kw) to attach to a transaction in order to achieve
949
// the confirmation target.
950
func (w *WalletKit) EstimateFee(ctx context.Context,
951
        req *EstimateFeeRequest) (*EstimateFeeResponse, error) {
×
952

×
953
        switch {
×
954
        // A confirmation target of zero doesn't make any sense. Similarly, we
955
        // reject confirmation targets of 1 as they're unreasonable.
956
        case req.ConfTarget == 0 || req.ConfTarget == 1:
×
957
                return nil, fmt.Errorf("confirmation target must be greater " +
×
958
                        "than 1")
×
959
        }
960

NEW
961
        w.RLock()
×
NEW
962
        defer w.RUnlock()
×
NEW
963

×
964
        satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW(
×
965
                uint32(req.ConfTarget),
×
966
        )
×
967
        if err != nil {
×
968
                return nil, err
×
969
        }
×
970

971
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
×
972

×
973
        return &EstimateFeeResponse{
×
974
                SatPerKw:            int64(satPerKw),
×
975
                MinRelayFeeSatPerKw: int64(relayFeePerKw),
×
976
        }, nil
×
977
}
978

979
// PendingSweeps returns lists of on-chain outputs that lnd is currently
980
// attempting to sweep within its central batching engine. Outputs with similar
981
// fee rates are batched together in order to sweep them within a single
982
// transaction. The fee rate of each sweeping transaction is determined by
983
// taking the average fee rate of all the outputs it's trying to sweep.
984
func (w *WalletKit) PendingSweeps(ctx context.Context,
985
        in *PendingSweepsRequest) (*PendingSweepsResponse, error) {
4✔
986

4✔
987
        w.RLock()
4✔
988
        defer w.RUnlock()
4✔
989

4✔
990
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
4✔
991
        // sweep.
4✔
992
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
4✔
993
        if err != nil {
4✔
994
                return nil, err
×
995
        }
×
996

997
        // Convert them into their respective RPC format.
998
        rpcPendingSweeps := make([]*PendingSweep, 0, len(inputsMap))
4✔
999
        for _, inp := range inputsMap {
8✔
1000
                witnessType, ok := allWitnessTypes[inp.WitnessType]
4✔
1001
                if !ok {
4✔
1002
                        return nil, fmt.Errorf("unhandled witness type %v for "+
×
1003
                                "input %v", inp.WitnessType, inp.OutPoint)
×
1004
                }
×
1005

1006
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
4✔
1007
                amountSat := uint32(inp.Amount)
4✔
1008
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
4✔
1009
                broadcastAttempts := uint32(inp.BroadcastAttempts)
4✔
1010

4✔
1011
                // Get the requested starting fee rate, if set.
4✔
1012
                startingFeeRate := fn.MapOptionZ(
4✔
1013
                        inp.Params.StartingFeeRate,
4✔
1014
                        func(feeRate chainfee.SatPerKWeight) uint64 {
7✔
1015
                                return uint64(feeRate.FeePerVByte())
3✔
1016
                        })
3✔
1017

1018
                ps := &PendingSweep{
4✔
1019
                        Outpoint:             op,
4✔
1020
                        WitnessType:          witnessType,
4✔
1021
                        AmountSat:            amountSat,
4✔
1022
                        SatPerVbyte:          satPerVbyte,
4✔
1023
                        BroadcastAttempts:    broadcastAttempts,
4✔
1024
                        Immediate:            inp.Params.Immediate,
4✔
1025
                        Budget:               uint64(inp.Params.Budget),
4✔
1026
                        DeadlineHeight:       inp.DeadlineHeight,
4✔
1027
                        RequestedSatPerVbyte: startingFeeRate,
4✔
1028
                }
4✔
1029
                rpcPendingSweeps = append(rpcPendingSweeps, ps)
4✔
1030
        }
1031

1032
        return &PendingSweepsResponse{
4✔
1033
                PendingSweeps: rpcPendingSweeps,
4✔
1034
        }, nil
4✔
1035
}
1036

1037
// UnmarshallOutPoint converts an outpoint from its lnrpc type to its canonical
1038
// type.
1039
func UnmarshallOutPoint(op *lnrpc.OutPoint) (*wire.OutPoint, error) {
4✔
1040
        if op == nil {
4✔
1041
                return nil, fmt.Errorf("empty outpoint provided")
×
1042
        }
×
1043

1044
        var hash chainhash.Hash
4✔
1045
        switch {
4✔
1046
        // Return an error if both txid fields are unpopulated.
1047
        case len(op.TxidBytes) == 0 && len(op.TxidStr) == 0:
×
1048
                return nil, fmt.Errorf("TxidBytes and TxidStr are both " +
×
1049
                        "unspecified")
×
1050

1051
        // The hash was provided as raw bytes.
1052
        case len(op.TxidBytes) != 0:
4✔
1053
                copy(hash[:], op.TxidBytes)
4✔
1054

1055
        // The hash was provided as a hex-encoded string.
1056
        case len(op.TxidStr) != 0:
×
1057
                h, err := chainhash.NewHashFromStr(op.TxidStr)
×
1058
                if err != nil {
×
1059
                        return nil, err
×
1060
                }
×
1061
                hash = *h
×
1062
        }
1063

1064
        return &wire.OutPoint{
4✔
1065
                Hash:  hash,
4✔
1066
                Index: op.OutputIndex,
4✔
1067
        }, nil
4✔
1068
}
1069

1070
// validateBumpFeeRequest makes sure the deprecated fields are not used when
1071
// the new fields are set.
1072
func validateBumpFeeRequest(in *BumpFeeRequest) (
1073
        fn.Option[chainfee.SatPerKWeight], bool, error) {
4✔
1074

4✔
1075
        // Get the specified fee rate if set.
4✔
1076
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
4✔
1077

4✔
1078
        // We only allow using either the deprecated field or the new field.
4✔
1079
        switch {
4✔
1080
        case in.SatPerByte != 0 && in.SatPerVbyte != 0:
×
1081
                return satPerKwOpt, false, fmt.Errorf("either SatPerByte or " +
×
1082
                        "SatPerVbyte should be set, but not both")
×
1083

1084
        case in.SatPerByte != 0:
×
1085
                satPerKw := chainfee.SatPerVByte(
×
1086
                        in.SatPerByte,
×
1087
                ).FeePerKWeight()
×
1088
                satPerKwOpt = fn.Some(satPerKw)
×
1089

1090
        case in.SatPerVbyte != 0:
3✔
1091
                satPerKw := chainfee.SatPerVByte(
3✔
1092
                        in.SatPerVbyte,
3✔
1093
                ).FeePerKWeight()
3✔
1094
                satPerKwOpt = fn.Some(satPerKw)
3✔
1095
        }
1096

1097
        var immediate bool
4✔
1098
        switch {
4✔
1099
        case in.Force && in.Immediate:
×
1100
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
1101
                        "Immediate should be set, but not both")
×
1102

1103
        case in.Force:
×
1104
                immediate = in.Force
×
1105

1106
        case in.Immediate:
4✔
1107
                immediate = in.Immediate
4✔
1108
        }
1109

1110
        return satPerKwOpt, immediate, nil
4✔
1111
}
1112

1113
// prepareSweepParams creates the sweep params to be used for the sweeper. It
1114
// returns the new params and a bool indicating whether this is an existing
1115
// input.
1116
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1117
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
4✔
1118

4✔
1119
        // Return an error if both deprecated and new fields are used.
4✔
1120
        feerate, immediate, err := validateBumpFeeRequest(in)
4✔
1121
        if err != nil {
4✔
1122
                return sweep.Params{}, false, err
×
1123
        }
×
1124

1125
        w.RLock()
4✔
1126
        defer w.RUnlock()
4✔
1127

4✔
1128
        // Get the current pending inputs.
4✔
1129
        inputMap, err := w.cfg.Sweeper.PendingInputs()
4✔
1130
        if err != nil {
4✔
1131
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1132
                        "pending inputs: %w", err)
×
1133
        }
×
1134

1135
        // Find the pending input.
1136
        //
1137
        // TODO(yy): act differently based on the state of the input?
1138
        inp, ok := inputMap[op]
4✔
1139

4✔
1140
        if !ok {
7✔
1141
                // NOTE: if this input doesn't exist and the new budget is not
3✔
1142
                // specified, the params would have a zero budget.
3✔
1143
                params := sweep.Params{
3✔
1144
                        Immediate:       immediate,
3✔
1145
                        StartingFeeRate: feerate,
3✔
1146
                        Budget:          btcutil.Amount(in.Budget),
3✔
1147
                }
3✔
1148
                if in.TargetConf != 0 {
3✔
1149
                        params.DeadlineHeight = fn.Some(
×
1150
                                int32(in.TargetConf) + currentHeight,
×
1151
                        )
×
1152
                }
×
1153

1154
                return params, ok, nil
3✔
1155
        }
1156

1157
        // Find the existing budget used for this input. Note that this value
1158
        // must be greater than zero.
1159
        budget := inp.Params.Budget
4✔
1160

4✔
1161
        // Set the new budget if specified.
4✔
1162
        if in.Budget != 0 {
8✔
1163
                budget = btcutil.Amount(in.Budget)
4✔
1164
        }
4✔
1165

1166
        // For an existing input, we assign it first, then overwrite it if
1167
        // a deadline is requested.
1168
        deadline := inp.Params.DeadlineHeight
4✔
1169

4✔
1170
        // Set the deadline if target conf is specified.
4✔
1171
        //
4✔
1172
        // TODO(yy): upgrade `falafel` so we can make this field optional. Atm
4✔
1173
        // we cannot distinguish between user's not setting the field and
4✔
1174
        // setting it to 0.
4✔
1175
        if in.TargetConf != 0 {
8✔
1176
                deadline = fn.Some(int32(in.TargetConf) + currentHeight)
4✔
1177
        }
4✔
1178

1179
        // Prepare the new sweep params.
1180
        //
1181
        // NOTE: if this input doesn't exist and the new budget is not
1182
        // specified, the params would have a zero budget.
1183
        params := sweep.Params{
4✔
1184
                Immediate:       immediate,
4✔
1185
                StartingFeeRate: feerate,
4✔
1186
                DeadlineHeight:  deadline,
4✔
1187
                Budget:          budget,
4✔
1188
        }
4✔
1189

4✔
1190
        if ok {
8✔
1191
                log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
4✔
1192
                        "params=%v, new params=%v", op, inp.Params, params)
4✔
1193
        }
4✔
1194

1195
        return params, ok, nil
4✔
1196
}
1197

1198
// BumpFee allows bumping the fee rate of an arbitrary input. A fee preference
1199
// can be expressed either as a specific fee rate or a delta of blocks in which
1200
// the output should be swept on-chain within. If a fee preference is not
1201
// explicitly specified, then an error is returned. The status of the input
1202
// sweep can be checked through the PendingSweeps RPC.
1203
func (w *WalletKit) BumpFee(ctx context.Context,
1204
        in *BumpFeeRequest) (*BumpFeeResponse, error) {
4✔
1205

4✔
1206
        // Parse the outpoint from the request.
4✔
1207
        op, err := UnmarshallOutPoint(in.Outpoint)
4✔
1208
        if err != nil {
4✔
1209
                return nil, err
×
1210
        }
×
1211

1212
        w.RLock()
4✔
1213
        defer w.RUnlock()
4✔
1214

4✔
1215
        // Get the current height so we can calculate the deadline height.
4✔
1216
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
4✔
1217
        if err != nil {
4✔
1218
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1219
                        err)
×
1220
        }
×
1221

1222
        // We now create a new sweeping params and update it in the sweeper.
1223
        // This will complicate the RBF conditions if this input has already
1224
        // been offered to sweeper before and it has already been included in a
1225
        // tx with other inputs. If this is the case, two results are possible:
1226
        // - either this input successfully RBFed the existing tx, or,
1227
        // - the budget of this input was not enough to RBF the existing tx.
1228
        params, existing, err := w.prepareSweepParams(in, *op, currentHeight)
4✔
1229
        if err != nil {
4✔
1230
                return nil, err
×
1231
        }
×
1232

1233
        // If this input exists, we will update its params.
1234
        if existing {
8✔
1235
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
4✔
1236
                if err != nil {
4✔
1237
                        return nil, err
×
1238
                }
×
1239

1240
                return &BumpFeeResponse{
4✔
1241
                        Status: "Successfully registered rbf-tx with sweeper",
4✔
1242
                }, nil
4✔
1243
        }
1244

1245
        // Otherwise, create a new sweeping request for this input.
1246
        err = w.sweepNewInput(op, uint32(currentHeight), params)
3✔
1247
        if err != nil {
3✔
1248
                return nil, err
×
1249
        }
×
1250

1251
        return &BumpFeeResponse{
3✔
1252
                Status: "Successfully registered CPFP-tx with the sweeper",
3✔
1253
        }, nil
3✔
1254
}
1255

1256
// getWaitingCloseChannel returns the waiting close channel in case it does
1257
// exist in the underlying channel state database.
1258
func (w *WalletKit) getWaitingCloseChannel(
1259
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
3✔
1260

3✔
1261
        // Fetch all channels, which still have their commitment transaction not
3✔
1262
        // confirmed (waiting close channels).
3✔
1263
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
3✔
1264
        if err != nil {
3✔
1265
                return nil, err
×
1266
        }
×
1267

1268
        channel := fn.Find(func(c *channeldb.OpenChannel) bool {
6✔
1269
                return c.FundingOutpoint == chanPoint
3✔
1270
        }, chans)
3✔
1271

1272
        return channel.UnwrapOrErr(errors.New("channel not found"))
3✔
1273
}
1274

1275
// BumpForceCloseFee bumps the fee rate of an unconfirmed anchor channel. It
1276
// updates the new fee rate parameters with the sweeper subsystem. Additionally
1277
// it will try to create anchor cpfp transactions for all possible commitment
1278
// transactions (local, remote, remote-dangling) so depending on which
1279
// commitment is in the local mempool only one of them will succeed in being
1280
// broadcasted.
1281
func (w *WalletKit) BumpForceCloseFee(_ context.Context,
1282
        in *BumpForceCloseFeeRequest) (*BumpForceCloseFeeResponse, error) {
3✔
1283

3✔
1284
        if in.ChanPoint == nil {
3✔
1285
                return nil, fmt.Errorf("no chan_point provided")
×
1286
        }
×
1287

1288
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
3✔
1289
        if err != nil {
3✔
1290
                return nil, err
×
1291
        }
×
1292

1293
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
3✔
1294
        if err != nil {
3✔
1295
                return nil, err
×
1296
        }
×
1297

1298
        // Get the relevant channel if it is in the waiting close state.
1299
        channel, err := w.getWaitingCloseChannel(*outPoint)
3✔
1300
        if err != nil {
3✔
1301
                return nil, err
×
1302
        }
×
1303

1304
        if !channel.ChanType.HasAnchors() {
3✔
1305
                return nil, fmt.Errorf("not able to bump the fee of a " +
×
1306
                        "non-anchor channel")
×
1307
        }
×
1308

1309
        // Match pending sweeps with commitments of the channel for which a bump
1310
        // is requested. Depending on the commitment state when force closing
1311
        // the channel we might have up to 3 commitments to consider when
1312
        // bumping the fee.
1313
        commitSet := fn.NewSet[chainhash.Hash]()
3✔
1314

3✔
1315
        if channel.LocalCommitment.CommitTx != nil {
6✔
1316
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
3✔
1317
                commitSet.Add(localTxID)
3✔
1318
        }
3✔
1319

1320
        if channel.RemoteCommitment.CommitTx != nil {
6✔
1321
                remoteTxID := channel.RemoteCommitment.CommitTx.TxHash()
3✔
1322
                commitSet.Add(remoteTxID)
3✔
1323
        }
3✔
1324

1325
        // Check whether there was a dangling commitment at the time the channel
1326
        // was force closed.
1327
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
3✔
1328
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
3✔
1329
                return nil, err
×
1330
        }
×
1331

1332
        if remoteCommitDiff != nil {
3✔
1333
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1334
                commitSet.Add(hash)
×
1335
        }
×
1336

1337
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1338
        // sweep.
1339
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
3✔
1340
        if err != nil {
3✔
1341
                return nil, err
×
1342
        }
×
1343

1344
        // Get the current height so we can calculate the deadline height.
1345
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
3✔
1346
        if err != nil {
3✔
1347
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1348
                        err)
×
1349
        }
×
1350

1351
        pendingSweeps := maps.Values(inputsMap)
3✔
1352

3✔
1353
        // Discard everything except for the anchor sweeps.
3✔
1354
        anchors := fn.Filter(func(sweep *sweep.PendingInputResponse) bool {
6✔
1355
                // Only filter for anchor inputs because these are the only
3✔
1356
                // inputs which can be used to bump a closed unconfirmed
3✔
1357
                // commitment transaction.
3✔
1358
                if sweep.WitnessType != input.CommitmentAnchor &&
3✔
1359
                        sweep.WitnessType != input.TaprootAnchorSweepSpend {
3✔
1360

×
1361
                        return false
×
1362
                }
×
1363

1364
                return commitSet.Contains(sweep.OutPoint.Hash)
3✔
1365
        }, pendingSweeps)
1366

1367
        if len(anchors) == 0 {
3✔
1368
                return nil, fmt.Errorf("unable to find pending anchor outputs")
×
1369
        }
×
1370

1371
        // Filter all relevant anchor sweeps and update the sweep request.
1372
        for _, anchor := range anchors {
6✔
1373
                // Anchor cpfp bump request are predictable because they are
3✔
1374
                // swept separately hence not batched with other sweeps (they
3✔
1375
                // are marked with the exclusive group flag). Bumping the fee
3✔
1376
                // rate does not create any conflicting fee bump conditions.
3✔
1377
                // Either the rbf requirements are met or the bump is rejected
3✔
1378
                // by the mempool rules.
3✔
1379
                params, existing, err := w.prepareSweepParams(
3✔
1380
                        &BumpFeeRequest{
3✔
1381
                                Outpoint:    lnrpcOutpoint,
3✔
1382
                                TargetConf:  in.DeadlineDelta,
3✔
1383
                                SatPerVbyte: in.StartingFeerate,
3✔
1384
                                Immediate:   in.Immediate,
3✔
1385
                                Budget:      in.Budget,
3✔
1386
                        }, anchor.OutPoint, currentHeight,
3✔
1387
                )
3✔
1388
                if err != nil {
3✔
1389
                        return nil, err
×
1390
                }
×
1391

1392
                // There might be the case when an anchor sweep is confirmed
1393
                // between fetching the pending sweeps and preparing the sweep
1394
                // params. We log this case and proceed.
1395
                if !existing {
3✔
1396
                        log.Errorf("Sweep anchor input(%v) not known to the " +
×
1397
                                "sweeper subsystem")
×
1398
                        continue
×
1399
                }
1400

1401
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
3✔
1402
                if err != nil {
3✔
1403
                        return nil, err
×
1404
                }
×
1405
        }
1406

1407
        return &BumpForceCloseFeeResponse{
3✔
1408
                Status: "Successfully registered anchor-cpfp transaction to" +
3✔
1409
                        "bump channel force close transaction",
3✔
1410
        }, nil
3✔
1411
}
1412

1413
// sweepNewInput handles the case where an input is seen the first time by the
1414
// sweeper. It will fetch the output from the wallet and construct an input and
1415
// offer it to the sweeper.
1416
//
1417
// NOTE: if the budget is not set, the default budget ratio is used.
1418
func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
1419
        params sweep.Params) error {
3✔
1420

3✔
1421
        w.RLock()
3✔
1422
        defer w.RUnlock()
3✔
1423

3✔
1424
        log.Debugf("Attempting to sweep outpoint %s", op)
3✔
1425

3✔
1426
        // Since the sweeper is not aware of the input, we'll assume the user
3✔
1427
        // is attempting to bump an unconfirmed transaction's fee rate by
3✔
1428
        // sweeping an output within it under control of the wallet with a
3✔
1429
        // higher fee rate. In this case, this will be a CPFP.
3✔
1430
        //
3✔
1431
        // We'll gather all of the information required by the UtxoSweeper in
3✔
1432
        // order to sweep the output.
3✔
1433
        utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
3✔
1434
        if err != nil {
3✔
1435
                return err
×
1436
        }
×
1437

1438
        // We're only able to bump the fee of unconfirmed transactions.
1439
        if utxo.Confirmations > 0 {
3✔
1440
                return errors.New("unable to bump fee of a confirmed " +
×
1441
                        "transaction")
×
1442
        }
×
1443

1444
        // If there's no budget set, use the default value.
1445
        if params.Budget == 0 {
6✔
1446
                params.Budget = utxo.Value.MulF64(
3✔
1447
                        contractcourt.DefaultBudgetRatio,
3✔
1448
                )
3✔
1449
        }
3✔
1450

1451
        signDesc := &input.SignDescriptor{
3✔
1452
                Output: &wire.TxOut{
3✔
1453
                        PkScript: utxo.PkScript,
3✔
1454
                        Value:    int64(utxo.Value),
3✔
1455
                },
3✔
1456
                HashType: txscript.SigHashAll,
3✔
1457
        }
3✔
1458

3✔
1459
        var witnessType input.WitnessType
3✔
1460
        switch utxo.AddressType {
3✔
1461
        case lnwallet.WitnessPubKey:
×
1462
                witnessType = input.WitnessKeyHash
×
1463
        case lnwallet.NestedWitnessPubKey:
×
1464
                witnessType = input.NestedWitnessKeyHash
×
1465
        case lnwallet.TaprootPubkey:
3✔
1466
                witnessType = input.TaprootPubKeySpend
3✔
1467
                signDesc.HashType = txscript.SigHashDefault
3✔
1468
        default:
×
1469
                return fmt.Errorf("unknown input witness %v", op)
×
1470
        }
1471

1472
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
3✔
1473
                params)
3✔
1474

3✔
1475
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
3✔
1476
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
3✔
1477
                return err
×
1478
        }
×
1479

1480
        return nil
3✔
1481
}
1482

1483
// ListSweeps returns a list of the sweeps that our node has published.
1484
func (w *WalletKit) ListSweeps(ctx context.Context,
1485
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
4✔
1486

4✔
1487
        w.RLock()
4✔
1488
        defer w.RUnlock()
4✔
1489

4✔
1490
        sweeps, err := w.cfg.Sweeper.ListSweeps()
4✔
1491
        if err != nil {
4✔
1492
                return nil, err
×
1493
        }
×
1494

1495
        sweepTxns := make(map[string]bool)
4✔
1496
        for _, sweep := range sweeps {
8✔
1497
                sweepTxns[sweep.String()] = true
4✔
1498
        }
4✔
1499

1500
        // Some of our sweeps could have been replaced by fee, or dropped out
1501
        // of the mempool. Here, we lookup our wallet transactions so that we
1502
        // can match our list of sweeps against the list of transactions that
1503
        // the wallet is still tracking. Sweeps are currently always swept to
1504
        // the default wallet account.
1505
        transactions, err := w.cfg.Wallet.ListTransactionDetails(
4✔
1506
                in.StartHeight, btcwallet.UnconfirmedHeight,
4✔
1507
                lnwallet.DefaultAccountName,
4✔
1508
        )
4✔
1509
        if err != nil {
4✔
1510
                return nil, err
×
1511
        }
×
1512

1513
        var (
4✔
1514
                txids     []string
4✔
1515
                txDetails []*lnwallet.TransactionDetail
4✔
1516
        )
4✔
1517

4✔
1518
        for _, tx := range transactions {
8✔
1519
                _, ok := sweepTxns[tx.Hash.String()]
4✔
1520
                if !ok {
8✔
1521
                        continue
4✔
1522
                }
1523

1524
                // Add the txid or full tx details depending on whether we want
1525
                // verbose output or not.
1526
                if in.Verbose {
8✔
1527
                        txDetails = append(txDetails, tx)
4✔
1528
                } else {
8✔
1529
                        txids = append(txids, tx.Hash.String())
4✔
1530
                }
4✔
1531
        }
1532

1533
        if in.Verbose {
8✔
1534
                return &ListSweepsResponse{
4✔
1535
                        Sweeps: &ListSweepsResponse_TransactionDetails{
4✔
1536
                                TransactionDetails: lnrpc.RPCTransactionDetails(
4✔
1537
                                        txDetails,
4✔
1538
                                ),
4✔
1539
                        },
4✔
1540
                }, nil
4✔
1541
        }
4✔
1542

1543
        return &ListSweepsResponse{
4✔
1544
                Sweeps: &ListSweepsResponse_TransactionIds{
4✔
1545
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
4✔
1546
                                TransactionIds: txids,
4✔
1547
                        },
4✔
1548
                },
4✔
1549
        }, nil
4✔
1550
}
1551

1552
// LabelTransaction adds a label to a transaction.
1553
func (w *WalletKit) LabelTransaction(ctx context.Context,
1554
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
4✔
1555

4✔
1556
        // Check that the label provided in non-zero.
4✔
1557
        if len(req.Label) == 0 {
8✔
1558
                return nil, ErrZeroLabel
4✔
1559
        }
4✔
1560

1561
        // Validate the length of the non-zero label. We do not need to use the
1562
        // label returned here, because the original is non-zero so will not
1563
        // be replaced.
1564
        if _, err := labels.ValidateAPI(req.Label); err != nil {
4✔
1565
                return nil, err
×
1566
        }
×
1567

1568
        hash, err := chainhash.NewHash(req.Txid)
4✔
1569
        if err != nil {
4✔
1570
                return nil, err
×
1571
        }
×
1572

1573
        w.RLock()
4✔
1574
        defer w.RUnlock()
4✔
1575

4✔
1576
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
4✔
1577

4✔
1578
        return &LabelTransactionResponse{
4✔
1579
                Status: fmt.Sprintf("transaction label '%s' added", req.Label),
4✔
1580
        }, err
4✔
1581
}
1582

1583
// FundPsbt creates a fully populated PSBT that contains enough inputs to fund
1584
// the outputs specified in the template. There are three ways a user can
1585
// specify what we call the template (a list of inputs and outputs to use in the
1586
// PSBT): Either as a PSBT packet directly with no coin selection (using the
1587
// legacy "psbt" field), a PSBT with advanced coin selection support (using the
1588
// new "coin_select" field) or as a raw RPC message (using the "raw" field).
1589
// The legacy "psbt" and "raw" modes, the following restrictions apply:
1590
//  1. If there are no inputs specified in the template, coin selection is
1591
//     performed automatically.
1592
//  2. If the template does contain any inputs, it is assumed that full coin
1593
//     selection happened externally and no additional inputs are added. If the
1594
//     specified inputs aren't enough to fund the outputs with the given fee
1595
//     rate, an error is returned.
1596
//
1597
// The new "coin_select" mode does not have these restrictions and allows the
1598
// user to specify a PSBT with inputs and outputs and still perform coin
1599
// selection on top of that.
1600
// For all modes this RPC requires any inputs that are specified to be locked by
1601
// the user (if they belong to this node in the first place).
1602
// After either selecting or verifying the inputs, all input UTXOs are locked
1603
// with an internal app ID. A custom address type for change can be specified
1604
// for default accounts and single imported public keys (only P2TR for now).
1605
// Otherwise, P2WPKH will be used by default. No custom address type should be
1606
// provided for custom accounts as we will always generate the change address
1607
// using the coin selection key scope.
1608
//
1609
// NOTE: If this method returns without an error, it is the caller's
1610
// responsibility to either spend the locked UTXOs (by finalizing and then
1611
// publishing the transaction) or to unlock/release the locked UTXOs in case of
1612
// an error on the caller's side.
1613
func (w *WalletKit) FundPsbt(_ context.Context,
1614
        req *FundPsbtRequest) (*FundPsbtResponse, error) {
4✔
1615

4✔
1616
        w.RLock()
4✔
1617
        defer w.RUnlock()
4✔
1618

4✔
1619
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
4✔
1620
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
4✔
1621
        )
4✔
1622
        if err != nil {
4✔
1623
                return nil, err
×
1624
        }
×
1625

1626
        // Determine the desired transaction fee.
1627
        var feeSatPerKW chainfee.SatPerKWeight
4✔
1628
        switch {
4✔
1629
        // Estimate the fee by the target number of blocks to confirmation.
1630
        case req.GetTargetConf() != 0:
×
1631
                targetConf := req.GetTargetConf()
×
1632
                if targetConf < 2 {
×
1633
                        return nil, fmt.Errorf("confirmation target must be " +
×
1634
                                "greater than 1")
×
1635
                }
×
1636

1637
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
×
1638
                        targetConf,
×
1639
                )
×
1640
                if err != nil {
×
1641
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1642
                                err)
×
1643
                }
×
1644

1645
        // Convert the fee to sat/kW from the specified sat/vByte.
1646
        case req.GetSatPerVbyte() != 0:
4✔
1647
                feeSatPerKW = chainfee.SatPerKVByte(
4✔
1648
                        req.GetSatPerVbyte() * 1000,
4✔
1649
                ).FeePerKWeight()
4✔
1650

1651
        case req.GetSatPerKw() != 0:
×
1652
                feeSatPerKW = chainfee.SatPerKWeight(req.GetSatPerKw())
×
1653

1654
        default:
×
1655
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1656
                        "specify either target_conf, sat_per_vbyte or " +
×
1657
                        "sat_per_kw")
×
1658
        }
1659

1660
        // Then, we'll extract the minimum number of confirmations that each
1661
        // output we use to fund the transaction should satisfy.
1662
        minConfs, err := lnrpc.ExtractMinConfs(
4✔
1663
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
4✔
1664
        )
4✔
1665
        if err != nil {
4✔
1666
                return nil, err
×
1667
        }
×
1668

1669
        // We'll assume the PSBT will be funded by the default account unless
1670
        // otherwise specified.
1671
        account := lnwallet.DefaultAccountName
4✔
1672
        if req.Account != "" {
8✔
1673
                account = req.Account
4✔
1674
        }
4✔
1675

1676
        // There are three ways a user can specify what we call the template (a
1677
        // list of inputs and outputs to use in the PSBT): Either as a PSBT
1678
        // packet directly with no coin selection, a PSBT with coin selection or
1679
        // as a special RPC message. Find out which one the user wants to use,
1680
        // they are mutually exclusive.
1681
        switch {
4✔
1682
        // The template is specified as a PSBT. All we have to do is parse it.
1683
        case req.GetPsbt() != nil:
4✔
1684
                r := bytes.NewReader(req.GetPsbt())
4✔
1685
                packet, err := psbt.NewFromRawBytes(r, false)
4✔
1686
                if err != nil {
4✔
1687
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1688
                }
×
1689

1690
                // Run the actual funding process now, using the internal
1691
                // wallet.
1692
                return w.fundPsbtInternalWallet(
4✔
1693
                        account, keyScopeFromChangeAddressType(req.ChangeType),
4✔
1694
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
4✔
1695
                )
4✔
1696

1697
        // The template is specified as a PSBT with the intention to perform
1698
        // coin selection even if inputs are already present.
1699
        case req.GetCoinSelect() != nil:
4✔
1700
                coinSelectRequest := req.GetCoinSelect()
4✔
1701
                r := bytes.NewReader(coinSelectRequest.Psbt)
4✔
1702
                packet, err := psbt.NewFromRawBytes(r, false)
4✔
1703
                if err != nil {
4✔
1704
                        return nil, fmt.Errorf("could not parse PSBT: %w", err)
×
1705
                }
×
1706

1707
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
4✔
1708
                if numOutputs == 0 {
4✔
1709
                        return nil, fmt.Errorf("no outputs specified in " +
×
1710
                                "template")
×
1711
                }
×
1712

1713
                outputSum := int64(0)
4✔
1714
                for _, txOut := range packet.UnsignedTx.TxOut {
8✔
1715
                        outputSum += txOut.Value
4✔
1716
                }
4✔
1717
                if outputSum <= 0 {
4✔
1718
                        return nil, fmt.Errorf("output sum must be positive")
×
1719
                }
×
1720

1721
                var (
4✔
1722
                        changeIndex int32 = -1
4✔
1723
                        changeType  chanfunding.ChangeAddressType
4✔
1724
                )
4✔
1725
                switch t := coinSelectRequest.ChangeOutput.(type) {
4✔
1726
                // The user wants to use an existing output as change output.
1727
                case *PsbtCoinSelect_ExistingOutputIndex:
4✔
1728
                        if t.ExistingOutputIndex < 0 ||
4✔
1729
                                t.ExistingOutputIndex >= numOutputs {
4✔
1730

×
1731
                                return nil, fmt.Errorf("change output index "+
×
1732
                                        "out of range: %d",
×
1733
                                        t.ExistingOutputIndex)
×
1734
                        }
×
1735

1736
                        changeIndex = t.ExistingOutputIndex
4✔
1737

4✔
1738
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
4✔
1739
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
4✔
1740
                        if err != nil {
4✔
1741
                                return nil, fmt.Errorf("error parsing change "+
×
1742
                                        "script: %w", err)
×
1743
                        }
×
1744

1745
                        changeType = chanfunding.ExistingChangeAddress
4✔
1746

1747
                // The user wants to use a new output as change output.
1748
                case *PsbtCoinSelect_Add:
×
1749
                        // We already set the change index to -1 above to
×
1750
                        // indicate no change output should be used if possible
×
1751
                        // or a new one should be created if needed. So we only
×
1752
                        // need to parse the type of change output we want to
×
1753
                        // create.
×
1754
                        switch req.ChangeType {
×
1755
                        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
×
1756
                                changeType = chanfunding.P2TRChangeAddress
×
1757

1758
                        default:
×
1759
                                changeType = chanfunding.P2WKHChangeAddress
×
1760
                        }
1761

1762
                default:
×
1763
                        return nil, fmt.Errorf("unknown change output type")
×
1764
                }
1765

1766
                maxFeeRatio := chanfunding.DefaultMaxFeeRatio
4✔
1767

4✔
1768
                if req.MaxFeeRatio != 0 {
4✔
1769
                        maxFeeRatio = req.MaxFeeRatio
×
1770
                }
×
1771

1772
                // Run the actual funding process now, using the channel funding
1773
                // coin selection algorithm.
1774
                return w.fundPsbtCoinSelect(
4✔
1775
                        account, changeIndex, packet, minConfs, changeType,
4✔
1776
                        feeSatPerKW, coinSelectionStrategy, maxFeeRatio,
4✔
1777
                )
4✔
1778

1779
        // The template is specified as a RPC message. We need to create a new
1780
        // PSBT and copy the RPC information over.
1781
        case req.GetRaw() != nil:
4✔
1782
                tpl := req.GetRaw()
4✔
1783

4✔
1784
                txOut := make([]*wire.TxOut, 0, len(tpl.Outputs))
4✔
1785
                for addrStr, amt := range tpl.Outputs {
8✔
1786
                        addr, err := btcutil.DecodeAddress(
4✔
1787
                                addrStr, w.cfg.ChainParams,
4✔
1788
                        )
4✔
1789
                        if err != nil {
4✔
1790
                                return nil, fmt.Errorf("error parsing address "+
×
1791
                                        "%s for network %s: %v", addrStr,
×
1792
                                        w.cfg.ChainParams.Name, err)
×
1793
                        }
×
1794

1795
                        if !addr.IsForNet(w.cfg.ChainParams) {
4✔
1796
                                return nil, fmt.Errorf("address is not for %s",
×
1797
                                        w.cfg.ChainParams.Name)
×
1798
                        }
×
1799

1800
                        pkScript, err := txscript.PayToAddrScript(addr)
4✔
1801
                        if err != nil {
4✔
1802
                                return nil, fmt.Errorf("error getting pk "+
×
1803
                                        "script for address %s: %w", addrStr,
×
1804
                                        err)
×
1805
                        }
×
1806

1807
                        txOut = append(txOut, &wire.TxOut{
4✔
1808
                                Value:    int64(amt),
4✔
1809
                                PkScript: pkScript,
4✔
1810
                        })
4✔
1811
                }
1812

1813
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
4✔
1814
                for idx, in := range tpl.Inputs {
4✔
1815
                        op, err := UnmarshallOutPoint(in)
×
1816
                        if err != nil {
×
1817
                                return nil, fmt.Errorf("error parsing "+
×
1818
                                        "outpoint: %w", err)
×
1819
                        }
×
1820
                        txIn[idx] = op
×
1821
                }
1822

1823
                sequences := make([]uint32, len(txIn))
4✔
1824
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
4✔
1825
                if err != nil {
4✔
1826
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1827
                }
×
1828

1829
                // Run the actual funding process now, using the internal
1830
                // wallet.
1831
                return w.fundPsbtInternalWallet(
4✔
1832
                        account, keyScopeFromChangeAddressType(req.ChangeType),
4✔
1833
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
4✔
1834
                )
4✔
1835

1836
        default:
×
1837
                return nil, fmt.Errorf("transaction template missing, need " +
×
1838
                        "to specify either PSBT or raw TX template")
×
1839
        }
1840
}
1841

1842
// fundPsbtInternalWallet uses the "old" PSBT funding method of the internal
1843
// wallet that does not allow specifying custom inputs while selecting coins.
1844
func (w *WalletKit) fundPsbtInternalWallet(account string,
1845
        keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
1846
        feeSatPerKW chainfee.SatPerKWeight,
1847
        strategy base.CoinSelectionStrategy) (*FundPsbtResponse, error) {
4✔
1848

4✔
1849
        w.RLock()
4✔
1850
        defer w.RUnlock()
4✔
1851

4✔
1852
        // The RPC parsing part is now over. Several of the following operations
4✔
1853
        // require us to hold the global coin selection lock, so we do the rest
4✔
1854
        // of the tasks while holding the lock. The result is a list of locked
4✔
1855
        // UTXOs.
4✔
1856
        var response *FundPsbtResponse
4✔
1857
        err := w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
8✔
1858
                // In case the user did specify inputs, we need to make sure
4✔
1859
                // they are known to us, still unspent and not yet locked.
4✔
1860
                if len(packet.UnsignedTx.TxIn) > 0 {
8✔
1861
                        // Get a list of all unspent witness outputs.
4✔
1862
                        utxos, err := w.cfg.Wallet.ListUnspentWitness(
4✔
1863
                                minConfs, defaultMaxConf, account,
4✔
1864
                        )
4✔
1865
                        if err != nil {
4✔
1866
                                return err
×
1867
                        }
×
1868

1869
                        // filterFn makes sure utxos which are unconfirmed and
1870
                        // still used by the sweeper are not used.
1871
                        filterFn := func(u *lnwallet.Utxo) bool {
8✔
1872
                                // Confirmed utxos are always allowed.
4✔
1873
                                if u.Confirmations > 0 {
8✔
1874
                                        return true
4✔
1875
                                }
4✔
1876

1877
                                // Unconfirmed utxos in use by the sweeper are
1878
                                // not stable to use because they can be
1879
                                // replaced.
1880
                                if w.cfg.Sweeper.IsSweeperOutpoint(u.OutPoint) {
8✔
1881
                                        log.Warnf("Cannot use unconfirmed "+
4✔
1882
                                                "utxo=%v because it is "+
4✔
1883
                                                "unstable and could be "+
4✔
1884
                                                "replaced", u.OutPoint)
4✔
1885

4✔
1886
                                        return false
4✔
1887
                                }
4✔
1888

1889
                                return true
×
1890
                        }
1891

1892
                        eligibleUtxos := fn.Filter(filterFn, utxos)
4✔
1893

4✔
1894
                        // Validate all inputs against our known list of UTXOs
4✔
1895
                        // now.
4✔
1896
                        err = verifyInputsUnspent(
4✔
1897
                                packet.UnsignedTx.TxIn, eligibleUtxos,
4✔
1898
                        )
4✔
1899
                        if err != nil {
8✔
1900
                                return err
4✔
1901
                        }
4✔
1902
                }
1903

1904
                // currentHeight is needed to determine whether the internal
1905
                // wallet utxo is still unconfirmed.
1906
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
4✔
1907
                if err != nil {
4✔
1908
                        return fmt.Errorf("unable to retrieve current "+
×
1909
                                "height: %v", err)
×
1910
                }
×
1911

1912
                // restrictUnstableUtxos is a filter function which disallows
1913
                // the usage of unconfirmed outputs published (still in use) by
1914
                // the sweeper.
1915
                restrictUnstableUtxos := func(utxo wtxmgr.Credit) bool {
8✔
1916
                        // Wallet utxos which are unmined have a height
4✔
1917
                        // of -1.
4✔
1918
                        if utxo.Height != -1 && utxo.Height <= currentHeight {
8✔
1919
                                // Confirmed utxos are always allowed.
4✔
1920
                                return true
4✔
1921
                        }
4✔
1922

1923
                        // Utxos used by the sweeper are not used for
1924
                        // channel openings.
1925
                        allowed := !w.cfg.Sweeper.IsSweeperOutpoint(
4✔
1926
                                utxo.OutPoint,
4✔
1927
                        )
4✔
1928
                        if !allowed {
8✔
1929
                                log.Warnf("Cannot use unconfirmed "+
4✔
1930
                                        "utxo=%v because it is "+
4✔
1931
                                        "unstable and could be "+
4✔
1932
                                        "replaced", utxo.OutPoint)
4✔
1933
                        }
4✔
1934

1935
                        return allowed
4✔
1936
                }
1937

1938
                // We made sure the input from the user is as sane as possible.
1939
                // We can now ask the wallet to fund the TX. This will not yet
1940
                // lock any coins but might still change the wallet DB by
1941
                // generating a new change address.
1942
                changeIndex, err := w.cfg.Wallet.FundPsbt(
4✔
1943
                        packet, minConfs, feeSatPerKW, account, keyScope,
4✔
1944
                        strategy, restrictUnstableUtxos,
4✔
1945
                )
4✔
1946
                if err != nil {
8✔
1947
                        return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
4✔
1948
                }
4✔
1949

1950
                // Now we have obtained a set of coins that can be used to fund
1951
                // the TX. Let's lock them to be sure they aren't spent by the
1952
                // time the PSBT is published. This is the action we do here
1953
                // that could cause an error. Therefore, if some of the UTXOs
1954
                // cannot be locked, the rollback of the other's locks also
1955
                // happens in this function. If we ever need to do more after
1956
                // this function, we need to extract the rollback needs to be
1957
                // extracted into a defer.
1958
                outpoints := make([]wire.OutPoint, len(packet.UnsignedTx.TxIn))
4✔
1959
                for i, txIn := range packet.UnsignedTx.TxIn {
8✔
1960
                        outpoints[i] = txIn.PreviousOutPoint
4✔
1961
                }
4✔
1962

1963
                response, err = w.lockAndCreateFundingResponse(
4✔
1964
                        packet, outpoints, changeIndex,
4✔
1965
                )
4✔
1966

4✔
1967
                return err
4✔
1968
        })
1969
        if err != nil {
8✔
1970
                return nil, err
4✔
1971
        }
4✔
1972

1973
        return response, nil
4✔
1974
}
1975

1976
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
1977
// funding coin selection algorithm that allows specifying custom inputs while
1978
// selecting coins.
1979
//
1980
//nolint:funlen
1981
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
1982
        packet *psbt.Packet, minConfs int32,
1983
        changeType chanfunding.ChangeAddressType,
1984
        feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy,
1985
        maxFeeRatio float64) (*FundPsbtResponse, error) {
4✔
1986

4✔
1987
        // We want to make sure we don't select any inputs that are already
4✔
1988
        // specified in the template. To do that, we require those inputs to
4✔
1989
        // either not belong to this lnd at all or to be already locked through
4✔
1990
        // a manual lock call by the user. Either way, they should not appear in
4✔
1991
        // the list of unspent outputs.
4✔
1992
        err := w.assertNotAvailable(packet.UnsignedTx.TxIn, minConfs, account)
4✔
1993
        if err != nil {
4✔
1994
                return nil, err
×
1995
        }
×
1996

1997
        w.RLock()
4✔
1998
        defer w.RUnlock()
4✔
1999

4✔
2000
        // In case the user just specified the input outpoints of UTXOs we own,
4✔
2001
        // the fee estimation below will error out because the UTXO information
4✔
2002
        // is missing. We need to fetch the UTXO information from the wallet
4✔
2003
        // and add it to the PSBT. We ignore inputs we don't actually know as
4✔
2004
        // they could belong to another wallet.
4✔
2005
        err = w.cfg.Wallet.DecorateInputs(packet, false)
4✔
2006
        if err != nil {
4✔
2007
                return nil, fmt.Errorf("error decorating inputs: %w", err)
×
2008
        }
×
2009

2010
        // Before we select anything, we need to calculate the input, output and
2011
        // current weight amounts. While doing that we also ensure the PSBT has
2012
        // all the required information we require at this step.
2013
        var (
4✔
2014
                inputSum, outputSum btcutil.Amount
4✔
2015
                estimator           input.TxWeightEstimator
4✔
2016
        )
4✔
2017
        for i := range packet.Inputs {
8✔
2018
                in := packet.Inputs[i]
4✔
2019

4✔
2020
                err := btcwallet.EstimateInputWeight(&in, &estimator)
4✔
2021
                if err != nil {
4✔
2022
                        return nil, fmt.Errorf("error estimating input "+
×
2023
                                "weight: %w", err)
×
2024
                }
×
2025

2026
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
4✔
2027
        }
2028
        for i := range packet.UnsignedTx.TxOut {
8✔
2029
                out := packet.UnsignedTx.TxOut[i]
4✔
2030

4✔
2031
                estimator.AddOutput(out.PkScript)
4✔
2032
                outputSum += btcutil.Amount(out.Value)
4✔
2033
        }
4✔
2034

2035
        // The amount we want to fund is the total output sum plus the current
2036
        // fee estimate, minus the sum of any already specified inputs. Since we
2037
        // pass the estimator of the current transaction into the coin selection
2038
        // algorithm, we don't need to subtract the fees here.
2039
        fundingAmount := outputSum - inputSum
4✔
2040

4✔
2041
        var changeDustLimit btcutil.Amount
4✔
2042
        switch changeType {
4✔
2043
        case chanfunding.P2TRChangeAddress:
×
2044
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
×
2045

2046
        case chanfunding.P2WKHChangeAddress:
×
2047
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
×
2048

2049
        case chanfunding.ExistingChangeAddress:
4✔
2050
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
4✔
2051
                changeDustLimit = lnwallet.DustLimitForSize(
4✔
2052
                        len(changeOut.PkScript),
4✔
2053
                )
4✔
2054
        }
2055

2056
        // Do we already have enough inputs specified to pay for the TX as it
2057
        // is? In that case we only need to allocate any change, if there is
2058
        // any.
2059
        packetFeeNoChange := feeRate.FeeForWeight(estimator.Weight())
4✔
2060
        if inputSum >= outputSum+packetFeeNoChange {
4✔
2061
                // Calculate the packet's fee with a change output so, so we can
×
2062
                // let the coin selection algorithm decide whether to use a
×
2063
                // change output or not.
×
2064
                switch changeType {
×
2065
                case chanfunding.P2TRChangeAddress:
×
2066
                        estimator.AddP2TROutput()
×
2067

2068
                case chanfunding.P2WKHChangeAddress:
×
2069
                        estimator.AddP2WKHOutput()
×
2070
                }
2071
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
×
2072

×
2073
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
×
2074
                        inputSum, outputSum, packetFeeNoChange,
×
2075
                        packetFeeWithChange, changeDustLimit, changeType,
×
2076
                        maxFeeRatio,
×
2077
                )
×
2078
                if err != nil {
×
2079
                        return nil, fmt.Errorf("error calculating change "+
×
2080
                                "amount: %w", err)
×
2081
                }
×
2082

2083
                // We shouldn't get into this branch if the input sum isn't
2084
                // enough to pay for the current package without a change
2085
                // output. So this should never be non-zero.
2086
                if needMore != 0 {
×
2087
                        return nil, fmt.Errorf("internal error with change " +
×
2088
                                "amount calculation")
×
2089
                }
×
2090

2091
                if changeAmt > 0 {
×
2092
                        changeIndex, err = w.handleChange(
×
2093
                                packet, changeIndex, int64(changeAmt),
×
2094
                                changeType, account,
×
2095
                        )
×
2096
                        if err != nil {
×
2097
                                return nil, fmt.Errorf("error handling change "+
×
2098
                                        "amount: %w", err)
×
2099
                        }
×
2100
                }
2101

2102
                // We're done. Let's serialize and return the updated package.
2103
                return w.lockAndCreateFundingResponse(packet, nil, changeIndex)
×
2104
        }
2105

2106
        // The RPC parsing part is now over. Several of the following operations
2107
        // require us to hold the global coin selection lock, so we do the rest
2108
        // of the tasks while holding the lock. The result is a list of locked
2109
        // UTXOs.
2110
        var response *FundPsbtResponse
4✔
2111
        err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
8✔
2112
                // Get a list of all unspent witness outputs.
4✔
2113
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
4✔
2114
                        minConfs, defaultMaxConf, account,
4✔
2115
                )
4✔
2116
                if err != nil {
4✔
2117
                        return err
×
2118
                }
×
2119

2120
                coins := make([]base.Coin, len(utxos))
4✔
2121
                for i, utxo := range utxos {
8✔
2122
                        coins[i] = base.Coin{
4✔
2123
                                TxOut: wire.TxOut{
4✔
2124
                                        Value:    int64(utxo.Value),
4✔
2125
                                        PkScript: utxo.PkScript,
4✔
2126
                                },
4✔
2127
                                OutPoint: utxo.OutPoint,
4✔
2128
                        }
4✔
2129
                }
4✔
2130

2131
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
4✔
2132
                        feeRate, fundingAmount, changeDustLimit, coins,
4✔
2133
                        strategy, estimator, changeType, maxFeeRatio,
4✔
2134
                )
4✔
2135
                if err != nil {
4✔
2136
                        return fmt.Errorf("error selecting coins: %w", err)
×
2137
                }
×
2138

2139
                if changeAmount > 0 {
8✔
2140
                        changeIndex, err = w.handleChange(
4✔
2141
                                packet, changeIndex, int64(changeAmount),
4✔
2142
                                changeType, account,
4✔
2143
                        )
4✔
2144
                        if err != nil {
4✔
2145
                                return fmt.Errorf("error handling change "+
×
2146
                                        "amount: %w", err)
×
2147
                        }
×
2148
                }
2149

2150
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
4✔
2151
                for i := range selectedCoins {
8✔
2152
                        coin := selectedCoins[i]
4✔
2153
                        addedOutpoints[i] = coin.OutPoint
4✔
2154

4✔
2155
                        packet.UnsignedTx.TxIn = append(
4✔
2156
                                packet.UnsignedTx.TxIn, &wire.TxIn{
4✔
2157
                                        PreviousOutPoint: coin.OutPoint,
4✔
2158
                                },
4✔
2159
                        )
4✔
2160
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
4✔
2161
                                WitnessUtxo: &coin.TxOut,
4✔
2162
                        })
4✔
2163
                }
4✔
2164

2165
                // Now that we've added the bare TX inputs, we also need to add
2166
                // the more verbose input information to the packet, so a future
2167
                // signer doesn't need to do any lookups. We skip any inputs
2168
                // that our wallet doesn't own.
2169
                err = w.cfg.Wallet.DecorateInputs(packet, false)
4✔
2170
                if err != nil {
4✔
2171
                        return fmt.Errorf("error decorating inputs: %w", err)
×
2172
                }
×
2173

2174
                response, err = w.lockAndCreateFundingResponse(
4✔
2175
                        packet, addedOutpoints, changeIndex,
4✔
2176
                )
4✔
2177

4✔
2178
                return err
4✔
2179
        })
2180
        if err != nil {
4✔
2181
                return nil, err
×
2182
        }
×
2183

2184
        return response, nil
4✔
2185
}
2186

2187
// assertNotAvailable makes sure the specified inputs either don't belong to
2188
// this node or are already locked by the user.
2189
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2190
        account string) error {
4✔
2191

4✔
2192
        w.RLock()
4✔
2193
        defer w.RUnlock()
4✔
2194

4✔
2195
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
8✔
2196
                // Get a list of all unspent witness outputs.
4✔
2197
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
4✔
2198
                        minConfs, defaultMaxConf, account,
4✔
2199
                )
4✔
2200
                if err != nil {
4✔
2201
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2202
                }
×
2203

2204
                // We'll now check that none of the inputs specified in the
2205
                // template are available to us. That means they either don't
2206
                // belong to us or are already locked by the user.
2207
                for _, txIn := range inputs {
8✔
2208
                        for _, utxo := range utxos {
8✔
2209
                                if txIn.PreviousOutPoint == utxo.OutPoint {
4✔
2210
                                        return fmt.Errorf("input %v is not "+
×
2211
                                                "locked", txIn.PreviousOutPoint)
×
2212
                                }
×
2213
                        }
2214
                }
2215

2216
                return nil
4✔
2217
        })
2218
}
2219

2220
// lockAndCreateFundingResponse locks the given outpoints and creates a funding
2221
// response with the serialized PSBT, the change index and the locked UTXOs.
2222
func (w *WalletKit) lockAndCreateFundingResponse(packet *psbt.Packet,
2223
        newOutpoints []wire.OutPoint, changeIndex int32) (*FundPsbtResponse,
2224
        error) {
4✔
2225

4✔
2226
        w.RLock()
4✔
2227
        defer w.RUnlock()
4✔
2228

4✔
2229
        // Make sure we can properly serialize the packet. If this goes wrong
4✔
2230
        // then something isn't right with the inputs, and we probably shouldn't
4✔
2231
        // try to lock any of them.
4✔
2232
        var buf bytes.Buffer
4✔
2233
        err := packet.Serialize(&buf)
4✔
2234
        if err != nil {
4✔
2235
                return nil, fmt.Errorf("error serializing funded PSBT: %w", err)
×
2236
        }
×
2237

2238
        locks, err := lockInputs(w.cfg.Wallet, newOutpoints)
4✔
2239
        if err != nil {
4✔
2240
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2241
        }
×
2242

2243
        // Convert the lock leases to the RPC format.
2244
        rpcLocks := marshallLeases(locks)
4✔
2245

4✔
2246
        return &FundPsbtResponse{
4✔
2247
                FundedPsbt:        buf.Bytes(),
4✔
2248
                ChangeOutputIndex: changeIndex,
4✔
2249
                LockedUtxos:       rpcLocks,
4✔
2250
        }, nil
4✔
2251
}
2252

2253
// handleChange is a closure that either adds the non-zero change amount to an
2254
// existing output or creates a change output. The function returns the new
2255
// change output index if a new change output was added.
2256
func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
2257
        changeAmount int64, changeType chanfunding.ChangeAddressType,
2258
        changeAccount string) (int32, error) {
4✔
2259

4✔
2260
        // Does an existing output get the change?
4✔
2261
        if changeIndex >= 0 {
8✔
2262
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
4✔
2263
                changeOut.Value += changeAmount
4✔
2264

4✔
2265
                return changeIndex, nil
4✔
2266
        }
4✔
2267

NEW
2268
        w.RLock()
×
NEW
2269
        defer w.RUnlock()
×
NEW
2270

×
2271
        // The user requested a new change output.
×
2272
        addrType := addrTypeFromChangeAddressType(changeType)
×
2273
        changeAddr, err := w.cfg.Wallet.NewAddress(
×
2274
                addrType, true, changeAccount,
×
2275
        )
×
2276
        if err != nil {
×
2277
                return 0, fmt.Errorf("could not derive change address: %w", err)
×
2278
        }
×
2279

2280
        changeScript, err := txscript.PayToAddrScript(changeAddr)
×
2281
        if err != nil {
×
2282
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2283
        }
×
2284

2285
        // We need to add the derivation info for the change address in case it
2286
        // is a P2TR address. This is mostly to prove it's a bare BIP-0086
2287
        // address, which is required for some protocols (such as Taproot
2288
        // Assets).
2289
        pOut := psbt.POutput{}
×
2290
        _, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
×
2291
        if isTaprootChangeAddr {
×
2292
                changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
×
2293
                if err != nil {
×
2294
                        return 0, fmt.Errorf("could not get address info: %w",
×
2295
                                err)
×
2296
                }
×
2297

2298
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2299
                        changeAddrInfo,
×
2300
                )
×
2301
                if err != nil {
×
2302
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2303
                                "%w", err)
×
2304
                }
×
2305

2306
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2307
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2308
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2309
                        trDeriv,
×
2310
                }
×
2311
        }
2312

2313
        newChangeIndex := int32(len(packet.Outputs))
×
2314
        packet.UnsignedTx.TxOut = append(
×
2315
                packet.UnsignedTx.TxOut, &wire.TxOut{
×
2316
                        Value:    changeAmount,
×
2317
                        PkScript: changeScript,
×
2318
                },
×
2319
        )
×
2320
        packet.Outputs = append(packet.Outputs, pOut)
×
2321

×
2322
        return newChangeIndex, nil
×
2323
}
2324

2325
// marshallLeases converts the lock leases to the RPC format.
2326
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
4✔
2327
        rpcLocks := make([]*UtxoLease, len(locks))
4✔
2328
        for idx, lock := range locks {
8✔
2329
                lock := lock
4✔
2330

4✔
2331
                rpcLocks[idx] = &UtxoLease{
4✔
2332
                        Id:         lock.LockID[:],
4✔
2333
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
4✔
2334
                        Expiration: uint64(lock.Expiration.Unix()),
4✔
2335
                        PkScript:   lock.PkScript,
4✔
2336
                        Value:      uint64(lock.Value),
4✔
2337
                }
4✔
2338
        }
4✔
2339

2340
        return rpcLocks
4✔
2341
}
2342

2343
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2344
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2345
// it returns nil.
2346
func keyScopeFromChangeAddressType(
2347
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
4✔
2348

4✔
2349
        switch changeAddressType {
4✔
2350
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
4✔
2351
                return &waddrmgr.KeyScopeBIP0086
4✔
2352

2353
        default:
4✔
2354
                return nil
4✔
2355
        }
2356
}
2357

2358
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2359
// lnwallet.AddressType.
2360
func addrTypeFromChangeAddressType(
2361
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
×
2362

×
2363
        switch changeAddressType {
×
2364
        case chanfunding.P2TRChangeAddress:
×
2365
                return lnwallet.TaprootPubkey
×
2366

2367
        default:
×
2368
                return lnwallet.WitnessPubKey
×
2369
        }
2370
}
2371

2372
// SignPsbt expects a partial transaction with all inputs and outputs fully
2373
// declared and tries to sign all unsigned inputs that have all required fields
2374
// (UTXO information, BIP32 derivation information, witness or sig scripts)
2375
// set.
2376
// If no error is returned, the PSBT is ready to be given to the next signer or
2377
// to be finalized if lnd was the last signer.
2378
//
2379
// NOTE: This RPC only signs inputs (and only those it can sign), it does not
2380
// perform any other tasks (such as coin selection, UTXO locking or
2381
// input/output/fee value validation, PSBT finalization). Any input that is
2382
// incomplete will be skipped.
2383
func (w *WalletKit) SignPsbt(_ context.Context, req *SignPsbtRequest) (
2384
        *SignPsbtResponse, error) {
4✔
2385

4✔
2386
        packet, err := psbt.NewFromRawBytes(
4✔
2387
                bytes.NewReader(req.FundedPsbt), false,
4✔
2388
        )
4✔
2389
        if err != nil {
4✔
2390
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2391
                        req.FundedPsbt)
×
2392
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2393
        }
×
2394

2395
        // Before we attempt to sign the packet, ensure that every input either
2396
        // has a witness UTXO, or a non witness UTXO.
2397
        for idx := range packet.UnsignedTx.TxIn {
8✔
2398
                in := packet.Inputs[idx]
4✔
2399

4✔
2400
                // Doesn't have either a witness or non witness UTXO so we need
4✔
2401
                // to exit here as otherwise signing will fail.
4✔
2402
                if in.WitnessUtxo == nil && in.NonWitnessUtxo == nil {
8✔
2403
                        return nil, fmt.Errorf("input (index=%v) doesn't "+
4✔
2404
                                "specify any UTXO info", idx)
4✔
2405
                }
4✔
2406
        }
2407

2408
        w.RLock()
4✔
2409
        defer w.RUnlock()
4✔
2410

4✔
2411
        // Let the wallet do the heavy lifting. This will sign all inputs that
4✔
2412
        // we have the UTXO for. If some inputs can't be signed and don't have
4✔
2413
        // witness data attached, they will just be skipped.
4✔
2414
        signedInputs, err := w.cfg.Wallet.SignPsbt(packet)
4✔
2415
        if err != nil {
4✔
2416
                return nil, fmt.Errorf("error signing PSBT: %w", err)
×
2417
        }
×
2418

2419
        // Serialize the signed PSBT in both the packet and wire format.
2420
        var signedPsbtBytes bytes.Buffer
4✔
2421
        err = packet.Serialize(&signedPsbtBytes)
4✔
2422
        if err != nil {
4✔
2423
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2424
        }
×
2425

2426
        return &SignPsbtResponse{
4✔
2427
                SignedPsbt:   signedPsbtBytes.Bytes(),
4✔
2428
                SignedInputs: signedInputs,
4✔
2429
        }, nil
4✔
2430
}
2431

2432
// FinalizePsbt expects a partial transaction with all inputs and outputs fully
2433
// declared and tries to sign all inputs that belong to the wallet. Lnd must be
2434
// the last signer of the transaction. That means, if there are any unsigned
2435
// non-witness inputs or inputs without UTXO information attached or inputs
2436
// without witness data that do not belong to lnd's wallet, this method will
2437
// fail. If no error is returned, the PSBT is ready to be extracted and the
2438
// final TX within to be broadcast.
2439
//
2440
// NOTE: This method does NOT publish the transaction once finalized. It is the
2441
// caller's responsibility to either publish the transaction on success or
2442
// unlock/release any locked UTXOs in case of an error in this method.
2443
func (w *WalletKit) FinalizePsbt(_ context.Context,
2444
        req *FinalizePsbtRequest) (*FinalizePsbtResponse, error) {
4✔
2445

4✔
2446
        // We'll assume the PSBT was funded by the default account unless
4✔
2447
        // otherwise specified.
4✔
2448
        account := lnwallet.DefaultAccountName
4✔
2449
        if req.Account != "" {
4✔
2450
                account = req.Account
×
2451
        }
×
2452

2453
        // Parse the funded PSBT.
2454
        packet, err := psbt.NewFromRawBytes(
4✔
2455
                bytes.NewReader(req.FundedPsbt), false,
4✔
2456
        )
4✔
2457
        if err != nil {
4✔
2458
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2459
        }
×
2460

2461
        // The only check done at this level is to validate that the PSBT is
2462
        // not complete. The wallet performs all other checks.
2463
        if packet.IsComplete() {
4✔
2464
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2465
        }
×
2466

2467
        w.RLock()
4✔
2468
        defer w.RUnlock()
4✔
2469

4✔
2470
        // Let the wallet do the heavy lifting. This will sign all inputs that
4✔
2471
        // we have the UTXO for. If some inputs can't be signed and don't have
4✔
2472
        // witness data attached, this will fail.
4✔
2473
        err = w.cfg.Wallet.FinalizePsbt(packet, account)
4✔
2474
        if err != nil {
4✔
2475
                return nil, fmt.Errorf("error finalizing PSBT: %w", err)
×
2476
        }
×
2477

2478
        var (
4✔
2479
                finalPsbtBytes bytes.Buffer
4✔
2480
                finalTxBytes   bytes.Buffer
4✔
2481
        )
4✔
2482

4✔
2483
        // Serialize the finalized PSBT in both the packet and wire format.
4✔
2484
        err = packet.Serialize(&finalPsbtBytes)
4✔
2485
        if err != nil {
4✔
2486
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2487
        }
×
2488
        finalTx, err := psbt.Extract(packet)
4✔
2489
        if err != nil {
4✔
2490
                return nil, fmt.Errorf("unable to extract final TX: %w", err)
×
2491
        }
×
2492
        err = finalTx.Serialize(&finalTxBytes)
4✔
2493
        if err != nil {
4✔
2494
                return nil, fmt.Errorf("error serializing final TX: %w", err)
×
2495
        }
×
2496

2497
        return &FinalizePsbtResponse{
4✔
2498
                SignedPsbt: finalPsbtBytes.Bytes(),
4✔
2499
                RawFinalTx: finalTxBytes.Bytes(),
4✔
2500
        }, nil
4✔
2501
}
2502

2503
// marshalWalletAccount converts the properties of an account into its RPC
2504
// representation.
2505
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2506
        account *waddrmgr.AccountProperties) (*Account, error) {
4✔
2507

4✔
2508
        var addrType AddressType
4✔
2509
        switch account.KeyScope {
4✔
2510
        case waddrmgr.KeyScopeBIP0049Plus:
4✔
2511
                // No address schema present represents the traditional BIP-0049
4✔
2512
                // address derivation scheme.
4✔
2513
                if account.AddrSchema == nil {
8✔
2514
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
4✔
2515
                        break
4✔
2516
                }
2517

2518
                switch *account.AddrSchema {
4✔
2519
                case waddrmgr.KeyScopeBIP0049AddrSchema:
4✔
2520
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
4✔
2521

2522
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
4✔
2523
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
4✔
2524

2525
                default:
×
2526
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2527
                                *account.AddrSchema)
×
2528
                }
2529

2530
        case waddrmgr.KeyScopeBIP0084:
4✔
2531
                addrType = AddressType_WITNESS_PUBKEY_HASH
4✔
2532

2533
        case waddrmgr.KeyScopeBIP0086:
4✔
2534
                addrType = AddressType_TAPROOT_PUBKEY
4✔
2535

2536
        case internalScope:
4✔
2537
                addrType = AddressType_WITNESS_PUBKEY_HASH
4✔
2538

2539
        default:
×
2540
                return nil, fmt.Errorf("account %v has unsupported "+
×
2541
                        "key scope %v", account.AccountName, account.KeyScope)
×
2542
        }
2543

2544
        rpcAccount := &Account{
4✔
2545
                Name:             account.AccountName,
4✔
2546
                AddressType:      addrType,
4✔
2547
                ExternalKeyCount: account.ExternalKeyCount,
4✔
2548
                InternalKeyCount: account.InternalKeyCount,
4✔
2549
                WatchOnly:        account.IsWatchOnly,
4✔
2550
        }
4✔
2551

4✔
2552
        // The remaining fields can only be done on accounts other than the
4✔
2553
        // default imported one existing within each key scope.
4✔
2554
        if account.AccountName != waddrmgr.ImportedAddrAccountName {
8✔
2555
                nonHardenedIndex := account.AccountPubKey.ChildIndex() -
4✔
2556
                        hdkeychain.HardenedKeyStart
4✔
2557
                rpcAccount.ExtendedPublicKey = account.AccountPubKey.String()
4✔
2558
                if account.MasterKeyFingerprint != 0 {
4✔
2559
                        var mkfp [4]byte
×
2560
                        binary.BigEndian.PutUint32(
×
2561
                                mkfp[:], account.MasterKeyFingerprint,
×
2562
                        )
×
2563
                        rpcAccount.MasterKeyFingerprint = mkfp[:]
×
2564
                }
×
2565
                rpcAccount.DerivationPath = fmt.Sprintf("%v/%v'",
4✔
2566
                        account.KeyScope, nonHardenedIndex)
4✔
2567
        }
2568

2569
        return rpcAccount, nil
4✔
2570
}
2571

2572
// marshalWalletAddressList converts the list of address into its RPC
2573
// representation.
2574
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2575
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
4✔
2576

4✔
2577
        // Get the RPC representation of account.
4✔
2578
        rpcAccount, err := marshalWalletAccount(
4✔
2579
                w.internalScope(), account,
4✔
2580
        )
4✔
2581
        if err != nil {
4✔
2582
                return nil, err
×
2583
        }
×
2584

2585
        addresses := make([]*AddressProperty, len(addressList))
4✔
2586
        for idx, addr := range addressList {
8✔
2587
                var pubKeyBytes []byte
4✔
2588
                if addr.PublicKey != nil {
8✔
2589
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
4✔
2590
                }
4✔
2591
                addresses[idx] = &AddressProperty{
4✔
2592
                        Address:        addr.Address,
4✔
2593
                        IsInternal:     addr.Internal,
4✔
2594
                        Balance:        int64(addr.Balance),
4✔
2595
                        DerivationPath: addr.DerivationPath,
4✔
2596
                        PublicKey:      pubKeyBytes,
4✔
2597
                }
4✔
2598
        }
2599

2600
        rpcAddressList := &AccountWithAddresses{
4✔
2601
                Name:           rpcAccount.Name,
4✔
2602
                AddressType:    rpcAccount.AddressType,
4✔
2603
                DerivationPath: rpcAccount.DerivationPath,
4✔
2604
                Addresses:      addresses,
4✔
2605
        }
4✔
2606

4✔
2607
        return rpcAddressList, nil
4✔
2608
}
2609

2610
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2611
// name and key scope filter can be provided to filter through all of the wallet
2612
// accounts and return only those matching.
2613
func (w *WalletKit) ListAccounts(ctx context.Context,
2614
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
4✔
2615

4✔
2616
        // Map the supported address types into their corresponding key scope.
4✔
2617
        var keyScopeFilter *waddrmgr.KeyScope
4✔
2618
        switch req.AddressType {
4✔
2619
        case AddressType_UNKNOWN:
4✔
2620
                break
4✔
2621

2622
        case AddressType_WITNESS_PUBKEY_HASH:
4✔
2623
                keyScope := waddrmgr.KeyScopeBIP0084
4✔
2624
                keyScopeFilter = &keyScope
4✔
2625

2626
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2627
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
4✔
2628

4✔
2629
                keyScope := waddrmgr.KeyScopeBIP0049Plus
4✔
2630
                keyScopeFilter = &keyScope
4✔
2631

2632
        case AddressType_TAPROOT_PUBKEY:
4✔
2633
                keyScope := waddrmgr.KeyScopeBIP0086
4✔
2634
                keyScopeFilter = &keyScope
4✔
2635

2636
        default:
×
2637
                return nil, fmt.Errorf("unhandled address type %v",
×
2638
                        req.AddressType)
×
2639
        }
2640

2641
        w.RLock()
4✔
2642
        defer w.RUnlock()
4✔
2643

4✔
2644
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
4✔
2645
        if err != nil {
4✔
2646
                return nil, err
×
2647
        }
×
2648

2649
        rpcAccounts := make([]*Account, 0, len(accounts))
4✔
2650
        for _, account := range accounts {
8✔
2651
                // Don't include the default imported accounts created by the
4✔
2652
                // wallet in the response if they don't have any keys imported.
4✔
2653
                if account.AccountName == waddrmgr.ImportedAddrAccountName &&
4✔
2654
                        account.ImportedKeyCount == 0 {
8✔
2655

4✔
2656
                        continue
4✔
2657
                }
2658

2659
                rpcAccount, err := marshalWalletAccount(
4✔
2660
                        w.internalScope(), account,
4✔
2661
                )
4✔
2662
                if err != nil {
4✔
2663
                        return nil, err
×
2664
                }
×
2665
                rpcAccounts = append(rpcAccounts, rpcAccount)
4✔
2666
        }
2667

2668
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
4✔
2669
}
2670

2671
// RequiredReserve returns the minimum amount of satoshis that should be
2672
// kept in the wallet in order to fee bump anchor channels if necessary.
2673
// The value scales with the number of public anchor channels but is
2674
// capped at a maximum.
2675
func (w *WalletKit) RequiredReserve(ctx context.Context,
2676
        req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
4✔
2677

4✔
2678
        w.RLock()
4✔
2679
        defer w.RUnlock()
4✔
2680

4✔
2681
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
4✔
2682
        if err != nil {
4✔
2683
                return nil, err
×
2684
        }
×
2685

2686
        additionalChans := req.AdditionalPublicChannels
4✔
2687
        totalChans := uint32(numAnchorChans) + additionalChans
4✔
2688
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
4✔
2689

4✔
2690
        return &RequiredReserveResponse{
4✔
2691
                RequiredReserve: int64(reserved),
4✔
2692
        }, nil
4✔
2693
}
2694

2695
// ListAddresses retrieves all the addresses along with their balance. An
2696
// account name filter can be provided to filter through all of the
2697
// wallet accounts and return the addresses of only those matching.
2698
func (w *WalletKit) ListAddresses(ctx context.Context,
2699
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
4✔
2700

4✔
2701
        w.RLock()
4✔
2702
        defer w.RUnlock()
4✔
2703

4✔
2704
        addressLists, err := w.cfg.Wallet.ListAddresses(
4✔
2705
                req.AccountName,
4✔
2706
                req.ShowCustomAccounts,
4✔
2707
        )
4✔
2708
        if err != nil {
4✔
2709
                return nil, err
×
2710
        }
×
2711

2712
        // Create a slice of accounts from addressLists map.
2713
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
4✔
2714
        for account := range addressLists {
8✔
2715
                accounts = append(accounts, account)
4✔
2716
        }
4✔
2717

2718
        // Sort the accounts by derivation path.
2719
        sort.Slice(accounts, func(i, j int) bool {
8✔
2720
                scopeI := accounts[i].KeyScope
4✔
2721
                scopeJ := accounts[j].KeyScope
4✔
2722
                if scopeI.Purpose == scopeJ.Purpose {
4✔
2723
                        if scopeI.Coin == scopeJ.Coin {
×
2724
                                acntNumI := accounts[i].AccountNumber
×
2725
                                acntNumJ := accounts[j].AccountNumber
×
2726
                                return acntNumI < acntNumJ
×
2727
                        }
×
2728

2729
                        return scopeI.Coin < scopeJ.Coin
×
2730
                }
2731

2732
                return scopeI.Purpose < scopeJ.Purpose
4✔
2733
        })
2734

2735
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
4✔
2736
        for _, account := range accounts {
8✔
2737
                addressList := addressLists[account]
4✔
2738
                rpcAddressList, err := marshalWalletAddressList(
4✔
2739
                        w, account, addressList,
4✔
2740
                )
4✔
2741
                if err != nil {
4✔
2742
                        return nil, err
×
2743
                }
×
2744

2745
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
4✔
2746
        }
2747

2748
        return &ListAddressesResponse{
4✔
2749
                AccountWithAddresses: rpcAddressLists,
4✔
2750
        }, nil
4✔
2751
}
2752

2753
// parseAddrType parses an address type from its RPC representation to a
2754
// *waddrmgr.AddressType.
2755
func parseAddrType(addrType AddressType,
2756
        required bool) (*waddrmgr.AddressType, error) {
4✔
2757

4✔
2758
        switch addrType {
4✔
2759
        case AddressType_UNKNOWN:
×
2760
                if required {
×
2761
                        return nil, fmt.Errorf("an address type must be " +
×
2762
                                "specified")
×
2763
                }
×
2764
                return nil, nil
×
2765

2766
        case AddressType_WITNESS_PUBKEY_HASH:
4✔
2767
                addrTyp := waddrmgr.WitnessPubKey
4✔
2768
                return &addrTyp, nil
4✔
2769

2770
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
4✔
2771
                addrTyp := waddrmgr.NestedWitnessPubKey
4✔
2772
                return &addrTyp, nil
4✔
2773

2774
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
4✔
2775
                addrTyp := waddrmgr.WitnessPubKey
4✔
2776
                return &addrTyp, nil
4✔
2777

2778
        case AddressType_TAPROOT_PUBKEY:
4✔
2779
                addrTyp := waddrmgr.TaprootPubKey
4✔
2780
                return &addrTyp, nil
4✔
2781

2782
        default:
×
2783
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2784
        }
2785
}
2786

2787
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2788
// transaction or a signature. It is prepended in front of the message and
2789
// follows the same standard as bitcoin core and btcd.
2790
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2791

2792
// SignMessageWithAddr signs a message with the private key of the provided
2793
// address. The address needs to belong to the lnd wallet.
2794
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2795
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
4✔
2796

4✔
2797
        w.RLock()
4✔
2798
        defer w.RUnlock()
4✔
2799

4✔
2800
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
4✔
2801
        if err != nil {
4✔
2802
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2803
        }
×
2804

2805
        if !addr.IsForNet(w.cfg.ChainParams) {
4✔
2806
                return nil, fmt.Errorf("encoded address is for "+
×
2807
                        "the wrong network %s", req.Addr)
×
2808
        }
×
2809

2810
        // Fetch address infos from own wallet and check whether it belongs
2811
        // to the lnd wallet.
2812
        managedAddr, err := w.cfg.Wallet.AddressInfo(addr)
4✔
2813
        if err != nil {
4✔
2814
                return nil, fmt.Errorf("address could not be found in the "+
×
2815
                        "wallet database: %w", err)
×
2816
        }
×
2817

2818
        // Verifying by checking the interface type that the wallet knows about
2819
        // the public and private keys so it can sign the message with the
2820
        // private key of this address.
2821
        pubKey, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress)
4✔
2822
        if !ok {
4✔
2823
                return nil, fmt.Errorf("private key to address is unknown")
×
2824
        }
×
2825

2826
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
4✔
2827
        if err != nil {
4✔
2828
                return nil, err
×
2829
        }
×
2830

2831
        // For all address types (P2WKH, NP2WKH,P2TR) the ECDSA compact signing
2832
        // algorithm is used. For P2TR addresses this represents a special case.
2833
        // ECDSA is used to create a compact signature which makes the public
2834
        // key of the signature recoverable. For Schnorr no known compact
2835
        // signing algorithm exists yet.
2836
        privKey, err := pubKey.PrivKey()
4✔
2837
        if err != nil {
4✔
2838
                return nil, fmt.Errorf("no private key could be "+
×
2839
                        "fetched from wallet database: %w", err)
×
2840
        }
×
2841

2842
        sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
4✔
2843

4✔
2844
        // Bitcoin signatures are base64 encoded (being compatible with
4✔
2845
        // bitcoin-core and btcd).
4✔
2846
        sig := base64.StdEncoding.EncodeToString(sigBytes)
4✔
2847

4✔
2848
        return &SignMessageWithAddrResponse{
4✔
2849
                Signature: sig,
4✔
2850
        }, nil
4✔
2851
}
2852

2853
// VerifyMessageWithAddr verifies a signature on a message with a provided
2854
// address, it checks both the validity of the signature itself and then
2855
// verifies whether the signature corresponds to the public key of the
2856
// provided address. There is no dependence on the private key of the address
2857
// therefore also external addresses are allowed to verify signatures.
2858
// Supported address types are P2PKH, P2WKH, NP2WKH, P2TR.
2859
func (w *WalletKit) VerifyMessageWithAddr(_ context.Context,
2860
        req *VerifyMessageWithAddrRequest) (*VerifyMessageWithAddrResponse,
2861
        error) {
4✔
2862

4✔
2863
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
4✔
2864
        if err != nil {
4✔
2865
                return nil, fmt.Errorf("malformed base64 encoding of "+
×
2866
                        "the signature: %w", err)
×
2867
        }
×
2868

2869
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
4✔
2870
        if err != nil {
4✔
2871
                return nil, err
×
2872
        }
×
2873

2874
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
4✔
2875
        if err != nil {
4✔
2876
                return nil, fmt.Errorf("unable to recover public key "+
×
2877
                        "from compact signature: %w", err)
×
2878
        }
×
2879

2880
        var serializedPubkey []byte
4✔
2881
        if wasCompressed {
8✔
2882
                serializedPubkey = pk.SerializeCompressed()
4✔
2883
        } else {
4✔
2884
                serializedPubkey = pk.SerializeUncompressed()
×
2885
        }
×
2886

2887
        w.RLock()
4✔
2888
        defer w.RUnlock()
4✔
2889

4✔
2890
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
4✔
2891
        if err != nil {
4✔
2892
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2893
        }
×
2894

2895
        if !addr.IsForNet(w.cfg.ChainParams) {
4✔
2896
                return nil, fmt.Errorf("encoded address is for"+
×
2897
                        "the wrong network %s", req.Addr)
×
2898
        }
×
2899

2900
        var (
4✔
2901
                address    btcutil.Address
4✔
2902
                pubKeyHash = btcutil.Hash160(serializedPubkey)
4✔
2903
        )
4✔
2904

4✔
2905
        // Ensure the address is one of the supported types.
4✔
2906
        switch addr.(type) {
4✔
2907
        case *btcutil.AddressPubKeyHash:
4✔
2908
                address, err = btcutil.NewAddressPubKeyHash(
4✔
2909
                        pubKeyHash, w.cfg.ChainParams,
4✔
2910
                )
4✔
2911
                if err != nil {
4✔
2912
                        return nil, err
×
2913
                }
×
2914

2915
        case *btcutil.AddressWitnessPubKeyHash:
4✔
2916
                address, err = btcutil.NewAddressWitnessPubKeyHash(
4✔
2917
                        pubKeyHash, w.cfg.ChainParams,
4✔
2918
                )
4✔
2919
                if err != nil {
4✔
2920
                        return nil, err
×
2921
                }
×
2922

2923
        case *btcutil.AddressScriptHash:
4✔
2924
                // Check if address is a Nested P2WKH (NP2WKH).
4✔
2925
                address, err = btcutil.NewAddressWitnessPubKeyHash(
4✔
2926
                        pubKeyHash, w.cfg.ChainParams,
4✔
2927
                )
4✔
2928
                if err != nil {
4✔
2929
                        return nil, err
×
2930
                }
×
2931

2932
                witnessScript, err := txscript.PayToAddrScript(address)
4✔
2933
                if err != nil {
4✔
2934
                        return nil, err
×
2935
                }
×
2936

2937
                address, err = btcutil.NewAddressScriptHashFromHash(
4✔
2938
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
4✔
2939
                )
4✔
2940
                if err != nil {
4✔
2941
                        return nil, err
×
2942
                }
×
2943

2944
        case *btcutil.AddressTaproot:
4✔
2945
                // Only addresses without a tapscript are allowed because
4✔
2946
                // the verification is using the internal key.
4✔
2947
                tapKey := txscript.ComputeTaprootKeyNoScript(pk)
4✔
2948
                address, err = btcutil.NewAddressTaproot(
4✔
2949
                        schnorr.SerializePubKey(tapKey),
4✔
2950
                        w.cfg.ChainParams,
4✔
2951
                )
4✔
2952
                if err != nil {
4✔
2953
                        return nil, err
×
2954
                }
×
2955

2956
        default:
×
2957
                return nil, fmt.Errorf("unsupported address type")
×
2958
        }
2959

2960
        return &VerifyMessageWithAddrResponse{
4✔
2961
                Valid:  req.Addr == address.EncodeAddress(),
4✔
2962
                Pubkey: serializedPubkey,
4✔
2963
        }, nil
4✔
2964
}
2965

2966
// ImportAccount imports an account backed by an account extended public key.
2967
// The master key fingerprint denotes the fingerprint of the root key
2968
// corresponding to the account public key (also known as the key with
2969
// derivation path m/). This may be required by some hardware wallets for proper
2970
// identification and signing.
2971
//
2972
// The address type can usually be inferred from the key's version, but may be
2973
// required for certain keys to map them into the proper scope.
2974
//
2975
// For BIP-0044 keys, an address type must be specified as we intend to not
2976
// support importing BIP-0044 keys into the wallet using the legacy
2977
// pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force
2978
// the standard BIP-0049 derivation scheme, while a witness address type will
2979
// force the standard BIP-0084 derivation scheme.
2980
//
2981
// For BIP-0049 keys, an address type must also be specified to make a
2982
// distinction between the standard BIP-0049 address schema (nested witness
2983
// pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys
2984
// externally, witness pubkeys internally).
2985
func (w *WalletKit) ImportAccount(_ context.Context,
2986
        req *ImportAccountRequest) (*ImportAccountResponse, error) {
4✔
2987

4✔
2988
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
4✔
2989
        if err != nil {
4✔
2990
                return nil, err
×
2991
        }
×
2992

2993
        var mkfp uint32
4✔
2994
        switch len(req.MasterKeyFingerprint) {
4✔
2995
        // No master key fingerprint provided, which is fine as it's not
2996
        // required.
2997
        case 0:
4✔
2998
        // Expected length.
2999
        case 4:
×
3000
                mkfp = binary.BigEndian.Uint32(req.MasterKeyFingerprint)
×
3001
        default:
×
3002
                return nil, errors.New("invalid length for master key " +
×
3003
                        "fingerprint, expected 4 bytes in big-endian")
×
3004
        }
3005

3006
        addrType, err := parseAddrType(req.AddressType, false)
4✔
3007
        if err != nil {
4✔
3008
                return nil, err
×
3009
        }
×
3010

3011
        w.RLock()
4✔
3012
        defer w.RUnlock()
4✔
3013

4✔
3014
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
4✔
3015
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
4✔
3016
        )
4✔
3017
        if err != nil {
8✔
3018
                return nil, err
4✔
3019
        }
4✔
3020

3021
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
4✔
3022
        if err != nil {
4✔
3023
                return nil, err
×
3024
        }
×
3025

3026
        resp := &ImportAccountResponse{Account: rpcAccount}
4✔
3027
        if !req.DryRun {
8✔
3028
                return resp, nil
4✔
3029
        }
4✔
3030

3031
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
3032
        for i := 0; i < len(extAddrs); i++ {
×
3033
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
3034
        }
×
3035
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
3036
        for i := 0; i < len(intAddrs); i++ {
×
3037
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
3038
        }
×
3039

3040
        return resp, nil
×
3041
}
3042

3043
// ImportPublicKey imports a single derived public key into the wallet. The
3044
// address type can usually be inferred from the key's version, but in the case
3045
// of legacy versions (xpub, tpub), an address type must be specified as we
3046
// intend to not support importing BIP-44 keys into the wallet using the legacy
3047
// pay-to-pubkey-hash (P2PKH) scheme. For Taproot keys, this will only watch
3048
// the BIP-0086 style output script. Use ImportTapscript for more advanced key
3049
// spend or script spend outputs.
3050
func (w *WalletKit) ImportPublicKey(_ context.Context,
3051
        req *ImportPublicKeyRequest) (*ImportPublicKeyResponse, error) {
4✔
3052

4✔
3053
        var (
4✔
3054
                pubKey *btcec.PublicKey
4✔
3055
                err    error
4✔
3056
        )
4✔
3057
        switch req.AddressType {
4✔
3058
        case AddressType_TAPROOT_PUBKEY:
4✔
3059
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
4✔
3060

3061
        default:
4✔
3062
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
4✔
3063
        }
3064
        if err != nil {
4✔
3065
                return nil, err
×
3066
        }
×
3067

3068
        addrType, err := parseAddrType(req.AddressType, true)
4✔
3069
        if err != nil {
4✔
3070
                return nil, err
×
3071
        }
×
3072

3073
        w.RLock()
4✔
3074
        defer w.RUnlock()
4✔
3075

4✔
3076
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
4✔
3077
                return nil, err
×
3078
        }
×
3079

3080
        return &ImportPublicKeyResponse{
4✔
3081
                Status: fmt.Sprintf("public key %x imported",
4✔
3082
                        pubKey.SerializeCompressed()),
4✔
3083
        }, nil
4✔
3084
}
3085

3086
// ImportTapscript imports a Taproot script and internal key and adds the
3087
// resulting Taproot output key as a watch-only output script into the wallet.
3088
// For BIP-0086 style Taproot keys (no root hash commitment and no script spend
3089
// path) use ImportPublicKey.
3090
//
3091
// NOTE: Taproot keys imported through this RPC currently _cannot_ be used for
3092
// funding PSBTs. Only tracking the balance and UTXOs is currently supported.
3093
func (w *WalletKit) ImportTapscript(_ context.Context,
3094
        req *ImportTapscriptRequest) (*ImportTapscriptResponse, error) {
4✔
3095

4✔
3096
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
4✔
3097
        if err != nil {
4✔
3098
                return nil, fmt.Errorf("error parsing internal key: %w", err)
×
3099
        }
×
3100

3101
        var tapscript *waddrmgr.Tapscript
4✔
3102
        switch {
4✔
3103
        case req.GetFullTree() != nil:
4✔
3104
                tree := req.GetFullTree()
4✔
3105
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
4✔
3106
                for idx, leaf := range tree.AllLeaves {
8✔
3107
                        leaves[idx] = txscript.TapLeaf{
4✔
3108
                                LeafVersion: txscript.TapscriptLeafVersion(
4✔
3109
                                        leaf.LeafVersion,
4✔
3110
                                ),
4✔
3111
                                Script: leaf.Script,
4✔
3112
                        }
4✔
3113
                }
4✔
3114

3115
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
4✔
3116

3117
        case req.GetPartialReveal() != nil:
4✔
3118
                partialReveal := req.GetPartialReveal()
4✔
3119
                if partialReveal.RevealedLeaf == nil {
4✔
3120
                        return nil, fmt.Errorf("missing revealed leaf")
×
3121
                }
×
3122

3123
                revealedLeaf := txscript.TapLeaf{
4✔
3124
                        LeafVersion: txscript.TapscriptLeafVersion(
4✔
3125
                                partialReveal.RevealedLeaf.LeafVersion,
4✔
3126
                        ),
4✔
3127
                        Script: partialReveal.RevealedLeaf.Script,
4✔
3128
                }
4✔
3129
                if len(partialReveal.FullInclusionProof)%32 != 0 {
4✔
3130
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
3131
                                "length, expected multiple of 32, got %d",
×
3132
                                len(partialReveal.FullInclusionProof)%32)
×
3133
                }
×
3134

3135
                tapscript = input.TapscriptPartialReveal(
4✔
3136
                        internalKey, revealedLeaf,
4✔
3137
                        partialReveal.FullInclusionProof,
4✔
3138
                )
4✔
3139

3140
        case req.GetRootHashOnly() != nil:
4✔
3141
                rootHash := req.GetRootHashOnly()
4✔
3142
                if len(rootHash) == 0 {
4✔
3143
                        return nil, fmt.Errorf("missing root hash")
×
3144
                }
×
3145

3146
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
4✔
3147

3148
        case req.GetFullKeyOnly():
4✔
3149
                tapscript = input.TapscriptFullKeyOnly(internalKey)
4✔
3150

3151
        default:
×
3152
                return nil, fmt.Errorf("invalid script")
×
3153
        }
3154

3155
        w.RLock()
4✔
3156
        defer w.RUnlock()
4✔
3157

4✔
3158
        taprootScope := waddrmgr.KeyScopeBIP0086
4✔
3159
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
4✔
3160
        if err != nil {
4✔
3161
                return nil, fmt.Errorf("error importing script into wallet: %w",
×
3162
                        err)
×
3163
        }
×
3164

3165
        return &ImportTapscriptResponse{
4✔
3166
                P2TrAddress: addr.Address().String(),
4✔
3167
        }, nil
4✔
3168
}
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