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

lightningnetwork / lnd / 15205630088

23 May 2025 08:14AM UTC coverage: 57.45% (-11.5%) from 68.996%
15205630088

Pull #9784

github

web-flow
Merge f8b9f36a3 into c52a6ddeb
Pull Request #9784: [wip] lnwallet+walletrpc: add SubmitPackage and related RPC call

47 of 96 new or added lines in 5 files covered. (48.96%)

30087 existing lines in 459 files now uncovered.

95586 of 166380 relevant lines covered (57.45%)

0.61 hits per line

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

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

4
package walletrpc
5

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

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

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

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

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

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

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

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

261
        cfg *Config
262
}
263

264
// A compile time check to ensure that WalletKit fully implements the
265
// WalletKitServer gRPC service.
266
var _ WalletKitServer = (*WalletKit)(nil)
267

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

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

1✔
285
                log.Infof("Baking macaroons for WalletKit RPC Server at: %v",
1✔
286
                        macFilePath)
1✔
287

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

309
        walletKit := &WalletKit{
1✔
310
                cfg: cfg,
1✔
311
        }
1✔
312

1✔
313
        return walletKit, macPermissions, nil
1✔
314
}
315

316
// Start launches any helper goroutines required for the sub-server to function.
317
//
318
// NOTE: This is part of the lnrpc.SubServer interface.
319
func (w *WalletKit) Start() error {
1✔
320
        return nil
1✔
321
}
1✔
322

323
// Stop signals any active goroutines for a graceful closure.
324
//
325
// NOTE: This is part of the lnrpc.SubServer interface.
326
func (w *WalletKit) Stop() error {
1✔
327
        return nil
1✔
328
}
1✔
329

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

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

1✔
348
        log.Debugf("WalletKit RPC server successfully registered with " +
1✔
349
                "root gRPC server")
1✔
350

1✔
351
        return nil
1✔
352
}
1✔
353

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

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

371
        log.Debugf("WalletKit REST server successfully registered with " +
1✔
372
                "root REST server")
1✔
373
        return nil
1✔
374
}
375

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

1✔
386
        subServer, macPermissions, err := createNewSubServer(configRegistry)
1✔
387
        if err != nil {
1✔
388
                return nil, nil, err
×
389
        }
×
390

391
        r.WalletKitServer = subServer
1✔
392
        return subServer, macPermissions, nil
1✔
393
}
394

395
// internalScope returns the internal key scope.
396
func (w *WalletKit) internalScope() waddrmgr.KeyScope {
1✔
397
        return waddrmgr.KeyScope{
1✔
398
                Purpose: keychain.BIP0043Purpose,
1✔
399
                Coin:    w.cfg.ChainParams.HDCoinType,
1✔
400
        }
1✔
401
}
1✔
402

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

1✔
412
        // Force min_confs and max_confs to be zero if unconfirmed_only is
1✔
413
        // true.
1✔
414
        if req.UnconfirmedOnly && (req.MinConfs != 0 || req.MaxConfs != 0) {
1✔
415
                return nil, fmt.Errorf("min_confs and max_confs must be zero " +
×
416
                        "if unconfirmed_only is true")
×
417
        }
×
418

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

426
        // Validate the confirmation arguments.
427
        minConfs, maxConfs, err := lnrpc.ParseConfs(req.MinConfs, req.MaxConfs)
1✔
428
        if err != nil {
1✔
429
                return nil, err
×
430
        }
×
431

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

1✔
444
                return err
1✔
445
        })
1✔
446
        if err != nil {
1✔
447
                return nil, err
×
448
        }
×
449

450
        rpcUtxos, err := lnrpc.MarshalUtxos(utxos, w.cfg.ChainParams)
1✔
451
        if err != nil {
1✔
452
                return nil, err
×
453
        }
×
454

455
        return &ListUnspentResponse{
1✔
456
                Utxos: rpcUtxos,
1✔
457
        }, nil
1✔
458
}
459

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

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

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

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

489
        op, err := UnmarshallOutPoint(req.Outpoint)
×
490
        if err != nil {
×
491
                return nil, err
×
492
        }
×
493

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

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

513
        return &LeaseOutputResponse{
×
514
                Expiration: uint64(expiration.Unix()),
×
515
        }, nil
×
516
}
517

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

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

×
530
        op, err := UnmarshallOutPoint(req.Outpoint)
×
531
        if err != nil {
×
532
                return nil, err
×
533
        }
×
534

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

544
        return &ReleaseOutputResponse{
×
545
                Status: fmt.Sprintf("output %v released", op.String()),
×
546
        }, nil
×
547
}
548

549
// ListLeases returns a list of all currently locked utxos.
550
func (w *WalletKit) ListLeases(ctx context.Context,
551
        req *ListLeasesRequest) (*ListLeasesResponse, error) {
1✔
552

1✔
553
        leases, err := w.cfg.Wallet.ListLeasedOutputs()
1✔
554
        if err != nil {
1✔
555
                return nil, err
×
556
        }
×
557

558
        return &ListLeasesResponse{
1✔
559
                LockedUtxos: marshallLeases(leases),
1✔
560
        }, nil
1✔
561
}
562

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

1✔
569
        nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey(
1✔
570
                keychain.KeyFamily(req.KeyFamily),
1✔
571
        )
1✔
572
        if err != nil {
1✔
573
                return nil, err
×
574
        }
×
575

576
        return &signrpc.KeyDescriptor{
1✔
577
                KeyLoc: &signrpc.KeyLocator{
1✔
578
                        KeyFamily: int32(nextKeyDesc.Family),
1✔
579
                        KeyIndex:  int32(nextKeyDesc.Index),
1✔
580
                },
1✔
581
                RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(),
1✔
582
        }, nil
1✔
583
}
584

585
// DeriveKey attempts to derive an arbitrary key specified by the passed
586
// KeyLocator.
587
func (w *WalletKit) DeriveKey(ctx context.Context,
588
        req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) {
1✔
589

1✔
590
        keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{
1✔
591
                Family: keychain.KeyFamily(req.KeyFamily),
1✔
592
                Index:  uint32(req.KeyIndex),
1✔
593
        })
1✔
594
        if err != nil {
1✔
595
                return nil, err
×
596
        }
×
597

598
        return &signrpc.KeyDescriptor{
1✔
599
                KeyLoc: &signrpc.KeyLocator{
1✔
600
                        KeyFamily: int32(keyDesc.Family),
1✔
601
                        KeyIndex:  int32(keyDesc.Index),
1✔
602
                },
1✔
603
                RawKeyBytes: keyDesc.PubKey.SerializeCompressed(),
1✔
604
        }, nil
1✔
605
}
606

607
// NextAddr returns the next unused address within the wallet.
608
func (w *WalletKit) NextAddr(ctx context.Context,
609
        req *AddrRequest) (*AddrResponse, error) {
1✔
610

1✔
611
        account := lnwallet.DefaultAccountName
1✔
612
        if req.Account != "" {
1✔
613
                account = req.Account
×
614
        }
×
615

616
        addrType := lnwallet.WitnessPubKey
1✔
617
        switch req.Type {
1✔
618
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
×
619
                addrType = lnwallet.NestedWitnessPubKey
×
620

621
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
×
622
                return nil, fmt.Errorf("invalid address type for next "+
×
623
                        "address: %v", req.Type)
×
624

625
        case AddressType_TAPROOT_PUBKEY:
×
626
                addrType = lnwallet.TaprootPubkey
×
627
        }
628

629
        addr, err := w.cfg.Wallet.NewAddress(addrType, req.Change, account)
1✔
630
        if err != nil {
1✔
631
                return nil, err
×
632
        }
×
633

634
        return &AddrResponse{
1✔
635
                Addr: addr.String(),
1✔
636
        }, nil
1✔
637
}
638

639
// GetTransaction returns a transaction from the wallet given its hash.
640
func (w *WalletKit) GetTransaction(_ context.Context,
641
        req *GetTransactionRequest) (*lnrpc.Transaction, error) {
1✔
642

1✔
643
        // If the client doesn't specify a hash, then there's nothing to
1✔
644
        // return.
1✔
645
        if req.Txid == "" {
1✔
646
                return nil, fmt.Errorf("must provide a transaction hash")
×
647
        }
×
648

649
        txHash, err := chainhash.NewHashFromStr(req.Txid)
1✔
650
        if err != nil {
1✔
651
                return nil, err
×
652
        }
×
653

654
        res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
1✔
655
        if err != nil {
1✔
656
                return nil, err
×
657
        }
×
658

659
        return lnrpc.RPCTransaction(res), nil
1✔
660
}
661

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

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

676
        tx := &wire.MsgTx{}
1✔
677
        txReader := bytes.NewReader(req.TxHex)
1✔
678
        if err := tx.Deserialize(txReader); err != nil {
1✔
679
                return nil, err
×
680
        }
×
681

682
        label, err := labels.ValidateAPI(req.Label)
1✔
683
        if err != nil {
1✔
684
                return nil, err
×
685
        }
×
686

687
        err = w.cfg.Wallet.PublishTransaction(tx, label)
1✔
688
        if err != nil {
1✔
689
                return nil, err
×
690
        }
×
691

692
        return &PublishResponse{}, nil
1✔
693
}
694

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

1✔
708
        // If the client doesn't specify a hash, then there's nothing to
1✔
709
        // return.
1✔
710
        if req.Txid == "" {
1✔
711
                return nil, fmt.Errorf("must provide a transaction hash")
×
712
        }
×
713

714
        txHash, err := chainhash.NewHashFromStr(req.Txid)
1✔
715
        if err != nil {
1✔
716
                return nil, err
×
717
        }
×
718

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

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

737
        tx := &wire.MsgTx{}
1✔
738
        txReader := bytes.NewReader(res.RawTx)
1✔
739
        if err := tx.Deserialize(txReader); err != nil {
1✔
740
                return nil, err
×
741
        }
×
742

743
        err = w.cfg.Wallet.RemoveDescendants(tx)
1✔
744
        if err != nil {
1✔
745
                return nil, err
×
746
        }
×
747

748
        return &RemoveTransactionResponse{
1✔
749
                Status: "Successfully removed transaction",
1✔
750
        }, nil
1✔
751
}
752

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

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

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

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

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

