• 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

54.48
/lnwallet/interface.go
1
package lnwallet
2

3
import (
4
        "errors"
5
        "fmt"
6
        "sync"
7
        "time"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btcd/btcec/v2/ecdsa"
11
        "github.com/btcsuite/btcd/btcjson"
12
        "github.com/btcsuite/btcd/btcutil"
13
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
14
        "github.com/btcsuite/btcd/btcutil/psbt"
15
        "github.com/btcsuite/btcd/chaincfg"
16
        "github.com/btcsuite/btcd/chaincfg/chainhash"
17
        "github.com/btcsuite/btcd/txscript"
18
        "github.com/btcsuite/btcd/wire"
19
        "github.com/btcsuite/btcwallet/waddrmgr"
20
        base "github.com/btcsuite/btcwallet/wallet"
21
        "github.com/btcsuite/btcwallet/wallet/txauthor"
22
        "github.com/btcsuite/btcwallet/wtxmgr"
23
        "github.com/lightningnetwork/lnd/fn/v2"
24
        "github.com/lightningnetwork/lnd/keychain"
25
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
26
        "github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
27
        "github.com/lightningnetwork/lnd/lnwire"
28
)
29

30
const (
31
        // DefaultAccountName is the name for the default account used to manage
32
        // on-chain funds within the wallet.
33
        DefaultAccountName = "default"
34
)
35

36
// AddressType is an enum-like type which denotes the possible address types
37
// WalletController supports.
38
type AddressType uint8
39

40
// AccountAddressMap maps the account properties to an array of
41
// address properties.
42
type AccountAddressMap map[*waddrmgr.AccountProperties][]AddressProperty
43

44
const (
45
        // UnknownAddressType represents an output with an unknown or non-standard
46
        // script.
47
        UnknownAddressType AddressType = iota
48

49
        // WitnessPubKey represents a p2wkh address.
50
        WitnessPubKey
51

52
        // NestedWitnessPubKey represents a p2sh output which is itself a
53
        // nested p2wkh output.
54
        NestedWitnessPubKey
55

56
        // TaprootPubkey represents a p2tr key path spending address.
57
        TaprootPubkey
58
)
59

60
var (
61
        // DefaultPublicPassphrase is the default public passphrase used for the
62
        // wallet.
63
        DefaultPublicPassphrase = []byte("public")
64

65
        // DefaultPrivatePassphrase is the default private passphrase used for
66
        // the wallet.
67
        DefaultPrivatePassphrase = []byte("hello")
68

69
        // ErrDoubleSpend is returned from PublishTransaction in case the
70
        // tx being published is spending an output spent by a conflicting
71
        // transaction.
72
        ErrDoubleSpend = errors.New("transaction rejected: output already spent")
73

74
        // ErrNotMine is an error denoting that a WalletController instance is
75
        // unable to spend a specified output.
76
        ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
77

78
        // ErrMempoolFee is returned from PublishTransaction in case the tx
79
        // being published is not accepted into mempool because the fee
80
        // requirements of the mempool backend are not met.
81
        ErrMempoolFee = errors.New("transaction rejected by the mempool " +
82
                "because of low fees")
83

84
        // ErrSubmitPackageUnsupported signals the backend cannot do package
85
        // relay yet.
86
        ErrSubmitPackageUnsupported = errors.New("submitpackage not supported")
87
)
88

89
// ErrNoOutputs is returned if we try to create a transaction with no outputs
90
// or send coins to a set of outputs that is empty.
91
var ErrNoOutputs = errors.New("no outputs")
92

93
// ErrInvalidMinconf is returned if we try to create a transaction with
94
// invalid minConfs value.
95
var ErrInvalidMinconf = errors.New("minimum number of confirmations must " +
96
        "be a non-negative number")
97

98
// AddressProperty contains wallet related information of an address.
99
type AddressProperty struct {
100
        // Address is the address of an account.
101
        Address string
102

103
        // Internal denotes if the address is a change address.
104
        Internal bool
105

106
        // Balance returns the total balance of an address.
107
        Balance btcutil.Amount
108

109
        // DerivationPath is the derivation path of the address.
110
        DerivationPath string
111

112
        // PublicKey is the public key of the address.
113
        PublicKey *btcec.PublicKey
114
}
115

116
// AccountIdentifier contains information to uniquely identify an account.
117
type AccountIdentifier struct {
118
        // Name is the name of the account.
119
        Name string
120

121
        // AddressType is the type of addresses supported by the account.
122
        AddressType AddressType
123

124
        // DerivationPath is the derivation path corresponding to the account
125
        // public key.
126
        DerivationPath string
127
}
128

129
// Utxo is an unspent output denoted by its outpoint, and output value of the
130
// original output.
131
type Utxo struct {
132
        AddressType   AddressType
133
        Value         btcutil.Amount
134
        Confirmations int64
135
        PkScript      []byte
136
        wire.OutPoint
137
        PrevTx *wire.MsgTx
138
}
139