799
        // We'll get the currently required reserve amount.
800
        reserve, err := w.RequiredReserve(ctx, &RequiredReserveRequest{})
1✔
801
        if err != nil {
1✔
802
                return nil, err
×
803
        }
×
804

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

811
        label, err := labels.ValidateAPI(req.Label)
1✔
812
        if err != nil {
1✔
813
                return nil, err
×
814
        }
×
815

816
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
1✔
817
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
1✔
818
        )
1✔
819
        if err != nil {
1✔
820
                return nil, err
×
821
        }
×
822

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

834
        var b bytes.Buffer
1✔
835
        if err := tx.Serialize(&b); err != nil {
1✔
836
                return nil, err
×
837
        }
×
838

839
        return &SendOutputsResponse{
1✔
840
                RawTx: b.Bytes(),
1✔
841
        }, nil
1✔
842
}
843

844
// EstimateFee attempts to query the internal fee estimator of the wallet to
845
// determine the fee (in sat/kw) to attach to a transaction in order to achieve
846
// the confirmation target.
847
func (w *WalletKit) EstimateFee(ctx context.Context,
848
        req *EstimateFeeRequest) (*EstimateFeeResponse, error) {
×
849

×
850
        switch {
×
851
        // A confirmation target of zero doesn't make any sense. Similarly, we
852
        // reject confirmation targets of 1 as they're unreasonable.
853
        case req.ConfTarget == 0 || req.ConfTarget == 1:
×
854
                return nil, fmt.Errorf("confirmation target must be greater " +
×
855
                        "than 1")
×
856
        }
857

858
        satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW(
×
859
                uint32(req.ConfTarget),
×
860
        )
×
861
        if err != nil {
×
862
                return nil, err
×
863
        }
×
864

865
        relayFeePerKw := w.cfg.FeeEstimator.RelayFeePerKW()
×
866

×
867
        return &EstimateFeeResponse{
×
868
                SatPerKw:            int64(satPerKw),
×
869
                MinRelayFeeSatPerKw: int64(relayFeePerKw),
×
870
        }, nil
×
871
}
872

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

1✔
881
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1✔
882
        // sweep.
1✔
883
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
1✔
884
        if err != nil {
1✔
885
                return nil, err
×
886
        }
×
887

888
        // Convert them into their respective RPC format.
889
        rpcPendingSweeps := make([]*PendingSweep, 0, len(inputsMap))
1✔
890
        for _, inp := range inputsMap {
2✔
891
                witnessType, ok := allWitnessTypes[inp.WitnessType]
1✔
892
                if !ok {
1✔
893
                        return nil, fmt.Errorf("unhandled witness type %v for "+
×
894
                                "input %v", inp.WitnessType, inp.OutPoint)
×
895
                }
×
896

897
                op := lnrpc.MarshalOutPoint(&inp.OutPoint)
1✔
898
                amountSat := uint32(inp.Amount)
1✔
899
                satPerVbyte := uint64(inp.LastFeeRate.FeePerVByte())
1✔
900
                broadcastAttempts := uint32(inp.BroadcastAttempts)
1✔
901

1✔
902
                // Get the requested starting fee rate, if set.
1✔
903
                startingFeeRate := fn.MapOptionZ(
1✔
904
                        inp.Params.StartingFeeRate,
1✔
905
                        func(feeRate chainfee.SatPerKWeight) uint64 {
2✔
906
                                return uint64(feeRate.FeePerVByte())
1✔
907
                        })
1✔
908

909
                ps := &PendingSweep{
1✔
910
                        Outpoint:             op,
1✔
911
                        WitnessType:          witnessType,
1✔
912
                        AmountSat:            amountSat,
1✔
913
                        SatPerVbyte:          satPerVbyte,
1✔
914
                        BroadcastAttempts:    broadcastAttempts,
1✔
915
                        Immediate:            inp.Params.Immediate,
1✔
916
                        Budget:               uint64(inp.Params.Budget),
1✔
917
                        DeadlineHeight:       inp.DeadlineHeight,
1✔
918
                        RequestedSatPerVbyte: startingFeeRate,
1✔
919
                        MaturityHeight:       inp.MaturityHeight,
1✔
920
                }
1✔
921
                rpcPendingSweeps = append(rpcPendingSweeps, ps)
1✔
922
        }
923

924
        return &PendingSweepsResponse{
1✔
925
                PendingSweeps: rpcPendingSweeps,
1✔
926
        }, nil
1✔
927
}
928

929
// UnmarshallOutPoint converts an outpoint from its lnrpc type to its canonical
930
// type.
931
func UnmarshallOutPoint(op *lnrpc.OutPoint) (*wire.OutPoint, error) {
1✔
932
        if op == nil {
1✔
933
                return nil, fmt.Errorf("empty outpoint provided")
×
934
        }
×
935

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

943
        // The hash was provided as raw bytes.
944
        case len(op.TxidBytes) != 0:
1✔
945
                copy(hash[:], op.TxidBytes)
1✔
946

947
        // The hash was provided as a hex-encoded string.
948
        case len(op.TxidStr) != 0:
×
949
                h, err := chainhash.NewHashFromStr(op.TxidStr)
×
950
                if err != nil {
×
951
                        return nil, err
×
952
                }
×
953
                hash = *h
×
954
        }
955

956
        return &wire.OutPoint{
1✔
957
                Hash:  hash,
1✔
958
                Index: op.OutputIndex,
1✔
959
        }, nil
1✔
960
}
961

962
// validateBumpFeeRequest makes sure the deprecated fields are not used when
963
// the new fields are set.
964
func validateBumpFeeRequest(in *BumpFeeRequest, estimator chainfee.Estimator) (
965
        fn.Option[chainfee.SatPerKWeight], bool, error) {
1✔
966

1✔
967
        // Get the specified fee rate if set.
1✔
968
        satPerKwOpt := fn.None[chainfee.SatPerKWeight]()
1✔
969

1✔
970
        // We only allow using either the deprecated field or the new field.
1✔
971
        switch {
1✔
972
        case in.SatPerByte != 0 && in.SatPerVbyte != 0:
×
973
                return satPerKwOpt, false, fmt.Errorf("either SatPerByte or " +
×
974
                        "SatPerVbyte should be set, but not both")
×
975

976
        case in.SatPerByte != 0:
×
977
                satPerKw := chainfee.SatPerVByte(
×
978
                        in.SatPerByte,
×
979
                ).FeePerKWeight()
×
980
                satPerKwOpt = fn.Some(satPerKw)
×
981

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

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

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

1010
                // Set the starting fee rate to the estimated fee rate.
1011
                satPerKwOpt = fn.Some(startingFeeRate)
1✔
1012
        }
1013

1014
        var immediate bool
1✔
1015
        switch {
1✔
1016
        case in.Force && in.Immediate:
×
1017
                return satPerKwOpt, false, fmt.Errorf("either Force or " +
×
1018
                        "Immediate should be set, but not both")
×
1019

1020
        case in.Force:
×
1021
                immediate = in.Force
×
1022

1023
        case in.Immediate:
1✔
1024
                immediate = in.Immediate
1✔
1025
        }
1026

1027
        if in.DeadlineDelta != 0 && in.Budget == 0 {
1✔
1028
                return satPerKwOpt, immediate, fmt.Errorf("budget must be " +
×
1029
                        "set if deadline-delta is set")
×
1030
        }
×
1031

1032
        return satPerKwOpt, immediate, nil
1✔
1033
}
1034

1035
// prepareSweepParams creates the sweep params to be used for the sweeper. It
1036
// returns the new params and a bool indicating whether this is an existing
1037
// input.
1038
func (w *WalletKit) prepareSweepParams(in *BumpFeeRequest,
1039
        op wire.OutPoint, currentHeight int32) (sweep.Params, bool, error) {
1✔
1040

1✔
1041
        // Return an error if the bump fee request is invalid.
1✔
1042
        feeRate, immediate, err := validateBumpFeeRequest(
1✔
1043
                in, w.cfg.FeeEstimator,
1✔
1044
        )
1✔
1045
        if err != nil {
1✔
1046
                return sweep.Params{}, false, err
×
1047
        }
×
1048

1049
        // Get the current pending inputs.
1050
        inputMap, err := w.cfg.Sweeper.PendingInputs()
1✔
1051
        if err != nil {
1✔
1052
                return sweep.Params{}, false, fmt.Errorf("unable to get "+
×
1053
                        "pending inputs: %w", err)
×
1054
        }
×
1055

1056
        // Find the pending input.
1057
        //
1058
        // TODO(yy): act differently based on the state of the input?
1059
        inp, ok := inputMap[op]
1✔
1060

1✔
1061
        if !ok {
2✔
1062
                // NOTE: if this input doesn't exist and the new budget is not
1✔
1063
                // specified, the params would have a zero budget.
1✔
1064
                params := sweep.Params{
1✔
1065
                        Immediate:       immediate,
1✔
1066
                        StartingFeeRate: feeRate,
1✔
1067
                        Budget:          btcutil.Amount(in.Budget),
1✔
1068
                }
1✔
1069

1✔
1070
                if in.DeadlineDelta != 0 {
2✔
1071
                        params.DeadlineHeight = fn.Some(
1✔
1072
                                int32(in.DeadlineDelta) + currentHeight,
1✔
1073
                        )
1✔
1074
                }
1✔
1075

1076
                return params, ok, nil
1✔
1077
        }
1078

1079
        // Find the existing budget used for this input. Note that this value
1080
        // must be greater than zero.
1081
        budget := inp.Params.Budget
1✔
1082

1✔
1083
        // Set the new budget if specified. If a new deadline delta is
1✔
1084
        // specified we also require the budget value which is checked in the
1✔
1085
        // validateBumpFeeRequest function.
1✔
1086
        if in.Budget != 0 {
2✔
1087
                budget = btcutil.Amount(in.Budget)
1✔
1088
        }
1✔
1089

1090
        // For an existing input, we assign it first, then overwrite it if
1091
        // a deadline is requested.
1092
        deadline := inp.Params.DeadlineHeight
1✔
1093

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

1103
        startingFeeRate := inp.Params.StartingFeeRate
1✔
1104

1✔
1105
        // We only set the starting fee rate if it was specified else we keep
1✔
1106
        // the existing one.
1✔
1107
        if feeRate.IsSome() {
2✔
1108
                startingFeeRate = feeRate
1✔
1109
        }
1✔
1110

1111
        // Prepare the new sweep params.
1112
        //
1113
        // NOTE: if this input doesn't exist and the new budget is not
1114
        // specified, the params would have a zero budget.
1115
        params := sweep.Params{
1✔
1116
                Immediate:       immediate,
1✔
1117
                DeadlineHeight:  deadline,
1✔
1118
                StartingFeeRate: startingFeeRate,
1✔
1119
                Budget:          budget,
1✔
1120
        }
1✔
1121

1✔
1122
        log.Infof("[BumpFee]: bumping fee for existing input=%v, old "+
1✔
1123
                "params=%v, new params=%v", op, inp.Params, params)
1✔
1124

1✔
1125
        return params, ok, nil
1✔
1126
}
1127

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