140
// OutputDetail contains additional information on a destination address.
141
type OutputDetail struct {
142
        OutputType   txscript.ScriptClass
143
        Addresses    []btcutil.Address
144
        PkScript     []byte
145
        OutputIndex  int
146
        Value        btcutil.Amount
147
        IsOurAddress bool
148
}
149

150
// PreviousOutPoint contains information about the previous outpoint.
151
type PreviousOutPoint struct {
152
        // OutPoint is the transaction out point in the format txid:n.
153
        OutPoint string
154

155
        // IsOurOutput denotes if the previous output is controlled by the
156
        // internal wallet. The flag will only detect p2wkh, np2wkh and p2tr
157
        // inputs as its own.
158
        IsOurOutput bool
159
}
160

161
// TransactionDetail describes a transaction with either inputs which belong to
162
// the wallet, or has outputs that pay to the wallet.
163
type TransactionDetail struct {
164
        // Hash is the transaction hash of the transaction.
165
        Hash chainhash.Hash
166

167
        // Value is the net value of this transaction (in satoshis) from the
168
        // PoV of the wallet. If this transaction purely spends from the
169
        // wallet's funds, then this value will be negative. Similarly, if this
170
        // transaction credits the wallet, then this value will be positive.
171
        Value btcutil.Amount
172

173
        // NumConfirmations is the number of confirmations this transaction
174
        // has. If the transaction is unconfirmed, then this value will be
175
        // zero.
176
        NumConfirmations int32
177

178
        // BlockHeight is the hash of the block which includes this
179
        // transaction. Unconfirmed transactions will have a nil value for this
180
        // field.
181
        BlockHash *chainhash.Hash
182

183
        // BlockHeight is the height of the block including this transaction.
184
        // Unconfirmed transaction will show a height of zero.
185
        BlockHeight int32
186

187
        // Timestamp is the unix timestamp of the block including this
188
        // transaction. If the transaction is unconfirmed, then this will be a
189
        // timestamp of txn creation.
190
        Timestamp int64
191

192
        // TotalFees is the total fee in satoshis paid by this transaction.
193
        TotalFees int64
194

195
        // OutputDetails contains output data for each destination address, such
196
        // as the output script and amount.
197
        OutputDetails []OutputDetail
198

199
        // RawTx returns the raw serialized transaction.
200
        RawTx []byte
201

202
        // Label is an optional transaction label.
203
        Label string
204

205
        // PreviousOutpoints are the inputs for a transaction.
206
        PreviousOutpoints []PreviousOutPoint
207
}
208

209
// TransactionSubscription is an interface which describes an object capable of
210
// receiving notifications of new transaction related to the underlying wallet.
211
// TODO(roasbeef): add balance updates?
212
type TransactionSubscription interface {
213
        // ConfirmedTransactions returns a channel which will be sent on as new
214
        // relevant transactions are confirmed.
215
        ConfirmedTransactions() chan *TransactionDetail
216

217
        // UnconfirmedTransactions returns a channel which will be sent on as
218
        // new relevant transactions are seen within the network.
219
        UnconfirmedTransactions() chan *TransactionDetail
220

221
        // Cancel finalizes the subscription, cleaning up any resources
222
        // allocated.
223
        Cancel()
224
}
225

226
// PackageBroadcaster defines an interface for submitting a transaction package
227
// consisting of potentially multiple parent transactions and exactly one child
228
// transaction to the mempool atomically. This is typically used for
229
// Child-Pays-For-Parent (CPFP) fee bumping scenarios.
230
type PackageBroadcaster interface {
231
        // SubmitPackage attempts to broadcast a package containing one or more
232
        // parent transactions and exactly one child transaction to the mempool
233
        // atomically using the backend node's package submission capabilities
234
        // (e.g., Bitcoin Core's `submitpackage` RPC).
235
        //
236
        // The package must adhere to the backend's requirements, typically
237
        // meaning the transactions are topologically sorted (parents first,
238
        // child last) and form a valid "child-with-unconfirmed-parents" set.
239
        //
240
        // Parameters:
241
        //        - parents: A slice containing one or more parent transactions.
242
        //          These are typically the transactions being fee-bumped.
243
        //        - child: The single child transaction that spends outputs from
244
        //          one or more of the transactions in `parents`. This transaction
245
        //          should provide sufficient fees for the entire package to meet
246
        //          mempool requirements.
247
        //        - maxFeeRate: An optional fee rate cap for the package,
248
        //          expressed in sat/kwu. If non-zero, the backend node may reject
249
        //          the package if its effective fee rate exceeds this value.
250
        //          A value of 0 typically indicates no specific limit should be
251
        //          enforced by the caller (allowing the backend node's default
252
        //          behavior).
253
        //
254
        // Returns:
255
        //        - []*chainhash.Hash: A slice containing the transaction IDs
256
        //          (txids) of all transactions from the submitted package
257
        //          (parents and child) that were successfully accepted into the
258
        //          backend node's mempool or were already present.
259
        //        - error: nil if the submission process was initiated
260
        //          successfully (even if some transactions were rejected or
261
        //          already present). Returns ErrSubmitPackageUnsupported if the
262
        //          backend node does not support package submission, or another
263
        //          error for issues like serialization, RPC communication, or
264
        //          fundamental package validation failures reported by the node.
265
        SubmitPackage(parents []*wire.MsgTx, child *wire.MsgTx,
266
                maxFeeRate chainfee.SatPerKWeight) (
267
                *btcjson.SubmitPackageResult, error)
268
}
269

270
// WalletController defines an abstract interface for controlling a local Pure
271
// Go wallet, a local or remote wallet via an RPC mechanism, or possibly even
272
// a daemon assisted hardware wallet. This interface serves the purpose of
273
// allowing LightningWallet to be seamlessly compatible with several wallets
274
// such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then
275
// serves as a "base wallet", with Lightning Network awareness taking place at
276
// a "higher" level of abstraction. Essentially, an overlay wallet.
277
// Implementors of this interface must closely adhere to the documented
278
// behavior of all interface methods in order to ensure identical behavior
279
// across all concrete implementations.
280
type WalletController interface {
281
        PackageBroadcaster
282

283
        // FetchOutpointInfo queries for the WalletController's knowledge of
284
        // the passed outpoint. If the base wallet determines this output is
285
        // under its control, then the original txout should be returned.
286
        // Otherwise, a non-nil error value of ErrNotMine should be returned
287
        // instead.
288
        FetchOutpointInfo(prevOut *wire.OutPoint) (*Utxo, error)
289

290
        // FetchDerivationInfo queries for the wallet's knowledge of the passed
291
        // pkScript and constructs the derivation info and returns it.
292
        FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation, error)
293

294
        // ScriptForOutput returns the address, witness program and redeem
295
        // script for a given UTXO. An error is returned if the UTXO does not
296
        // belong to our wallet or it is not a managed pubKey address.
297
        ScriptForOutput(output *wire.TxOut) (waddrmgr.ManagedPubKeyAddress,
298
                []byte, []byte, error)
299

300
        // ConfirmedBalance returns the sum of all the wallet's unspent outputs
301
        // that have at least confs confirmations. If confs is set to zero,
302
        // then all unspent outputs, including those currently in the mempool
303
        // will be included in the final sum. The account parameter serves as a
304
        // filter to retrieve the balance for a specific account. When empty,
305
        // the confirmed balance of all wallet accounts is returned.
306
        //
307
        // NOTE: Only witness outputs should be included in the computation of
308
        // the total spendable balance of the wallet. We require this as only
309
        // witness inputs can be used for funding channels.
310
        ConfirmedBalance(confs int32, accountFilter string) (btcutil.Amount,
311
                error)
312

313
        // NewAddress returns the next external or internal address for the
314
        // wallet dictated by the value of the `change` parameter. If change is
315
        // true, then an internal address should be used, otherwise an external
316
        // address should be returned. The type of address returned is dictated
317
        // by the wallet's capabilities, and may be of type: p2sh, p2wkh,
318
        // p2wsh, etc. The account parameter must be non-empty as it determines
319
        // which account the address should be generated from.
320
        NewAddress(addrType AddressType, change bool,
321
                account string) (btcutil.Address, error)
322

323
        // LastUnusedAddress returns the last *unused* address known by the
324
        // wallet. An address is unused if it hasn't received any payments.
325
        // This can be useful in UIs in order to continually show the
326
        // "freshest" address without having to worry about "address inflation"
327
        // caused by continual refreshing. Similar to NewAddress it can derive
328
        // a specified address type. By default, this is a non-change address.
329
        // The account parameter must be non-empty as it determines which
330
        // account the address should be generated from.
331
        LastUnusedAddress(addrType AddressType,
332
                account string) (btcutil.Address, error)
333

334
        // IsOurAddress checks if the passed address belongs to this wallet
335
        IsOurAddress(a btcutil.Address) bool
336

337
        // AddressInfo returns the information about an address, if it's known
338
        // to this wallet.
339
        AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error)
340

341
        // ListAccounts retrieves all accounts belonging to the wallet by
342
        // default. A name and key scope filter can be provided to filter
343
        // through all of the wallet accounts and return only those matching.
344
        ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties,
345
                error)
346

347
        // RequiredReserve returns the minimum amount of satoshis that should be
348
        // kept in the wallet in order to fee bump anchor channels if necessary.
349
        // The value scales with the number of public anchor channels but is
350
        // capped at a maximum.
351
        RequiredReserve(uint32) btcutil.Amount
352

353
        // ListAddresses retrieves all the addresses along with their balance. An
354
        // account name filter can be provided to filter through all of the
355
        // wallet accounts and return the addresses of only those matching.
356
        ListAddresses(string, bool) (AccountAddressMap, error)
357