1✔
1136
        // Parse the outpoint from the request.
1✔
1137
        op, err := UnmarshallOutPoint(in.Outpoint)
1✔
1138
        if err != nil {
1✔
1139
                return nil, err
×
1140
        }
×
1141

1142
        // Get the current height so we can calculate the deadline height.
1143
        _, currentHeight, err := w.cfg.Chain.GetBestBlock()
1✔
1144
        if err != nil {
1✔
1145
                return nil, fmt.Errorf("unable to retrieve current height: %w",
×
1146
                        err)
×
1147
        }
×
1148

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

1160
        // If this input exists, we will update its params.
1161
        if existing {
2✔
1162
                _, err = w.cfg.Sweeper.UpdateParams(*op, params)
1✔
1163
                if err != nil {
1✔
1164
                        return nil, err
×
1165
                }
×
1166

1167
                return &BumpFeeResponse{
1✔
1168
                        Status: "Successfully registered rbf-tx with sweeper",
1✔
1169
                }, nil
1✔
1170
        }
1171

1172
        // Otherwise, create a new sweeping request for this input.
1173
        err = w.sweepNewInput(op, uint32(currentHeight), params)
1✔
1174
        if err != nil {
1✔
1175
                return nil, err
×
1176
        }
×
1177

1178
        return &BumpFeeResponse{
1✔
1179
                Status: "Successfully registered CPFP-tx with the sweeper",
1✔
1180
        }, nil
1✔
1181
}
1182

1183
// getWaitingCloseChannel returns the waiting close channel in case it does
1184
// exist in the underlying channel state database.
1185
func (w *WalletKit) getWaitingCloseChannel(
1186
        chanPoint wire.OutPoint) (*channeldb.OpenChannel, error) {
1✔
1187

1✔
1188
        // Fetch all channels, which still have their commitment transaction not
1✔
1189
        // confirmed (waiting close channels).
1✔
1190
        chans, err := w.cfg.ChanStateDB.FetchWaitingCloseChannels()
1✔
1191
        if err != nil {
1✔
1192
                return nil, err
×
1193
        }
×
1194

1195
        channel := fn.Find(chans, func(c *channeldb.OpenChannel) bool {
2✔
1196
                return c.FundingOutpoint == chanPoint
1✔
1197
        })
1✔
1198

1199
        return channel.UnwrapOrErr(errors.New("channel not found"))
1✔
1200
}
1201

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

1✔
1211
        if in.ChanPoint == nil {
1✔
1212
                return nil, fmt.Errorf("no chan_point provided")
×
1213
        }
×
1214

1215
        lnrpcOutpoint, err := lnrpc.GetChannelOutPoint(in.ChanPoint)
1✔
1216
        if err != nil {
1✔
1217
                return nil, err
×
1218
        }
×
1219

1220
        outPoint, err := UnmarshallOutPoint(lnrpcOutpoint)
1✔
1221
        if err != nil {
1✔
1222
                return nil, err
×
1223
        }
×
1224

1225
        // Get the relevant channel if it is in the waiting close state.
1226
        channel, err := w.getWaitingCloseChannel(*outPoint)
1✔
1227
        if err != nil {
1✔
1228
                return nil, err
×
1229
        }
×
1230

1231
        if !channel.ChanType.HasAnchors() {
1✔
1232
                return nil, fmt.Errorf("not able to bump the fee of a " +
×
1233
                        "non-anchor channel")
×
1234
        }
×
1235

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

1✔
1242
        if channel.LocalCommitment.CommitTx != nil {
2✔
1243
                localTxID := channel.LocalCommitment.CommitTx.TxHash()
1✔
1244
                commitSet.Add(localTxID)
1✔
1245
        }
1✔
1246

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

1252
        // Check whether there was a dangling commitment at the time the channel
1253
        // was force closed.
1254
        remoteCommitDiff, err := channel.RemoteCommitChainTip()
1✔
1255
        if err != nil && !errors.Is(err, channeldb.ErrNoPendingCommit) {
1✔
1256
                return nil, err
×
1257
        }
×
1258

1259
        if remoteCommitDiff != nil {
1✔
1260
                hash := remoteCommitDiff.Commitment.CommitTx.TxHash()
×
1261
                commitSet.Add(hash)
×
1262
        }
×
1263

1264
        // Retrieve all of the outputs the UtxoSweeper is currently trying to
1265
        // sweep.
1266
        inputsMap, err := w.cfg.Sweeper.PendingInputs()
1✔
1267
        if err != nil {
1✔
1268
                return nil, err
×
1269
        }
×
1270

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

1278
        pendingSweeps := slices.Collect(maps.Values(inputsMap))
1✔
1279

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

1295
                        return commitSet.Contains(sweep.OutPoint.Hash)
1✔
1296
                },
1297
        )
1298

1299
        if len(anchors) == 0 {
1✔
1300
                return nil, fmt.Errorf("unable to find pending anchor outputs")
×
1301
        }
×
1302

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

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

1334
                _, err = w.cfg.Sweeper.UpdateParams(anchor.OutPoint, params)
1✔
1335
                if err != nil {
1✔
1336
                        return nil, err
×
1337
                }
×
1338
        }
1339

1340
        return &BumpForceCloseFeeResponse{
1✔
1341
                Status: "Successfully registered anchor-cpfp transaction to" +
1✔
1342
                        "bump channel force close transaction",
1✔
1343
        }, nil
1✔
1344
}
1345

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

1✔
1354
        log.Debugf("Attempting to sweep outpoint %s", op)
1✔
1355

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

1368
        // We're only able to bump the fee of unconfirmed transactions.
1369
        if utxo.Confirmations > 0 {
1✔
1370
                return errors.New("unable to bump fee of a confirmed " +
×
1371
                        "transaction")
×
1372
        }
×
1373

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

1✔
1386
                log.Warnf("[BumpFee]: setting default budget value of %v for "+
1✔
1387
                        "input=%v, which will be used for the maximum fee "+
1✔
1388
                        "rate estimation (budget was not specified)",
1✔
1389
                        params.Budget, op)
1✔
1390
        }
1✔
1391

1392
        signDesc := &input.SignDescriptor{
1✔
1393
                Output: &wire.TxOut{
1✔
1394
                        PkScript: utxo.PkScript,
1✔
1395
                        Value:    int64(utxo.Value),
1✔
1396
                },
1✔
1397
                HashType: txscript.SigHashAll,
1✔
1398
        }
1✔
1399

1✔
1400
        var witnessType input.WitnessType
1✔
1401
        switch utxo.AddressType {
1✔
1402
        case lnwallet.WitnessPubKey:
×
1403
                witnessType = input.WitnessKeyHash
×
1404
        case lnwallet.NestedWitnessPubKey:
×
1405
                witnessType = input.NestedWitnessKeyHash
×
1406
        case lnwallet.TaprootPubkey:
1✔
1407
                witnessType = input.TaprootPubKeySpend
1✔
1408
                signDesc.HashType = txscript.SigHashDefault
1✔
1409
        default:
×
1410
                return fmt.Errorf("unknown input witness %v", op)
×
1411
        }
1412

1413
        log.Infof("[BumpFee]: bumping fee for new input=%v, params=%v", op,
1✔
1414
                params)
1✔
1415

1✔
1416
        inp := input.NewBaseInput(op, witnessType, signDesc, currentHeight)
1✔
1417
        if _, err = w.cfg.Sweeper.SweepInput(inp, params); err != nil {
1✔
1418
                return err
×
1419
        }
×
1420

1421
        return nil
1✔
1422
}
1423

1424
// ListSweeps returns a list of the sweeps that our node has published.
1425
func (w *WalletKit) ListSweeps(ctx context.Context,
1426
        in *ListSweepsRequest) (*ListSweepsResponse, error) {
1✔
1427

1✔
1428
        sweeps, err := w.cfg.Sweeper.ListSweeps()
1✔
1429
        if err != nil {
1✔
1430
                return nil, err
×
1431
        }
×
1432

1433
        sweepTxns := make(map[string]bool)
1✔
1434
        for _, sweep := range sweeps {
2✔
1435
                sweepTxns[sweep.String()] = true
1✔
1436
        }
1✔
1437

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

1451
        var (
1✔
1452
                txids     []string
1✔
1453
                txDetails []*lnwallet.TransactionDetail
1✔
1454
        )
1✔
1455

1✔
1456
        for _, tx := range txns {
2✔
1457
                _, ok := sweepTxns[tx.Hash.String()]
1✔
1458
                if !ok {
2✔
1459
                        continue
1✔
1460
                }
1461

1462
                // Add the txid or full tx details depending on whether we want
1463
                // verbose output or not.
1464
                if in.Verbose {
2✔
1465
                        txDetails = append(txDetails, tx)
1✔
1466
                } else {
2✔
1467
                        txids = append(txids, tx.Hash.String())
1✔
1468
                }
1✔
1469
        }
1470

1471
        if in.Verbose {
2✔
1472
                return &ListSweepsResponse{
1✔
1473
                        Sweeps: &ListSweepsResponse_TransactionDetails{
1✔
1474
                                TransactionDetails: lnrpc.RPCTransactionDetails(
1✔
1475
                                        txDetails, firstIdx, lastIdx,
1✔
1476
                                ),
1✔
1477
                        },
1✔
1478
                }, nil
1✔
1479
        }
1✔
1480

1481
        return &ListSweepsResponse{
1✔
1482
                Sweeps: &ListSweepsResponse_TransactionIds{
1✔
1483
                        TransactionIds: &ListSweepsResponse_TransactionIDs{
1✔
1484
                                TransactionIds: txids,
1✔
1485
                        },
1✔
1486
                },
1✔
1487
        }, nil
1✔
1488
}
1489