358
        // ImportAccount imports an account backed by an account extended public
359
        // key. The master key fingerprint denotes the fingerprint of the root
360
        // key corresponding to the account public key (also known as the key
361
        // with derivation path m/). This may be required by some hardware
362
        // wallets for proper identification and signing.
363
        //
364
        // The address type can usually be inferred from the key's version, but
365
        // may be required for certain keys to map them into the proper scope.
366
        //
367
        // For BIP-0044 keys, an address type must be specified as we intend to
368
        // not support importing BIP-0044 keys into the wallet using the legacy
369
        // pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will
370
        // force the standard BIP-0049 derivation scheme, while a witness
371
        // address type will force the standard BIP-0084 derivation scheme.
372
        //
373
        // For BIP-0049 keys, an address type must also be specified to make a
374
        // distinction between the standard BIP-0049 address schema (nested
375
        // witness pubkeys everywhere) and our own BIP-0049Plus address schema
376
        // (nested pubkeys externally, witness pubkeys internally).
377
        ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKey,
378
                masterKeyFingerprint uint32, addrType *waddrmgr.AddressType,
379
                dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address,
380
                []btcutil.Address, error)
381

382
        // ImportPublicKey imports a single derived public key into the wallet.
383
        // The address type can usually be inferred from the key's version, but
384
        // in the case of legacy versions (xpub, tpub), an address type must be
385
        // specified as we intend to not support importing BIP-44 keys into the
386
        // wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme.
387
        ImportPublicKey(pubKey *btcec.PublicKey,
388
                addrType waddrmgr.AddressType) error
389

390
        // ImportTaprootScript imports a user-provided taproot script into the
391
        // wallet. The imported script will act as a pay-to-taproot address.
392
        //
393
        // NOTE: Taproot keys imported through this RPC currently _cannot_ be
394
        // used for funding PSBTs. Only tracking the balance and UTXOs is
395
        // currently supported.
396
        ImportTaprootScript(scope waddrmgr.KeyScope,
397
                tapscript *waddrmgr.Tapscript) (waddrmgr.ManagedAddress, error)
398

399
        // SendOutputs funds, signs, and broadcasts a Bitcoin transaction paying
400
        // out to the specified outputs. In the case the wallet has insufficient
401
        // funds, or the outputs are non-standard, an error should be returned.
402
        // This method also takes the target fee expressed in sat/kw that should
403
        // be used when crafting the transaction.
404
        //
405
        // NOTE: This method requires the global coin selection lock to be held.
406
        SendOutputs(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
407
                feeRate chainfee.SatPerKWeight, minConfs int32, label string,
408
                strategy base.CoinSelectionStrategy) (*wire.MsgTx, error)
409

410
        // CreateSimpleTx creates a Bitcoin transaction paying to the specified
411
        // outputs. The transaction is not broadcasted to the network. In the
412
        // case the wallet has insufficient funds, or the outputs are
413
        // non-standard, an error should be returned. This method also takes
414
        // the target fee expressed in sat/kw that should be used when crafting
415
        // the transaction.
416
        //
417
        // NOTE: The dryRun argument can be set true to create a tx that
418
        // doesn't alter the database. A tx created with this set to true
419
        // SHOULD NOT be broadcasted.
420
        //
421
        // NOTE: This method requires the global coin selection lock to be held.
422
        CreateSimpleTx(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
423
                feeRate chainfee.SatPerKWeight, minConfs int32,
424
                strategy base.CoinSelectionStrategy, dryRun bool) (
425
                *txauthor.AuthoredTx, error)
426

427
        // GetTransactionDetails returns a detailed description of a transaction
428
        // given its transaction hash.
429
        GetTransactionDetails(txHash *chainhash.Hash) (
430
                *TransactionDetail, error)
431

432
        // ListUnspentWitness returns all unspent outputs which are version 0
433
        // witness programs. The 'minConfs' and 'maxConfs' parameters
434
        // indicate the minimum and maximum number of confirmations an output
435
        // needs in order to be returned by this method. Passing -1 as
436
        // 'minConfs' indicates that even unconfirmed outputs should be
437
        // returned. Using MaxInt32 as 'maxConfs' implies returning all
438
        // outputs with at least 'minConfs'. The account parameter serves as
439
        // a filter to retrieve the unspent outputs for a specific account.
440
        // When empty, the unspent outputs of all wallet accounts are returned.
441
        //
442
        // NOTE: This method requires the global coin selection lock to be held.
443
        ListUnspentWitness(minConfs, maxConfs int32,
444
                accountFilter string) ([]*Utxo, error)
445

446
        // ListTransactionDetails returns a list of all transactions which are
447
        // relevant to the wallet over [startHeight;endHeight]. If start height
448
        // is greater than end height, the transactions will be retrieved in
449
        // reverse order. To include unconfirmed transactions, endHeight should
450
        // be set to the special value -1. This will return transactions from
451
        // the tip of the chain until the start height (inclusive) and
452
        // unconfirmed transactions. The account parameter serves as a filter to
453
        // retrieve the transactions relevant to a specific account. When
454
        // empty, transactions of all wallet accounts are returned.
455
        ListTransactionDetails(startHeight, endHeight int32,
456
                accountFilter string, indexOffset uint32,
457
                maxTransactions uint32) ([]*TransactionDetail, uint64, uint64,
458
                error)
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
462
        // of the lock's expiration is returned. The expiration of the lock can
463
        // be extended by successive invocations of this call. Outputs can be
464
        // unlocked before their expiration through `ReleaseOutput`.
465
        //
466
        // If the output is not known, wtxmgr.ErrUnknownOutput is returned. If
467
        // the output has already been locked to a different ID, then
468
        // wtxmgr.ErrOutputAlreadyLocked is returned.
469
        //
470
        // NOTE: This method requires the global coin selection lock to be held.
471
        LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
472
                duration time.Duration) (time.Time, error)
473

474
        // ReleaseOutput unlocks an output, allowing it to be available for coin
475
        // selection if it remains unspent. The ID should match the one used to
476
        // originally lock the output.
477
        //
478
        // NOTE: This method requires the global coin selection lock to be held.
479
        ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error
480

481
        // ListLeasedOutputs returns a list of all currently locked outputs.
482
        ListLeasedOutputs() ([]*base.ListLeasedOutputResult, error)
483

484
        // PublishTransaction performs cursory validation (dust checks, etc),
485
        // then finally broadcasts the passed transaction to the Bitcoin network.
486
        // If the transaction is rejected because it is conflicting with an
487
        // already known transaction, ErrDoubleSpend is returned. If the
488
        // transaction is already known (published already), no error will be
489
        // returned. Other error returned depends on the currently active chain
490
        // backend. It takes an optional label which will save a label with the
491
        // published transaction.
492
        PublishTransaction(tx *wire.MsgTx, label string) error
493

494
        // LabelTransaction adds a label to a transaction. If the tx already
495
        // has a label, this call will fail unless the overwrite parameter
496
        // is set. Labels must not be empty, and they are limited to 500 chars.
497
        LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error
498

499
        // FetchTx attempts to fetch a transaction in the wallet's database
500
        // identified by the passed transaction hash. If the transaction can't
501
        // be found, then a nil pointer is returned.
502
        FetchTx(chainhash.Hash) (*wire.MsgTx, error)
503

504
        // RemoveDescendants attempts to remove any transaction from the
505
        // wallet's tx store (that may be unconfirmed) that spends outputs
506
        // created by the passed transaction. This remove propagates
507
        // recursively down the chain of descendent transactions.
508
        RemoveDescendants(*wire.MsgTx) error
509

510
        // FundPsbt creates a fully populated PSBT packet that contains enough
511
        // inputs to fund the outputs specified in the passed in packet with the
512
        // specified fee rate. If there is change left, a change output from the
513
        // internal wallet is added and the index of the change output is
514
        // returned. Otherwise no additional output is created and the index -1
515
        // is returned. If no custom change scope is specified, the BIP0084 will
516
        // be used for default accounts and single imported public keys. For
517
        // custom account, no key scope should be provided as the coin selection
518
        // key scope will always be used to generate the change address.
519
        //
520
        // NOTE: If the packet doesn't contain any inputs, coin selection is
521
        // performed automatically. The account parameter must be non-empty as
522
        // it determines which set of coins are eligible for coin selection. If
523
        // the packet does contain any inputs, it is assumed that full coin
524
        // selection happened externally and no additional inputs are added. If
525
        // the specified inputs aren't enough to fund the outputs with the given
526
        // fee rate, an error is returned. No lock lease is acquired for any of
527
        // the selected/validated inputs. It is in the caller's responsibility
528
        // to lock the inputs before handing them out.
529
        FundPsbt(packet *psbt.Packet, minConfs int32,
530
                feeRate chainfee.SatPerKWeight, account string,
531
                changeScope *waddrmgr.KeyScope,
532
                strategy base.CoinSelectionStrategy,
533
                allowUtxo func(wtxmgr.Credit) bool) (int32, error)
534

535
        // SignPsbt expects a partial transaction with all inputs and outputs
536
        // fully declared and tries to sign all unsigned inputs that have all
537
        // required fields (UTXO information, BIP32 derivation information,
538
        // witness or sig scripts) set.
539
        // If no error is returned, the PSBT is ready to be given to the next
540
        // signer or to be finalized if lnd was the last signer.
541
        //
542
        // NOTE: This method only signs inputs (and only those it can sign), it
543
        // does not perform any other tasks (such as coin selection, UTXO
544
        // locking or input/output/fee value validation, PSBT finalization). Any
545
        // input that is incomplete will be skipped.
546
        SignPsbt(packet *psbt.Packet) ([]uint32, error)
547

548
        // FinalizePsbt expects a partial transaction with all inputs and