1490
// LabelTransaction adds a label to a transaction.
1491
func (w *WalletKit) LabelTransaction(ctx context.Context,
1492
        req *LabelTransactionRequest) (*LabelTransactionResponse, error) {
1✔
1493

1✔
1494
        // Check that the label provided in non-zero.
1✔
1495
        if len(req.Label) == 0 {
2✔
1496
                return nil, ErrZeroLabel
1✔
1497
        }
1✔
1498

1499
        // Validate the length of the non-zero label. We do not need to use the
1500
        // label returned here, because the original is non-zero so will not
1501
        // be replaced.
1502
        if _, err := labels.ValidateAPI(req.Label); err != nil {
1✔
1503
                return nil, err
×
1504
        }
×
1505

1506
        hash, err := chainhash.NewHash(req.Txid)
1✔
1507
        if err != nil {
1✔
1508
                return nil, err
×
1509
        }
×
1510

1511
        err = w.cfg.Wallet.LabelTransaction(*hash, req.Label, req.Overwrite)
1✔
1512

1✔
1513
        return &LabelTransactionResponse{
1✔
1514
                Status: fmt.Sprintf("transaction label '%s' added", req.Label),
1✔
1515
        }, err
1✔
1516
}
1517

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

1✔
1551
        coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
1✔
1552
                req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
1✔
1553
        )
1✔
1554
        if err != nil {
1✔
1555
                return nil, err
×
1556
        }
×
1557

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

1569
                feeSatPerKW, err = w.cfg.FeeEstimator.EstimateFeePerKW(
×
1570
                        targetConf,
×
1571
                )
×
1572
                if err != nil {
×
1573
                        return nil, fmt.Errorf("could not estimate fee: %w",
×
1574
                                err)
×
1575
                }
×
1576

1577
        // Convert the fee to sat/kW from the specified sat/vByte.
1578
        case req.GetSatPerVbyte() != 0:
1✔
1579
                feeSatPerKW = chainfee.SatPerKVByte(
1✔
1580
                        req.GetSatPerVbyte() * 1000,
1✔
1581
                ).FeePerKWeight()
1✔
1582

1583
        case req.GetSatPerKw() != 0:
×
1584
                feeSatPerKW = chainfee.SatPerKWeight(req.GetSatPerKw())
×
1585

1586
        default:
×
1587
                return nil, fmt.Errorf("fee definition missing, need to " +
×
1588
                        "specify either target_conf, sat_per_vbyte or " +
×
1589
                        "sat_per_kw")
×
1590
        }
1591

1592
        // Then, we'll extract the minimum number of confirmations that each
1593
        // output we use to fund the transaction should satisfy.
1594
        minConfs, err := lnrpc.ExtractMinConfs(
1✔
1595
                req.GetMinConfs(), req.GetSpendUnconfirmed(),
1✔
1596
        )
1✔
1597
        if err != nil {
1✔
1598
                return nil, err
×
1599
        }
×
1600

1601
        // We'll assume the PSBT will be funded by the default account unless
1602
        // otherwise specified.
1603
        account := lnwallet.DefaultAccountName
1✔
1604
        if req.Account != "" {
2✔
1605
                account = req.Account
1✔
1606
        }
1✔
1607

1608
        var customLockID *wtxmgr.LockID
1✔
1609
        if len(req.CustomLockId) > 0 {
2✔
1610
                lockID := wtxmgr.LockID{}
1✔
1611
                if len(req.CustomLockId) != len(lockID) {
1✔
1612
                        return nil, fmt.Errorf("custom lock ID must be " +
×
1613
                                "exactly 32 bytes")
×
1614
                }
×
1615

1616
                copy(lockID[:], req.CustomLockId)
1✔
1617
                customLockID = &lockID
1✔
1618
        }
1619

1620
        var customLockDuration time.Duration
1✔
1621
        if req.LockExpirationSeconds != 0 {
2✔
1622
                customLockDuration = time.Duration(req.LockExpirationSeconds) *
1✔
1623
                        time.Second
1✔
1624
        }
1✔
1625

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

1640
                // Run the actual funding process now, using the internal
1641
                // wallet.
1642
                return w.fundPsbtInternalWallet(
1✔
1643
                        account, keyScopeFromChangeAddressType(req.ChangeType),
1✔
1644
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
1✔
1645
                        customLockID, customLockDuration,
1✔
1646
                )
1✔
1647

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

1658
                numOutputs := int32(len(packet.UnsignedTx.TxOut))
1✔
1659
                if numOutputs == 0 {
1✔
1660
                        return nil, fmt.Errorf("no outputs specified in " +
×
1661
                                "template")
×
1662
                }
×
1663

1664
                outputSum := int64(0)
1✔
1665
                for _, txOut := range packet.UnsignedTx.TxOut {
2✔
1666
                        outputSum += txOut.Value
1✔
1667
                }
1✔
1668
                if outputSum <= 0 {
1✔
1669
                        return nil, fmt.Errorf("output sum must be positive")
×
1670
                }
×
1671

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

×
1682
                                return nil, fmt.Errorf("change output index "+
×
1683
                                        "out of range: %d",
×
1684
                                        t.ExistingOutputIndex)
×
1685
                        }
×
1686

1687
                        changeIndex = t.ExistingOutputIndex
1✔
1688

1✔
1689
                        changeOut := packet.UnsignedTx.TxOut[changeIndex]
1✔
1690
                        _, err := txscript.ParsePkScript(changeOut.PkScript)
1✔
1691
                        if err != nil {
1✔
1692
                                return nil, fmt.Errorf("error parsing change "+
×
1693
                                        "script: %w", err)
×
1694
                        }
×
1695

1696
                        changeType = chanfunding.ExistingChangeAddress
1✔
1697

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

1709
                        default:
×
1710
                                changeType = chanfunding.P2WKHChangeAddress
×
1711
                        }
1712

1713
                default:
×
1714
                        return nil, fmt.Errorf("unknown change output type")
×
1715
                }
1716

1717
                maxFeeRatio := chanfunding.DefaultMaxFeeRatio
1✔
1718

1✔
1719
                if req.MaxFeeRatio != 0 {
1✔
1720
                        maxFeeRatio = req.MaxFeeRatio
×
1721
                }
×
1722

1723
                // Run the actual funding process now, using the channel funding
1724
                // coin selection algorithm.
1725
                return w.fundPsbtCoinSelect(
1✔
1726
                        account, changeIndex, packet, minConfs, changeType,
1✔
1727
                        feeSatPerKW, coinSelectionStrategy, maxFeeRatio,
1✔
1728
                        customLockID, customLockDuration,
1✔
1729
                )
1✔
1730

1731
        // The template is specified as a RPC message. We need to create a new
1732
        // PSBT and copy the RPC information over.
1733
        case req.GetRaw() != nil:
1✔
1734
                tpl := req.GetRaw()
1✔
1735

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

1747
                        if !addr.IsForNet(w.cfg.ChainParams) {
1✔
1748
                                return nil, fmt.Errorf("address is not for %s",
×
1749
                                        w.cfg.ChainParams.Name)
×
1750
                        }
×
1751

1752
                        pkScript, err := txscript.PayToAddrScript(addr)
1✔
1753
                        if err != nil {
1✔
1754
                                return nil, fmt.Errorf("error getting pk "+
×
1755
                                        "script for address %s: %w", addrStr,
×
1756
                                        err)
×
1757
                        }
×
1758

1759
                        txOut = append(txOut, &wire.TxOut{
1✔
1760
                                Value:    int64(amt),
1✔
1761
                                PkScript: pkScript,
1✔
1762
                        })
1✔
1763
                }
1764

1765
                txIn := make([]*wire.OutPoint, len(tpl.Inputs))
1✔
1766
                for idx, in := range tpl.Inputs {
1✔
1767
                        op, err := UnmarshallOutPoint(in)
×
1768
                        if err != nil {
×
1769
                                return nil, fmt.Errorf("error parsing "+
×
1770
                                        "outpoint: %w", err)
×
1771
                        }
×
1772
                        txIn[idx] = op
×
1773
                }
1774

1775
                sequences := make([]uint32, len(txIn))
1✔
1776
                packet, err := psbt.New(txIn, txOut, 2, 0, sequences)
1✔
1777
                if err != nil {
1✔
1778
                        return nil, fmt.Errorf("could not create PSBT: %w", err)
×
1779
                }
×
1780

1781
                // Run the actual funding process now, using the internal
1782
                // wallet.
1783
                return w.fundPsbtInternalWallet(
1✔
1784
                        account, keyScopeFromChangeAddressType(req.ChangeType),
1✔
1785
                        packet, minConfs, feeSatPerKW, coinSelectionStrategy,
1✔
1786
                        customLockID, customLockDuration,
1✔
1787
                )
1✔
1788

1789
        default:
×
1790
                return nil, fmt.Errorf("transaction template missing, need " +
×
1791
                        "to specify either PSBT or raw TX template")
×
1792
        }
1793
}
1794

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

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

1820
                        // filterFn makes sure utxos which are unconfirmed and
1821
                        // still used by the sweeper are not used.
1822
                        filterFn := func(u *lnwallet.Utxo) bool {
2✔
1823
                                // Confirmed utxos are always allowed.
1✔
1824
                                if u.Confirmations > 0 {
2✔
1825
                                        return true
1✔
1826
                                }
1✔
1827

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

1✔
1837
                                        return false
1✔
1838
                                }
1✔
1839

1840
                                return true
×
1841
                        }
1842

1843
                        eligibleUtxos := fn.Filter(utxos, filterFn)
1✔
1844

1✔
1845
                        // Validate all inputs against our known list of UTXOs
1✔
1846
                        // now.
1✔
1847
                        err = verifyInputsUnspent(
1✔
1848
                                packet.UnsignedTx.TxIn, eligibleUtxos,
1✔
1849
                        )
1✔
1850
                        if err != nil {
2✔
1851
                                return err
1✔
1852
                        }