549
        // outputs fully declared and tries to sign all inputs that belong to
550
        // the specified account. Lnd must be the last signer of the
551
        // transaction. That means, if there are any unsigned non-witness inputs
552
        // or inputs without UTXO information attached or inputs without witness
553
        // data that do not belong to lnd's wallet, this method will fail. If no
554
        // error is returned, the PSBT is ready to be extracted and the final TX
555
        // within to be broadcast.
556
        //
557
        // NOTE: This method does NOT publish the transaction after it's been
558
        // finalized successfully.
559
        FinalizePsbt(packet *psbt.Packet, account string) error
560

561
        // DecorateInputs fetches the UTXO information of all inputs it can
562
        // identify and adds the required information to the package's inputs.
563
        // The failOnUnknown boolean controls whether the method should return
564
        // an error if it cannot identify an input or if it should just skip it.
565
        DecorateInputs(packet *psbt.Packet, failOnUnknown bool) error
566

567
        // SubscribeTransactions returns a TransactionSubscription client which
568
        // is capable of receiving async notifications as new transactions
569
        // related to the wallet are seen within the network, or found in
570
        // blocks.
571
        //
572
        // NOTE: a non-nil error should be returned if notifications aren't
573
        // supported.
574
        //
575
        // TODO(roasbeef): make distinct interface?
576
        SubscribeTransactions() (TransactionSubscription, error)
577

578
        // IsSynced returns a boolean indicating if from the PoV of the wallet,
579
        // it has fully synced to the current best block in the main chain.
580
        // It also returns an int64 indicating the timestamp of the best block
581
        // known to the wallet, expressed in Unix epoch time
582
        IsSynced() (bool, int64, error)
583

584
        // GetRecoveryInfo returns a boolean indicating whether the wallet is
585
        // started in recovery mode. It also returns a float64 indicating the
586
        // recovery progress made so far.
587
        GetRecoveryInfo() (bool, float64, error)
588

589
        // Start initializes the wallet, making any necessary connections,
590
        // starting up required goroutines etc.
591
        Start() error
592

593
        // Stop signals the wallet for shutdown. Shutdown may entail closing
594
        // any active sockets, database handles, stopping goroutines, etc.
595
        Stop() error
596

597
        // BackEnd returns a name for the wallet's backing chain service,
598
        // which could be e.g. btcd, bitcoind, neutrino, or another consensus
599
        // service.
600
        BackEnd() string
601

602
        // CheckMempoolAcceptance checks whether a transaction follows mempool
603
        // policies and returns an error if it cannot be accepted into the
604
        // mempool.
605
        CheckMempoolAcceptance(tx *wire.MsgTx) error
606
}
607

608
// BlockChainIO is a dedicated source which will be used to obtain queries
609
// related to the current state of the blockchain. The data returned by each of
610
// the defined methods within this interface should always return the most up
611
// to date data possible.
612
//
613
// TODO(roasbeef): move to diff package perhaps?
614
// TODO(roasbeef): move publish txn here?
615
type BlockChainIO interface {
616
        // GetBestBlock returns the current height and block hash of the valid
617
        // most-work chain the implementation is aware of.
618
        GetBestBlock() (*chainhash.Hash, int32, error)
619

620
        // GetUtxo attempts to return the passed outpoint if it's still a
621
        // member of the utxo set. The passed height hint should be the "birth
622
        // height" of the passed outpoint. The script passed should be the
623
        // script that the outpoint creates. In the case that the output is in
624
        // the UTXO set, then the output corresponding to that output is
625
        // returned.  Otherwise, a non-nil error will be returned.
626
        // As for some backends this call can initiate a rescan, the passed
627
        // cancel channel can be closed to abort the call.
628
        GetUtxo(op *wire.OutPoint, pkScript []byte, heightHint uint32,
629
                cancel <-chan struct{}) (*wire.TxOut, error)
630

631
        // GetBlockHash returns the hash of the block in the best blockchain
632
        // at the given height.
633
        GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
634

635
        // GetBlock returns the block in the main chain identified by the given
636
        // hash.
637
        GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error)
638

639
        // GetBlockHeader returns the block header for the given block hash.
640
        GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
641
}
642

643
// MessageSigner represents an abstract object capable of signing arbitrary
644
// messages. The capabilities of this interface are used to sign announcements
645
// to the network, or just arbitrary messages that leverage the wallet's keys
646
// to attest to some message.
647
type MessageSigner interface {
648
        // SignMessage attempts to sign a target message with the private key
649
        // described in the key locator. If the target private key is unable to
650
        // be found, then an error will be returned. The actual digest signed is
651
        // the single or double SHA-256 of the passed message.
652
        SignMessage(keyLoc keychain.KeyLocator, msg []byte,
653
                doubleHash bool) (*ecdsa.Signature, error)
654
}
655

656
// AddrWithKey wraps a normal addr, but also includes the internal key for the
657
// delivery addr if known.
658
type AddrWithKey struct {
659
        lnwire.DeliveryAddress
660

661
        InternalKey fn.Option[keychain.KeyDescriptor]
662

663
        // TODO(roasbeef): consolidate w/ instance in chan closer
664
}
665

666
// InternalKeyForAddr returns the internal key associated with a taproot
667
// address.
668
func InternalKeyForAddr(wallet WalletController, netParams *chaincfg.Params,
669
        deliveryScript []byte) (fn.Option[keychain.KeyDescriptor], error) {
1✔
670

1✔
671
        none := fn.None[keychain.KeyDescriptor]()
1✔
672

1✔
673
        pkScript, err := txscript.ParsePkScript(deliveryScript)
1✔
674
        if err != nil {
1✔
675
                return none, err
×
676
        }
×
677
        addr, err := pkScript.Address(netParams)
1✔
678
        if err != nil {
1✔
679
                return none, err
×
680
        }
×
681

682
        // If it's not a taproot address, we don't require to know the internal
683
        // key in the first place. So we don't return an error here, but also no
684
        // internal key.
685
        _, isTaproot := addr.(*btcutil.AddressTaproot)
1✔
686
        if !isTaproot {
2✔
687
                return none, nil
1✔
688
        }
1✔
689

690
        walletAddr, err := wallet.AddressInfo(addr)
1✔
691
        if err != nil {
2✔
692
                // If the error is that the address can't be found, it is not
1✔
693
                // an error. This happens when any channel which is not a custom
1✔
694
                // taproot channel is cooperatively closed to an external P2TR
1✔
695
                // address. In this case there is no internal key associated
1✔
696
                // with the address. Callers can use the .Option() method to get
1✔
697
                // an option value.
1✔
698
                var managerErr waddrmgr.ManagerError
1✔
699
                if errors.As(err, &managerErr) &&
1✔
700
                        managerErr.ErrorCode == waddrmgr.ErrAddressNotFound {
2✔
701

1✔
702
                        return none, nil
1✔
703
                }
1✔
704

705
                return none, err
×
706
        }
707

708
        // No wallet addr. No error, but we'll return an nil error value here,
709
        // as callers can use the .Option() method to get an option value.
710
        if walletAddr == nil {
1✔
711
                return none, nil
×
712
        }
×
713

714
        // Imported addresses do not provide private keys, so they do not
715
        // implement waddrmgr.ManagedPubKeyAddress. See RPC ImportTapscript.
716
        if walletAddr.Imported() {
2✔
717
                return none, nil
1✔
718
        }
1✔
719

720
        pubKeyAddr, ok := walletAddr.(waddrmgr.ManagedPubKeyAddress)
1✔
721
        if !ok {
1✔
722
                return none, fmt.Errorf("expected pubkey addr, got %T",
×
723
                        walletAddr)
×
724
        }
×
725

726
        _, derivationPath, _ := pubKeyAddr.DerivationInfo()
1✔
727

1✔
728
        return fn.Some[keychain.KeyDescriptor](keychain.KeyDescriptor{
1✔
729
                KeyLocator: keychain.KeyLocator{
1✔
730
                        Family: keychain.KeyFamily(derivationPath.Account),
1✔
731
                        Index:  derivationPath.Index,
1✔
732
                },
1✔
733
                PubKey: pubKeyAddr.PubKey(),
1✔
734
        }), nil
1✔
735
}
736

737
// WalletDriver represents a "driver" for a particular concrete
738
// WalletController implementation. A driver is identified by a globally unique
739
// string identifier along with a 'New()' method which is responsible for
740
// initializing a particular WalletController concrete implementation.
741
type WalletDriver struct {
742
        // WalletType is a string which uniquely identifies the
743
        // WalletController that this driver, drives.
744
        WalletType string
745

746
        // New creates a new instance of a concrete WalletController
747
        // implementation given a variadic set up arguments. The function takes
748
        // a variadic number of interface parameters in order to provide
749
        // initialization flexibility, thereby accommodating several potential
750
        // WalletController implementations.
751
        New func(args ...interface{}) (WalletController, error)
752

753
        // BackEnds returns a list of available chain service drivers for the
754
        // wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc.
755
        BackEnds func() []string
756
}
757

758
var (
759
        wallets     = make(map[string]*WalletDriver)
760
        registerMtx sync.Mutex
761
)
762

763
// RegisteredWallets returns a slice of all currently registered notifiers.
764
//
765
// NOTE: This function is safe for concurrent access.
UNCOV
766
func RegisteredWallets() []*WalletDriver {
×
UNCOV
767
        registerMtx.Lock()
×
UNCOV
768
        defer registerMtx.Unlock()
×
UNCOV
769

×
UNCOV
770
        registeredWallets := make([]*WalletDriver, 0, len(wallets))
×
UNCOV
771
        for _, wallet := range wallets {
×
UNCOV
772
                registeredWallets = append(registeredWallets, wallet)
×
UNCOV
773
        }
×
774

UNCOV
775
        return registeredWallets
×
776
}
777