1✔
1853
                }
1854

1855
                // currentHeight is needed to determine whether the internal
1856
                // wallet utxo is still unconfirmed.
1857
                _, currentHeight, err := w.cfg.Chain.GetBestBlock()
1✔
1858
                if err != nil {
1✔
1859
                        return fmt.Errorf("unable to retrieve current "+
×
1860
                                "height: %v", err)
×
1861
                }
×
1862

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

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

1886
                        return allowed
1✔
1887
                }
1888

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

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

1914
                response, err = w.lockAndCreateFundingResponse(
1✔
1915
                        packet, outpoints, changeIndex, customLockID,
1✔
1916
                        customLockDuration,
1✔
1917
                )
1✔
1918

1✔
1919
                return err
1✔
1920
        })
1921
        if err != nil {
2✔
1922
                return nil, err
1✔
1923
        }
1✔
1924

1925
        return response, nil
1✔
1926
}
1927

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

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

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

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

1✔
1968
                err := btcwallet.EstimateInputWeight(&in, &estimator)
1✔
1969
                if err != nil {
1✔
1970
                        return nil, fmt.Errorf("error estimating input "+
×
1971
                                "weight: %w", err)
×
1972
                }
×
1973

1974
                inputSum += btcutil.Amount(in.WitnessUtxo.Value)
1✔
1975
        }
1976
        for i := range packet.UnsignedTx.TxOut {
2✔
1977
                out := packet.UnsignedTx.TxOut[i]
1✔
1978

1✔
1979
                estimator.AddOutput(out.PkScript)
1✔
1980
                outputSum += btcutil.Amount(out.Value)
1✔
1981
        }
1✔
1982

1983
        // The amount we want to fund is the total output sum plus the current
1984
        // fee estimate, minus the sum of any already specified inputs. Since we
1985
        // pass the estimator of the current transaction into the coin selection
1986
        // algorithm, we don't need to subtract the fees here.
1987
        fundingAmount := outputSum - inputSum
1✔
1988

1✔
1989
        var changeDustLimit btcutil.Amount
1✔
1990
        switch changeType {
1✔
1991
        case chanfunding.P2TRChangeAddress:
×
1992
                changeDustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
×
1993

1994
        case chanfunding.P2WKHChangeAddress:
×
1995
                changeDustLimit = lnwallet.DustLimitForSize(input.P2WPKHSize)
×
1996

1997
        case chanfunding.ExistingChangeAddress:
1✔
1998
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
1✔
1999
                changeDustLimit = lnwallet.DustLimitForSize(
1✔
2000
                        len(changeOut.PkScript),
1✔
2001
                )
1✔
2002
        }
2003

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

2016
                case chanfunding.P2WKHChangeAddress:
×
2017
                        estimator.AddP2WKHOutput()
×
2018
                }
2019
                packetFeeWithChange := feeRate.FeeForWeight(estimator.Weight())
×
2020

×
2021
                changeAmt, needMore, err := chanfunding.CalculateChangeAmount(
×
2022
                        inputSum, outputSum, packetFeeNoChange,
×
2023
                        packetFeeWithChange, changeDustLimit, changeType,
×
2024
                        maxFeeRatio,
×
2025
                )
×
2026
                if err != nil {
×
2027
                        return nil, fmt.Errorf("error calculating change "+
×
2028
                                "amount: %w", err)
×
2029
                }
×
2030

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

2039
                if changeAmt > 0 {
×
2040
                        changeIndex, err = w.handleChange(
×
2041
                                packet, changeIndex, int64(changeAmt),
×
2042
                                changeType, account,
×
2043
                        )
×
2044
                        if err != nil {
×
2045
                                return nil, fmt.Errorf("error handling change "+
×
2046
                                        "amount: %w", err)
×
2047
                        }
×
2048
                }
2049

2050
                // We're done. Let's serialize and return the updated package.
2051
                return w.lockAndCreateFundingResponse(
×
2052
                        packet, nil, changeIndex, customLockID,
×
2053
                        customLockDuration,
×
2054
                )
×
2055
        }
2056

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

2071
                coins := make([]base.Coin, len(utxos))
1✔
2072
                for i, utxo := range utxos {
2✔
2073
                        coins[i] = base.Coin{
1✔
2074
                                TxOut: wire.TxOut{
1✔
2075
                                        Value:    int64(utxo.Value),
1✔
2076
                                        PkScript: utxo.PkScript,
1✔
2077
                                },
1✔
2078
                                OutPoint: utxo.OutPoint,
1✔
2079
                        }
1✔
2080
                }
1✔
2081

2082
                selectedCoins, changeAmount, err := chanfunding.CoinSelect(
1✔
2083
                        feeRate, fundingAmount, changeDustLimit, coins,
1✔
2084
                        strategy, estimator, changeType, maxFeeRatio,
1✔
2085
                )
1✔
2086
                if err != nil {
1✔
2087
                        return fmt.Errorf("error selecting coins: %w", err)
×
2088
                }
×
2089

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

2101
                addedOutpoints := make([]wire.OutPoint, len(selectedCoins))
1✔
2102
                for i := range selectedCoins {
2✔
2103
                        coin := selectedCoins[i]
1✔
2104
                        addedOutpoints[i] = coin.OutPoint
1✔
2105

1✔
2106
                        packet.UnsignedTx.TxIn = append(
1✔
2107
                                packet.UnsignedTx.TxIn, &wire.TxIn{
1✔
2108
                                        PreviousOutPoint: coin.OutPoint,
1✔
2109
                                },
1✔
2110
                        )
1✔
2111
                        packet.Inputs = append(packet.Inputs, psbt.PInput{
1✔
2112
                                WitnessUtxo: &coin.TxOut,
1✔
2113
                        })
1✔
2114
                }
1✔
2115

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

2125
                response, err = w.lockAndCreateFundingResponse(
1✔
2126
                        packet, addedOutpoints, changeIndex, customLockID,
1✔
2127
                        customLockDuration,
1✔
2128
                )
1✔
2129

1✔
2130
                return err
1✔
2131
        })
2132
        if err != nil {
1✔
2133
                return nil, err
×
2134
        }
×
2135

2136
        return response, nil
1✔
2137
}
2138

2139
// assertNotAvailable makes sure the specified inputs either don't belong to
2140
// this node or are already locked by the user.
2141
func (w *WalletKit) assertNotAvailable(inputs []*wire.TxIn, minConfs int32,
2142
        account string) error {
1✔
2143

1✔
2144
        return w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
2✔
2145
                // Get a list of all unspent witness outputs.
1✔
2146
                utxos, err := w.cfg.Wallet.ListUnspentWitness(
1✔
2147
                        minConfs, defaultMaxConf, account,
1✔
2148
                )
1✔
2149
                if err != nil {
1✔
2150
                        return fmt.Errorf("error fetching UTXOs: %w", err)
×
2151
                }
×
2152

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

2165
                return nil
1✔
2166
        })
2167
}
2168

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

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

2185
        locks, err := lockInputs(
1✔
2186
                w.cfg.Wallet, newOutpoints, customLockID, customLockDuration,
1✔
2187
        )
1✔
2188
        if err != nil {
1✔
2189
                return nil, fmt.Errorf("could not lock inputs: %w", err)
×
2190
        }
×
2191

2192
        // Convert the lock leases to the RPC format.
2193
        rpcLocks := marshallLeases(locks)
1✔
2194

1✔
2195
        return &FundPsbtResponse{
1✔
2196
                FundedPsbt:        buf.Bytes(),
1✔
2197
                ChangeOutputIndex: changeIndex,
1✔
2198
                LockedUtxos:       rpcLocks,
1✔
2199
        }, nil
1✔
2200
}
2201

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

1✔
2209
        // Does an existing output get the change?
1✔
2210
        if changeIndex >= 0 {
2✔
2211
                changeOut := packet.UnsignedTx.TxOut[changeIndex]
1✔
2212
                changeOut.Value += changeAmount
1✔
2213

1✔
2214
                return changeIndex, nil
1✔
2215
        }
1✔
2216

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

2226
        changeScript, err := txscript.PayToAddrScript(changeAddr)
×
2227
        if err != nil {
×
2228
                return 0, fmt.Errorf("could not derive change script: %w", err)
×
2229
        }
×
2230

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

2244
                deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
×
2245
                        changeAddrInfo,
×
2246
                )
×
2247
                if err != nil {
×
2248
                        return 0, fmt.Errorf("could not get derivation info: "+
×
2249
                                "%w", err)
×
2250
                }
×
2251

2252
                pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
×
2253
                pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
×
2254
                pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
×
2255
                        trDeriv,
×
2256
                }
×
2257
        }
2258

2259
        newChangeIndex := int32(len(packet.Outputs))
×
2260
        packet.UnsignedTx.TxOut = append(
×
2261
                packet.UnsignedTx.TxOut, &wire.TxOut{
×
2262
                        Value:    changeAmount,
×
2263
                        PkScript: changeScript,
×
2264
                },
×
2265
        )
×
2266
        packet.Outputs = append(packet.Outputs, pOut)
×
2267

×
2268
        return newChangeIndex, nil
×
2269
}
2270

2271
// marshallLeases converts the lock leases to the RPC format.
2272
func marshallLeases(locks []*base.ListLeasedOutputResult) []*UtxoLease {
1✔
2273
        rpcLocks := make([]*UtxoLease, len(locks))
1✔
2274
        for idx, lock := range locks {
2✔
2275
                lock := lock
1✔
2276

1✔
2277
                rpcLocks[idx] = &UtxoLease{
1✔
2278
                        Id:         lock.LockID[:],
1✔
2279
                        Outpoint:   lnrpc.MarshalOutPoint(&lock.Outpoint),
1✔
2280
                        Expiration: uint64(lock.Expiration.Unix()),
1✔
2281
                        PkScript:   lock.PkScript,
1✔
2282
                        Value:      uint64(lock.Value),
1✔
2283
                }
1✔
2284
        }
1✔
2285

2286
        return rpcLocks
1✔
2287
}
2288