778
// RegisterWallet registers a WalletDriver which is capable of driving a
779
// concrete WalletController interface. In the case that this driver has
780
// already been registered, an error is returned.
781
//
782
// NOTE: This function is safe for concurrent access.
783
func RegisterWallet(driver *WalletDriver) error {
1✔
784
        registerMtx.Lock()
1✔
785
        defer registerMtx.Unlock()
1✔
786

1✔
787
        if _, ok := wallets[driver.WalletType]; ok {
1✔
788
                return fmt.Errorf("wallet already registered")
×
789
        }
×
790

791
        wallets[driver.WalletType] = driver
1✔
792

1✔
793
        return nil
1✔
794
}
795

796
// SupportedWallets returns a slice of strings that represents the wallet
797
// drivers that have been registered and are therefore supported.
798
//
799
// NOTE: This function is safe for concurrent access.
800
func SupportedWallets() []string {
×
801
        registerMtx.Lock()
×
802
        defer registerMtx.Unlock()
×
803

×
804
        supportedWallets := make([]string, 0, len(wallets))
×
805
        for walletName := range wallets {
×
806
                supportedWallets = append(supportedWallets, walletName)
×
807
        }
×
808

809
        return supportedWallets
×
810
}
811

812
// FetchFundingTxWrapper is a wrapper around FetchFundingTx, except that it will
813
// exit when the supplied quit channel is closed.
814
func FetchFundingTxWrapper(chain BlockChainIO, chanID *lnwire.ShortChannelID,
815
        quit chan struct{}) (*wire.MsgTx, error) {
1✔
816

1✔
817
        txChan := make(chan *wire.MsgTx, 1)
1✔
818
        errChan := make(chan error, 1)
1✔
819

1✔
820
        go func() {
2✔
821
                tx, err := FetchFundingTx(chain, chanID)
1✔
822
                if err != nil {
1✔
UNCOV
823
                        errChan <- err
×
UNCOV
824
                        return
×
UNCOV
825
                }
×
826

827
                txChan <- tx
1✔
828
        }()
829

830
        select {
1✔
831
        case tx := <-txChan:
1✔
832
                return tx, nil
1✔
833

UNCOV
834
        case err := <-errChan:
×
UNCOV
835
                return nil, err
×
836

837
        case <-quit:
×
838
                return nil, fmt.Errorf("quit channel passed to " +
×
839
                        "lnwallet.FetchFundingTxWrapper has been closed")
×
840
        }
841
}
842

843
// FetchFundingTx uses the given BlockChainIO to fetch and return the funding
844
// transaction identified by the passed short channel ID.
845
//
846
// TODO(roasbeef): replace with call to GetBlockTransaction? (would allow to
847
// later use getblocktxn).
848
func FetchFundingTx(chain BlockChainIO,
849
        chanID *lnwire.ShortChannelID) (*wire.MsgTx, error) {
1✔
850

1✔
851
        // First fetch the block hash by the block number encoded, then use
1✔
852
        // that hash to fetch the block itself.
1✔
853
        blockNum := int64(chanID.BlockHeight)
1✔
854
        blockHash, err := chain.GetBlockHash(blockNum)
1✔
855
        if err != nil {
1✔
856
                return nil, err
×
857
        }
×
858

859
        fundingBlock, err := chain.GetBlock(blockHash)
1✔
860
        if err != nil {
1✔
UNCOV
861
                return nil, err
×
UNCOV
862
        }
×
863

864
        // As a sanity check, ensure that the advertised transaction index is
865
        // within the bounds of the total number of transactions within a
866
        // block.
867
        numTxns := uint32(len(fundingBlock.Transactions))
1✔
868
        if chanID.TxIndex > numTxns-1 {
1✔
869
                return nil, fmt.Errorf("tx_index=#%v "+
×
870
                        "is out of range (max_index=%v), network_chan_id=%v",
×
871
                        chanID.TxIndex, numTxns-1, chanID)
×
872
        }
×
873

874
        return fundingBlock.Transactions[chanID.TxIndex].Copy(), nil
1✔
875
}
876

877
// FetchPKScriptWithQuit fetches the output script for the given SCID and exits
878
// early with an error if the provided quit channel is closed before
879
// completion.
880
func FetchPKScriptWithQuit(chain BlockChainIO, chanID *lnwire.ShortChannelID,
881
        quit chan struct{}) ([]byte, error) {
×
882

×
883
        tx, err := FetchFundingTxWrapper(chain, chanID, quit)
×
884
        if err != nil {
×
885
                return nil, err
×
886
        }
×
887

888
        outputLocator := chanvalidate.ShortChanIDChanLocator{
×
889
                ID: *chanID,
×
890
        }
×
891

×
892
        output, _, err := outputLocator.Locate(tx)
×
893
        if err != nil {
×
894
                return nil, err
×
895
        }
×
896

897
        return output.PkScript, nil
×
898
}
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