2289
// keyScopeFromChangeAddressType maps a ChangeAddressType from protobuf to a
2290
// KeyScope. If the type is ChangeAddressType_CHANGE_ADDRESS_TYPE_UNSPECIFIED,
2291
// it returns nil.
2292
func keyScopeFromChangeAddressType(
2293
        changeAddressType ChangeAddressType) *waddrmgr.KeyScope {
1✔
2294

1✔
2295
        switch changeAddressType {
1✔
2296
        case ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR:
1✔
2297
                return &waddrmgr.KeyScopeBIP0086
1✔
2298

2299
        default:
1✔
2300
                return nil
1✔
2301
        }
2302
}
2303

2304
// addrTypeFromChangeAddressType maps a chanfunding.ChangeAddressType to the
2305
// lnwallet.AddressType.
2306
func addrTypeFromChangeAddressType(
2307
        changeAddressType chanfunding.ChangeAddressType) lnwallet.AddressType {
×
2308

×
2309
        switch changeAddressType {
×
2310
        case chanfunding.P2TRChangeAddress:
×
2311
                return lnwallet.TaprootPubkey
×
2312

2313
        default:
×
2314
                return lnwallet.WitnessPubKey
×
2315
        }
2316
}
2317

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

1✔
2332
        packet, err := psbt.NewFromRawBytes(
1✔
2333
                bytes.NewReader(req.FundedPsbt), false,
1✔
2334
        )
1✔
2335
        if err != nil {
1✔
2336
                log.Debugf("Error parsing PSBT: %v, raw input: %x", err,
×
2337
                        req.FundedPsbt)
×
2338
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2339
        }
×
2340

2341
        // Before we attempt to sign the packet, ensure that every input either
2342
        // has a witness UTXO, or a non witness UTXO.
2343
        for idx := range packet.UnsignedTx.TxIn {
2✔
2344
                in := packet.Inputs[idx]
1✔
2345

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

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

2362
        // Serialize the signed PSBT in both the packet and wire format.
2363
        var signedPsbtBytes bytes.Buffer
1✔
2364
        err = packet.Serialize(&signedPsbtBytes)
1✔
2365
        if err != nil {
1✔
2366
                return nil, fmt.Errorf("error serializing PSBT: %w", err)
×
2367
        }
×
2368

2369
        return &SignPsbtResponse{
1✔
2370
                SignedPsbt:   signedPsbtBytes.Bytes(),
1✔
2371
                SignedInputs: signedInputs,
1✔
2372
        }, nil
1✔
2373
}
2374

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

1✔
2389
        // We'll assume the PSBT was funded by the default account unless
1✔
2390
        // otherwise specified.
1✔
2391
        account := lnwallet.DefaultAccountName
1✔
2392
        if req.Account != "" {
1✔
2393
                account = req.Account
×
2394
        }
×
2395

2396
        // Parse the funded PSBT.
2397
        packet, err := psbt.NewFromRawBytes(
1✔
2398
                bytes.NewReader(req.FundedPsbt), false,
1✔
2399
        )
1✔
2400
        if err != nil {
1✔
2401
                return nil, fmt.Errorf("error parsing PSBT: %w", err)
×
2402
        }
×
2403

2404
        // The only check done at this level is to validate that the PSBT is
2405
        // not complete. The wallet performs all other checks.
2406
        if packet.IsComplete() {
1✔
2407
                return nil, fmt.Errorf("PSBT is already fully signed")
×
2408
        }
×
2409

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

2418
        var (
1✔
2419
                finalPsbtBytes bytes.Buffer
1✔
2420
                finalTxBytes   bytes.Buffer
1✔
2421
        )
1✔
2422

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

2437
        return &FinalizePsbtResponse{
1✔
2438
                SignedPsbt: finalPsbtBytes.Bytes(),
1✔
2439
                RawFinalTx: finalTxBytes.Bytes(),
1✔
2440
        }, nil
1✔
2441
}
2442

2443
// marshalWalletAccount converts the properties of an account into its RPC
2444
// representation.
2445
func marshalWalletAccount(internalScope waddrmgr.KeyScope,
2446
        account *waddrmgr.AccountProperties) (*Account, error) {
1✔
2447

1✔
2448
        var addrType AddressType
1✔
2449
        switch account.KeyScope {
1✔
2450
        case waddrmgr.KeyScopeBIP0049Plus:
1✔
2451
                // No address schema present represents the traditional BIP-0049
1✔
2452
                // address derivation scheme.
1✔
2453
                if account.AddrSchema == nil {
2✔
2454
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
1✔
2455
                        break
1✔
2456
                }
2457

2458
                switch *account.AddrSchema {
1✔
2459
                case waddrmgr.KeyScopeBIP0049AddrSchema:
1✔
2460
                        addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH
1✔
2461

2462
                case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]:
1✔
2463
                        addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH
1✔
2464

2465
                default:
×
2466
                        return nil, fmt.Errorf("unsupported address schema %v",
×
2467
                                *account.AddrSchema)
×
2468
                }
2469

2470
        case waddrmgr.KeyScopeBIP0084:
1✔
2471
                addrType = AddressType_WITNESS_PUBKEY_HASH
1✔
2472

2473
        case waddrmgr.KeyScopeBIP0086:
1✔
2474
                addrType = AddressType_TAPROOT_PUBKEY
1✔
2475

2476
        case internalScope:
1✔
2477
                addrType = AddressType_WITNESS_PUBKEY_HASH
1✔
2478

2479
        default:
×
2480
                return nil, fmt.Errorf("account %v has unsupported "+
×
2481
                        "key scope %v", account.AccountName, account.KeyScope)
×
2482
        }
2483

2484
        rpcAccount := &Account{
1✔
2485
                Name:             account.AccountName,
1✔
2486
                AddressType:      addrType,
1✔
2487
                ExternalKeyCount: account.ExternalKeyCount,
1✔
2488
                InternalKeyCount: account.InternalKeyCount,
1✔
2489
                WatchOnly:        account.IsWatchOnly,
1✔
2490
        }
1✔
2491

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

2509
        return rpcAccount, nil
1✔
2510
}
2511

2512
// marshalWalletAddressList converts the list of address into its RPC
2513
// representation.
2514
func marshalWalletAddressList(w *WalletKit, account *waddrmgr.AccountProperties,
2515
        addressList []lnwallet.AddressProperty) (*AccountWithAddresses, error) {
1✔
2516

1✔
2517
        // Get the RPC representation of account.
1✔
2518
        rpcAccount, err := marshalWalletAccount(
1✔
2519
                w.internalScope(), account,
1✔
2520
        )
1✔
2521
        if err != nil {
1✔
2522
                return nil, err
×
2523
        }
×
2524

2525
        addresses := make([]*AddressProperty, len(addressList))
1✔
2526
        for idx, addr := range addressList {
2✔
2527
                var pubKeyBytes []byte
1✔
2528
                if addr.PublicKey != nil {
2✔
2529
                        pubKeyBytes = addr.PublicKey.SerializeCompressed()
1✔
2530
                }
1✔
2531
                addresses[idx] = &AddressProperty{
1✔
2532
                        Address:        addr.Address,
1✔
2533
                        IsInternal:     addr.Internal,
1✔
2534
                        Balance:        int64(addr.Balance),
1✔
2535
                        DerivationPath: addr.DerivationPath,
1✔
2536
                        PublicKey:      pubKeyBytes,
1✔
2537
                }
1✔
2538
        }
2539

2540
        rpcAddressList := &AccountWithAddresses{
1✔
2541
                Name:           rpcAccount.Name,
1✔
2542
                AddressType:    rpcAccount.AddressType,
1✔
2543
                DerivationPath: rpcAccount.DerivationPath,
1✔
2544
                Addresses:      addresses,
1✔
2545
        }
1✔
2546

1✔
2547
        return rpcAddressList, nil
1✔
2548
}
2549

2550
// ListAccounts retrieves all accounts belonging to the wallet by default. A
2551
// name and key scope filter can be provided to filter through all of the wallet
2552
// accounts and return only those matching.
2553
func (w *WalletKit) ListAccounts(ctx context.Context,
2554
        req *ListAccountsRequest) (*ListAccountsResponse, error) {
1✔
2555

1✔
2556
        // Map the supported address types into their corresponding key scope.
1✔
2557
        var keyScopeFilter *waddrmgr.KeyScope
1✔
2558
        switch req.AddressType {
1✔
2559
        case AddressType_UNKNOWN:
1✔
2560
                break
1✔
2561

2562
        case AddressType_WITNESS_PUBKEY_HASH:
1✔
2563
                keyScope := waddrmgr.KeyScopeBIP0084
1✔
2564
                keyScopeFilter = &keyScope
1✔
2565

2566
        case AddressType_NESTED_WITNESS_PUBKEY_HASH,
2567
                AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
1✔
2568

1✔
2569
                keyScope := waddrmgr.KeyScopeBIP0049Plus
1✔
2570
                keyScopeFilter = &keyScope
1✔
2571

2572
        case AddressType_TAPROOT_PUBKEY:
1✔
2573
                keyScope := waddrmgr.KeyScopeBIP0086
1✔
2574
                keyScopeFilter = &keyScope
1✔
2575

2576
        default:
×
2577
                return nil, fmt.Errorf("unhandled address type %v",
×
2578
                        req.AddressType)
×
2579
        }
2580

2581
        accounts, err := w.cfg.Wallet.ListAccounts(req.Name, keyScopeFilter)
1✔
2582
        if err != nil {
1✔
2583
                return nil, err
×
2584
        }
×
2585

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

1✔
2593
                        continue
1✔
2594
                }
2595

2596
                rpcAccount, err := marshalWalletAccount(
1✔
2597
                        w.internalScope(), account,
1✔
2598
                )
1✔
2599
                if err != nil {
1✔
2600
                        return nil, err
×
2601
                }
×
2602
                rpcAccounts = append(rpcAccounts, rpcAccount)
1✔
2603
        }
2604

2605
        return &ListAccountsResponse{Accounts: rpcAccounts}, nil
1✔
2606
}
2607

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

1✔
2615
        numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
1✔
2616
        if err != nil {
1✔
2617
                return nil, err
×
2618
        }
×
2619

2620
        additionalChans := req.AdditionalPublicChannels
1✔
2621
        totalChans := uint32(numAnchorChans) + additionalChans
1✔
2622
        reserved := w.cfg.Wallet.RequiredReserve(totalChans)
1✔
2623

1✔
2624
        return &RequiredReserveResponse{
1✔
2625
                RequiredReserve: int64(reserved),
1✔
2626
        }, nil
1✔
2627
}
2628

2629
// ListAddresses retrieves all the addresses along with their balance. An
2630
// account name filter can be provided to filter through all of the
2631
// wallet accounts and return the addresses of only those matching.
2632
func (w *WalletKit) ListAddresses(ctx context.Context,
2633
        req *ListAddressesRequest) (*ListAddressesResponse, error) {
1✔
2634

1✔
2635
        addressLists, err := w.cfg.Wallet.ListAddresses(
1✔
2636
                req.AccountName,
1✔
2637
                req.ShowCustomAccounts,
1✔
2638
        )
1✔
2639
        if err != nil {
1✔
2640
                return nil, err
×
2641
        }
×
2642

2643
        // Create a slice of accounts from addressLists map.
2644
        accounts := make([]*waddrmgr.AccountProperties, 0, len(addressLists))
1✔
2645
        for account := range addressLists {
2✔
2646
                accounts = append(accounts, account)
1✔
2647
        }
1✔
2648

2649
        // Sort the accounts by derivation path.
2650
        sort.Slice(accounts, func(i, j int) bool {
2✔
2651
                scopeI := accounts[i].KeyScope
1✔
2652
                scopeJ := accounts[j].KeyScope
1✔
2653
                if scopeI.Purpose == scopeJ.Purpose {
1✔
2654
                        if scopeI.Coin == scopeJ.Coin {
×
2655
                                acntNumI := accounts[i].AccountNumber
×
2656
                                acntNumJ := accounts[j].AccountNumber
×
2657
                                return acntNumI < acntNumJ
×
2658
                        }
×
2659

2660
                        return scopeI.Coin < scopeJ.Coin
×
2661
                }
2662

2663
                return scopeI.Purpose < scopeJ.Purpose
1✔
2664
        })
2665

2666
        rpcAddressLists := make([]*AccountWithAddresses, 0, len(addressLists))
1✔
2667
        for _, account := range accounts {
2✔
2668
                addressList := addressLists[account]
1✔
2669
                rpcAddressList, err := marshalWalletAddressList(
1✔
2670
                        w, account, addressList,
1✔
2671
                )
1✔
2672
                if err != nil {
1✔
2673
                        return nil, err
×
2674
                }
×
2675

2676
                rpcAddressLists = append(rpcAddressLists, rpcAddressList)
1✔
2677
        }
2678

2679
        return &ListAddressesResponse{
1✔
2680
                AccountWithAddresses: rpcAddressLists,
1✔
2681
        }, nil
1✔
2682
}
2683

2684
// parseAddrType parses an address type from its RPC representation to a
2685
// *waddrmgr.AddressType.
2686
func parseAddrType(addrType AddressType,
2687
        required bool) (*waddrmgr.AddressType, error) {
1✔
2688

1✔
2689
        switch addrType {
1✔
2690
        case AddressType_UNKNOWN:
×
2691
                if required {
×
2692
                        return nil, fmt.Errorf("an address type must be " +
×
2693
                                "specified")
×
2694
                }
×
2695
                return nil, nil
×
2696

2697
        case AddressType_WITNESS_PUBKEY_HASH:
1✔
2698
                addrTyp := waddrmgr.WitnessPubKey
1✔
2699
                return &addrTyp, nil
1✔
2700

2701
        case AddressType_NESTED_WITNESS_PUBKEY_HASH:
1✔
2702
                addrTyp := waddrmgr.NestedWitnessPubKey
1✔
2703
                return &addrTyp, nil
1✔
2704

2705
        case AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
1✔
2706
                addrTyp := waddrmgr.WitnessPubKey
1✔
2707
                return &addrTyp, nil
1✔
2708

2709
        case AddressType_TAPROOT_PUBKEY:
1✔
2710
                addrTyp := waddrmgr.TaprootPubKey
1✔
2711
                return &addrTyp, nil
1✔
2712

2713
        default:
×
2714
                return nil, fmt.Errorf("unhandled address type %v", addrType)
×
2715
        }
2716
}
2717

2718
// msgSignaturePrefix is a prefix used to prevent inadvertently signing a
2719
// transaction or a signature. It is prepended in front of the message and
2720
// follows the same standard as bitcoin core and btcd.
2721
const msgSignaturePrefix = "Bitcoin Signed Message:\n"
2722

2723
// SignMessageWithAddr signs a message with the private key of the provided
2724
// address. The address needs to belong to the lnd wallet.
2725
func (w *WalletKit) SignMessageWithAddr(_ context.Context,
2726
        req *SignMessageWithAddrRequest) (*SignMessageWithAddrResponse, error) {
1✔
2727

1✔
2728
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
1✔
2729
        if err != nil {
1✔
2730
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2731
        }
×
2732

2733
        if !addr.IsForNet(w.cfg.ChainParams) {
1✔
2734
                return nil, fmt.Errorf("encoded address is for "+
×
2735
                        "the wrong network %s", req.Addr)
×
2736
        }
×
2737

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

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

2754
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
1✔
2755
        if err != nil {
1✔
2756
                return nil, err
×
2757
        }
×
2758

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

2770
        sigBytes := ecdsa.SignCompact(privKey, digest, pubKey.Compressed())
1✔
2771

1✔
2772
        // Bitcoin signatures are base64 encoded (being compatible with
1✔
2773
        // bitcoin-core and btcd).
1✔
2774
        sig := base64.StdEncoding.EncodeToString(sigBytes)
1✔
2775

1✔
2776
        return &SignMessageWithAddrResponse{
1✔
2777
                Signature: sig,
1✔
2778
        }, nil
1✔
2779
}
2780

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

1✔
2791
        sig, err := base64.StdEncoding.DecodeString(req.Signature)
1✔
2792
        if err != nil {
1✔
2793
                return nil, fmt.Errorf("malformed base64 encoding of "+
×
2794
                        "the signature: %w", err)
×
2795
        }
×
2796

2797
        digest, err := doubleHashMessage(msgSignaturePrefix, string(req.Msg))
1✔
2798
        if err != nil {
1✔
2799
                return nil, err
×
2800
        }
×
2801

2802
        pk, wasCompressed, err := ecdsa.RecoverCompact(sig, digest)
1✔
2803
        if err != nil {
1✔
2804
                return nil, fmt.Errorf("unable to recover public key "+
×
2805
                        "from compact signature: %w", err)
×
2806
        }
×
2807

2808
        var serializedPubkey []byte
1✔
2809
        if wasCompressed {
2✔
2810
                serializedPubkey = pk.SerializeCompressed()
1✔
2811
        } else {
1✔
2812
                serializedPubkey = pk.SerializeUncompressed()
×
2813
        }
×
2814

2815
        addr, err := btcutil.DecodeAddress(req.Addr, w.cfg.ChainParams)
1✔
2816
        if err != nil {
1✔
2817
                return nil, fmt.Errorf("unable to decode address: %w", err)
×
2818
        }
×
2819

2820
        if !addr.IsForNet(w.cfg.ChainParams) {
1✔
2821
                return nil, fmt.Errorf("encoded address is for"+
×
2822
                        "the wrong network %s", req.Addr)
×
2823
        }
×
2824

2825
        var (
1✔
2826
                address    btcutil.Address
1✔
2827
                pubKeyHash = btcutil.Hash160(serializedPubkey)
1✔
2828
        )
1✔
2829

1✔
2830
        // Ensure the address is one of the supported types.
1✔
2831
        switch addr.(type) {
1✔
2832
        case *btcutil.AddressPubKeyHash:
1✔
2833
                address, err = btcutil.NewAddressPubKeyHash(
1✔
2834
                        pubKeyHash, w.cfg.ChainParams,
1✔
2835
                )
1✔
2836
                if err != nil {
1✔
2837
                        return nil, err
×
2838
                }
×
2839

2840
        case *btcutil.AddressWitnessPubKeyHash:
1✔
2841
                address, err = btcutil.NewAddressWitnessPubKeyHash(
1✔
2842
                        pubKeyHash, w.cfg.ChainParams,
1✔
2843
                )
1✔
2844
                if err != nil {
1✔
2845
                        return nil, err
×
2846
                }
×
2847

2848
        case *btcutil.AddressScriptHash:
1✔
2849
                // Check if address is a Nested P2WKH (NP2WKH).
1✔
2850
                address, err = btcutil.NewAddressWitnessPubKeyHash(
1✔
2851
                        pubKeyHash, w.cfg.ChainParams,
1✔
2852
                )
1✔
2853
                if err != nil {
1✔
2854
                        return nil, err
×
2855
                }
×
2856

2857
                witnessScript, err := txscript.PayToAddrScript(address)
1✔
2858
                if err != nil {
1✔
2859
                        return nil, err
×
2860
                }
×
2861

2862
                address, err = btcutil.NewAddressScriptHashFromHash(
1✔
2863
                        btcutil.Hash160(witnessScript), w.cfg.ChainParams,
1✔
2864
                )
1✔
2865
                if err != nil {
1✔
2866
                        return nil, err
×
2867
                }
×
2868

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

2881
        default:
×
2882
                return nil, fmt.Errorf("unsupported address type")
×
2883
        }
2884

2885
        return &VerifyMessageWithAddrResponse{
1✔
2886
                Valid:  req.Addr == address.EncodeAddress(),
1✔
2887
                Pubkey: serializedPubkey,
1✔
2888
        }, nil
1✔
2889
}
2890

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

1✔
2913
        accountPubKey, err := hdkeychain.NewKeyFromString(req.ExtendedPublicKey)
1✔
2914
        if err != nil {
1✔
2915
                return nil, err
×
2916
        }
×
2917

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

2931
        addrType, err := parseAddrType(req.AddressType, false)
1✔
2932
        if err != nil {
1✔
2933
                return nil, err
×
2934
        }
×
2935

2936
        accountProps, extAddrs, intAddrs, err := w.cfg.Wallet.ImportAccount(
1✔
2937
                req.Name, accountPubKey, mkfp, addrType, req.DryRun,
1✔
2938
        )
1✔
2939
        if err != nil {
2✔
2940
                return nil, err
1✔
2941
        }
1✔
2942

2943
        rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps)
1✔
2944
        if err != nil {
1✔
2945
                return nil, err
×
2946
        }
×
2947

2948
        resp := &ImportAccountResponse{Account: rpcAccount}
1✔
2949
        if !req.DryRun {
2✔
2950
                return resp, nil
1✔
2951
        }
1✔
2952

2953
        resp.DryRunExternalAddrs = make([]string, len(extAddrs))
×
2954
        for i := 0; i < len(extAddrs); i++ {
×
2955
                resp.DryRunExternalAddrs[i] = extAddrs[i].String()
×
2956
        }
×
2957
        resp.DryRunInternalAddrs = make([]string, len(intAddrs))
×
2958
        for i := 0; i < len(intAddrs); i++ {
×
2959
                resp.DryRunInternalAddrs[i] = intAddrs[i].String()
×
2960
        }
×
2961

2962
        return resp, nil
×
2963
}
2964

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

1✔
2975
        var (
1✔
2976
                pubKey *btcec.PublicKey
1✔
2977
                err    error
1✔
2978
        )
1✔
2979
        switch req.AddressType {
1✔
2980
        case AddressType_TAPROOT_PUBKEY:
1✔
2981
                pubKey, err = schnorr.ParsePubKey(req.PublicKey)
1✔
2982

2983
        default:
1✔
2984
                pubKey, err = btcec.ParsePubKey(req.PublicKey)
1✔
2985
        }
2986
        if err != nil {
1✔
2987
                return nil, err
×
2988
        }
×
2989

2990
        addrType, err := parseAddrType(req.AddressType, true)
1✔
2991
        if err != nil {
1✔
2992
                return nil, err
×
2993
        }
×
2994

2995
        if err := w.cfg.Wallet.ImportPublicKey(pubKey, *addrType); err != nil {
1✔
2996
                return nil, err
×
2997
        }
×
2998

2999
        return &ImportPublicKeyResponse{
1✔
3000
                Status: fmt.Sprintf("public key %x imported",
1✔
3001
                        pubKey.SerializeCompressed()),
1✔
3002
        }, nil
1✔
3003
}
3004

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

1✔
3015
        internalKey, err := schnorr.ParsePubKey(req.InternalPublicKey)
1✔
3016
        if err != nil {
1✔
3017
                return nil, fmt.Errorf("error parsing internal key: %w", err)
×
3018
        }
×
3019

3020
        var tapscript *waddrmgr.Tapscript
1✔
3021
        switch {
1✔
3022
        case req.GetFullTree() != nil:
1✔
3023
                tree := req.GetFullTree()
1✔
3024
                leaves := make([]txscript.TapLeaf, len(tree.AllLeaves))
1✔
3025
                for idx, leaf := range tree.AllLeaves {
2✔
3026
                        leaves[idx] = txscript.TapLeaf{
1✔
3027
                                LeafVersion: txscript.TapscriptLeafVersion(
1✔
3028
                                        leaf.LeafVersion,
1✔
3029
                                ),
1✔
3030
                                Script: leaf.Script,
1✔
3031
                        }
1✔
3032
                }
1✔
3033

3034
                tapscript = input.TapscriptFullTree(internalKey, leaves...)
1✔
3035

3036
        case req.GetPartialReveal() != nil:
1✔
3037
                partialReveal := req.GetPartialReveal()
1✔
3038
                if partialReveal.RevealedLeaf == nil {
1✔
3039
                        return nil, fmt.Errorf("missing revealed leaf")
×
3040
                }
×
3041

3042
                revealedLeaf := txscript.TapLeaf{
1✔
3043
                        LeafVersion: txscript.TapscriptLeafVersion(
1✔
3044
                                partialReveal.RevealedLeaf.LeafVersion,
1✔
3045
                        ),
1✔
3046
                        Script: partialReveal.RevealedLeaf.Script,
1✔
3047
                }
1✔
3048
                if len(partialReveal.FullInclusionProof)%32 != 0 {
1✔
3049
                        return nil, fmt.Errorf("invalid inclusion proof "+
×
3050
                                "length, expected multiple of 32, got %d",
×
3051
                                len(partialReveal.FullInclusionProof)%32)
×
3052
                }
×
3053

3054
                tapscript = input.TapscriptPartialReveal(
1✔
3055
                        internalKey, revealedLeaf,
1✔
3056
                        partialReveal.FullInclusionProof,
1✔
3057
                )
1✔
3058

3059
        case req.GetRootHashOnly() != nil:
1✔
3060
                rootHash := req.GetRootHashOnly()
1✔
3061
                if len(rootHash) == 0 {
1✔
3062
                        return nil, fmt.Errorf("missing root hash")
×
3063
                }
×
3064

3065
                tapscript = input.TapscriptRootHashOnly(internalKey, rootHash)
1✔
3066

3067
        case req.GetFullKeyOnly():
1✔
3068
                tapscript = input.TapscriptFullKeyOnly(internalKey)
1✔
3069

3070
        default:
×
3071
                return nil, fmt.Errorf("invalid script")
×
3072
        }
3073

3074
        taprootScope := waddrmgr.KeyScopeBIP0086
1✔
3075
        addr, err := w.cfg.Wallet.ImportTaprootScript(taprootScope, tapscript)
1✔
3076
        if err != nil {
1✔
3077
                return nil, fmt.Errorf("error importing script into wallet: %w",
×
3078
                        err)
×
3079
        }
×
3080

3081
        return &ImportTapscriptResponse{
1✔
3082
                P2TrAddress: addr.Address().String(),
1✔
3083
        }, nil
1✔
3084
}
3085

3086
// SubmitPackage attempts to broadcast a transaction package, consisting of one
3087
// or more parent transactions and exactly one child transaction. The package is
3088
// submitted to the backend node's mempool atomically. This RPC is primarily
3089
// used for Child-Pays-For-Parent (CPFP) fee bumping.
3090
func (w *WalletKit) SubmitPackage(ctx context.Context,
3091
        req *SubmitPackageRequest) (*SubmitPackageResponse, error) {
1✔
3092

1✔
3093
        // Validate that there's at least one parent transaction.
1✔
3094
        if len(req.ParentTxs) == 0 {
1✔
NEW
3095
                return nil, fmt.Errorf("at least one parent transaction is " +
×
NEW
3096
                        "required")
×
NEW
3097
        }
×
3098

3099
        // Validate that there's a child transaction specified.
3100
        if len(req.ChildTx) == 0 {
1✔
NEW
3101
                return nil, fmt.Errorf("child transaction is required")
×
NEW
3102
        }
×
3103

3104
        // Deserialize parent transactions.
3105
        parents := make([]*wire.MsgTx, 0, len(req.ParentTxs))
1✔
3106
        for i, parentBytes := range req.ParentTxs {
2✔
3107
                if len(parentBytes) == 0 {
1✔
NEW
3108
                        return nil, fmt.Errorf("parent transaction at index "+
×
NEW
3109
                                "%d is empty", i)
×
NEW
3110
                }
×
3111

3112
                parentTx := wire.NewMsgTx(wire.TxVersion)
1✔
3113
                err := parentTx.Deserialize(bytes.NewReader(parentBytes))
1✔
3114
                if err != nil {
1✔
NEW
3115
                        return nil, fmt.Errorf("failed to deserialize parent "+
×
NEW
3116
                                "transaction at index %d: %w", i, err)
×
NEW
3117
                }
×
3118

3119
                parents = append(parents, parentTx)
1✔
3120
        }
3121

3122
        // Deserialize child transaction.
3123
        childTx := wire.NewMsgTx(wire.TxVersion)
1✔
3124
        err := childTx.Deserialize(bytes.NewReader(req.ChildTx))
1✔
3125
        if err != nil {
1✔
NEW
3126
                return nil, fmt.Errorf("failed to deserialize child "+
×
NEW
3127
                        "transaction: %w", err)
×
NEW
3128
        }
×
3129

3130
        // Attempt to submit the package using the underlying wallet.
3131
        submitPackageResult, err := w.cfg.Wallet.SubmitPackage(
1✔
3132
                parents, childTx,
1✔
3133
                chainfee.SatPerKWeight(req.MaxFeeRateSatPerKw),
1✔
3134
        )
1✔
3135
        if err != nil {
1✔
NEW
3136
                return nil, fmt.Errorf("failed to submit package: %w", err)
×
NEW
3137
        }
×
3138

3139
        // Translate the wallet's Go result to the protobuf response.
3140
        protoResponse := &SubmitPackageResponse{
1✔
3141
                PackageMsg: submitPackageResult.PackageMsg,
1✔
3142
                TxResults:  make(map[string]*SubmitPackageResponse_TxResult),
1✔
3143
        }
1✔
3144

1✔
3145
        // Translate TxResults.
1✔
3146
        for wtxid, txRes := range submitPackageResult.TxResults {
2✔
3147
                protoTxRes := &SubmitPackageResponse_TxResult{
1✔
3148
                        Txid: txRes.TxID.String(),
1✔
3149
                }
1✔
3150
                if txRes.OtherWtxid != nil {
1✔
NEW
3151
                        protoTxRes.OtherWtxid = txRes.OtherWtxid.String()
×
NEW
3152
                }
×
3153
                if txRes.Error != nil {
1✔
NEW
3154
                        protoTxRes.ErrorMessage = *txRes.Error
×
NEW
3155
                }
×
3156

3157
                protoResponse.TxResults[wtxid] = protoTxRes
1✔
3158
        }
3159

3160
        // Translate ReplacedTransactions.
3161
        if len(submitPackageResult.ReplacedTransactions) > 0 {
1✔
NEW
3162
                protoResponse.ReplacedTransactions = make(
×
NEW
3163
                        []string, 0,
×
NEW
3164
                        len(submitPackageResult.ReplacedTransactions),
×
NEW
3165
                )
×
NEW
3166
                for _, hash := range submitPackageResult.ReplacedTransactions {
×
NEW
3167
                        protoResponse.ReplacedTransactions = append(
×
NEW
3168
                                protoResponse.ReplacedTransactions,
×
NEW
3169
                                hash.String(),
×
NEW
3170
                        )
×
NEW
3171
                }
×
3172
        }
3173

3174
        return protoResponse, nil
1✔
3175
}
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