-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
wallet.go
2850 lines (2442 loc) · 92.3 KB
/
wallet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package lnwallet
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"math"
"net"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// The size of the buffered queue of requests to the wallet from the
// outside word.
msgBufferSize = 100
// AnchorChanReservedValue is the amount we'll keep around in the
// wallet in case we have to fee bump anchor channels on force close.
// TODO(halseth): update constant to target a specific commit size at
// set fee rate.
AnchorChanReservedValue = btcutil.Amount(10_000)
// MaxAnchorChanReservedValue is the maximum value we'll reserve for
// anchor channel fee bumping. We cap it at 10 times the per-channel
// amount such that nodes with a high number of channels don't have to
// keep around a very large amount for the unlikely scenario that they
// all close at the same time.
MaxAnchorChanReservedValue = 10 * AnchorChanReservedValue
)
var (
// ErrPsbtFundingRequired is the error that is returned during the
// contribution handling process if the process should be paused for
// the construction of a PSBT outside of lnd's wallet.
ErrPsbtFundingRequired = errors.New("PSBT funding required")
// ErrReservedValueInvalidated is returned if we try to publish a
// transaction that would take the walletbalance below what we require
// to keep around to fee bump our open anchor channels.
ErrReservedValueInvalidated = errors.New("reserved wallet balance " +
"invalidated: transaction would leave insufficient funds for " +
"fee bumping anchor channel closings (see debug log for details)")
// ErrEmptyPendingChanID is returned when an empty value is used for
// the pending channel ID.
ErrEmptyPendingChanID = errors.New("pending channel ID is empty")
// ErrDuplicatePendingChanID is returned when an existing pending
// channel ID is registered again.
ErrDuplicatePendingChanID = errors.New("duplicate pending channel ID")
)
// PsbtFundingRequired is a type that implements the error interface and
// contains the information needed to construct a PSBT.
type PsbtFundingRequired struct {
// Intent is the pending PSBT funding intent that needs to be funded
// if the wrapping error is returned.
Intent *chanfunding.PsbtIntent
}
// Error returns the underlying error.
//
// NOTE: This method is part of the error interface.
func (p *PsbtFundingRequired) Error() string {
return ErrPsbtFundingRequired.Error()
}
// AuxFundingDesc stores a series of attributes that may be used to modify the
// way the channel funding occurs. This struct contains information that can
// only be derived once both sides have received and sent their contributions
// to the channel (keys, etc.).
type AuxFundingDesc struct {
// CustomFundingBlob is a custom blob that'll be stored in the database
// within the OpenChannel struct. This should represent information
// static to the channel lifetime.
CustomFundingBlob tlv.Blob
// CustomLocalCommitBlob is a custom blob that'll be stored in the
// first commitment entry for the local party.
CustomLocalCommitBlob tlv.Blob
// CustomRemoteCommitBlob is a custom blob that'll be stored in the
// first commitment entry for the remote party.
CustomRemoteCommitBlob tlv.Blob
// LocalInitAuxLeaves is the set of aux leaves that'll be used for our
// very first commitment state.
LocalInitAuxLeaves CommitAuxLeaves
// RemoteInitAuxLeaves is the set of aux leaves that'll be used for the
// very first commitment state for the remote party.
RemoteInitAuxLeaves CommitAuxLeaves
}
// InitFundingReserveMsg is the first message sent to initiate the workflow
// required to open a payment channel with a remote peer. The initial required
// parameters are configurable across channels. These parameters are to be
// chosen depending on the fee climate within the network, and time value of
// funds to be locked up within the channel. Upon success a ChannelReservation
// will be created in order to track the lifetime of this pending channel.
// Outputs selected will be 'locked', making them unavailable, for any other
// pending reservations. Therefore, all channels in reservation limbo will be
// periodically timed out after an idle period in order to avoid "exhaustion"
// attacks.
type InitFundingReserveMsg struct {
// ChainHash denotes that chain to be used to ultimately open the
// target channel.
ChainHash *chainhash.Hash
// PendingChanID is the pending channel ID for this funding flow as
// used in the wire protocol.
PendingChanID [32]byte
// NodeID is the ID of the remote node we would like to open a channel
// with.
NodeID *btcec.PublicKey
// NodeAddr is the address port that we used to either establish or
// accept the connection which led to the negotiation of this funding
// workflow.
NodeAddr net.Addr
// SubtractFees should be set if we intend to spend exactly
// LocalFundingAmt when opening the channel, subtracting the fees from
// the funding output. This can be used for instance to use all our
// remaining funds to open the channel, since it will take fees into
// account.
SubtractFees bool
// LocalFundingAmt is the amount of funds requested from us for this
// channel.
LocalFundingAmt btcutil.Amount
// RemoteFundingAmnt is the amount of funds the remote will contribute
// to this channel.
RemoteFundingAmt btcutil.Amount
// FundUpToMaxAmt defines if channel funding should try to add as many
// funds to the channel opening as possible up to this amount. If used,
// then MinFundAmt is treated as the minimum amount of funds that must
// be available to open the channel. If set to zero it is ignored.
FundUpToMaxAmt btcutil.Amount
// MinFundAmt denotes the minimum channel capacity that has to be
// allocated iff the FundUpToMaxAmt is set.
MinFundAmt btcutil.Amount
// Outpoints is a list of client-selected outpoints that should be used
// for funding a channel. If LocalFundingAmt is specified then this
// amount is allocated from the sum of outpoints towards funding. If the
// FundUpToMaxAmt is specified the entirety of selected funds is
// allocated towards channel funding.
Outpoints []wire.OutPoint
// RemoteChanReserve is the channel reserve we required for the remote
// peer.
RemoteChanReserve btcutil.Amount
// CommitFeePerKw is the starting accepted satoshis/Kw fee for the set
// of initial commitment transactions. In order to ensure timely
// confirmation, it is recommended that this fee should be generous,
// paying some multiple of the accepted base fee rate of the network.
CommitFeePerKw chainfee.SatPerKWeight
// FundingFeePerKw is the fee rate in sat/kw to use for the initial
// funding transaction.
FundingFeePerKw chainfee.SatPerKWeight
// PushMSat is the number of milli-satoshis that should be pushed over
// the responder as part of the initial channel creation.
PushMSat lnwire.MilliSatoshi
// Flags are the channel flags specified by the initiator in the
// open_channel message.
Flags lnwire.FundingFlag
// MinConfs indicates the minimum number of confirmations that each
// output selected to fund the channel should satisfy.
MinConfs int32
// CommitType indicates what type of commitment type the channel should
// be using, like tweakless or anchors.
CommitType CommitmentType
// ChanFunder is an optional channel funder that allows the caller to
// control exactly how the channel funding is carried out. If not
// specified, then the default chanfunding.WalletAssembler will be
// used.
ChanFunder chanfunding.Assembler
// AllowUtxoForFunding enables the channel funding workflow to restrict
// the selection of utxos when selecting the inputs for the channel
// opening. This does ONLY apply for the internal wallet backed channel
// opening case.
//
// NOTE: This is very useful when opening channels with unconfirmed
// inputs to make sure stable non-replaceable inputs are used.
AllowUtxoForFunding func(Utxo) bool
// ZeroConf is a boolean that is true if a zero-conf channel was
// negotiated.
ZeroConf bool
// OptionScidAlias is a boolean that is true if an option-scid-alias
// channel type was explicitly negotiated.
OptionScidAlias bool
// ScidAliasFeature is true if the option-scid-alias feature bit was
// negotiated.
ScidAliasFeature bool
// Memo is any arbitrary information we wish to store locally about the
// channel that will be useful to our future selves.
Memo []byte
// TapscriptRoot is an optional tapscript root that if provided, will
// be used to create the combined key for musig2 based channels.
TapscriptRoot fn.Option[chainhash.Hash]
// err is a channel in which all errors will be sent across. Will be
// nil if this initial set is successful.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
// resp is channel in which a ChannelReservation with our contributions
// filled in will be sent across this channel in the case of a
// successfully reservation initiation. In the case of an error, this
// will read a nil pointer.
//
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
resp chan *ChannelReservation
}
// fundingReserveCancelMsg is a message reserved for cancelling an existing
// channel reservation identified by its reservation ID. Cancelling a reservation
// frees its locked outputs up, for inclusion within further reservations.
type fundingReserveCancelMsg struct {
pendingFundingID uint64
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error // Buffered
}
// addContributionMsg represents a message executing the second phase of the
// channel reservation workflow. This message carries the counterparty's
// "contribution" to the payment channel. In the case that this message is
// processed without generating any errors, then channel reservation will then
// be able to construct the funding tx, both commitment transactions, and
// finally generate signatures for all our inputs to the funding transaction,
// and for the remote node's version of the commitment transaction.
type addContributionMsg struct {
pendingFundingID uint64
contribution *ChannelContribution
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// continueContributionMsg represents a message that signals that the
// interrupted funding process involving a PSBT can now be continued because the
// finalized transaction is now available.
type continueContributionMsg struct {
pendingFundingID uint64
// auxFundingDesc is an optional descriptor that contains information
// about the custom channel funding flow.
auxFundingDesc fn.Option[AuxFundingDesc]
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addSingleContributionMsg represents a message executing the second phase of
// a single funder channel reservation workflow. This messages carries the
// counterparty's "contribution" to the payment channel. As this message is
// sent when on the responding side to a single funder workflow, no further
// action apart from storing the provided contribution is carried out.
type addSingleContributionMsg struct {
pendingFundingID uint64
contribution *ChannelContribution
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addCounterPartySigsMsg represents the final message required to complete,
// and 'open' a payment channel. This message carries the counterparty's
// signatures for each of their inputs to the funding transaction, and also a
// signature allowing us to spend our version of the commitment transaction.
// If we're able to verify all the signatures are valid, the funding transaction
// will be broadcast to the network. After the funding transaction gains a
// configurable number of confirmations, the channel is officially considered
// 'open'.
type addCounterPartySigsMsg struct {
pendingFundingID uint64
// Should be order of sorted inputs that are theirs. Sorting is done
// in accordance to BIP-69:
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
theirFundingInputScripts []*input.Script
// This should be 1/2 of the signatures needed to successfully spend our
// version of the commitment transaction.
theirCommitmentSig input.Signature
// This channel is used to return the completed channel after the wallet
// has completed all of its stages in the funding process.
completeChan chan *channeldb.OpenChannel
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// addSingleFunderSigsMsg represents the next-to-last message required to
// complete a single-funder channel workflow. Once the initiator is able to
// construct the funding transaction, they send both the outpoint and a
// signature for our version of the commitment transaction. Once this message
// is processed we (the responder) are able to construct both commitment
// transactions, signing the remote party's version.
type addSingleFunderSigsMsg struct {
pendingFundingID uint64
// auxFundingDesc is an optional descriptor that contains information
// about the custom channel funding flow.
auxFundingDesc fn.Option[AuxFundingDesc]
// fundingOutpoint is the outpoint of the completed funding
// transaction as assembled by the workflow initiator.
fundingOutpoint *wire.OutPoint
// theirCommitmentSig are the 1/2 of the signatures needed to
// successfully spend our version of the commitment transaction.
theirCommitmentSig input.Signature
// This channel is used to return the completed channel after the wallet
// has completed all of its stages in the funding process.
completeChan chan *channeldb.OpenChannel
// NOTE: In order to avoid deadlocks, this channel MUST be buffered.
err chan error
}
// CheckReservedValueTxReq is the request struct used to call
// CheckReservedValueTx with. It contains the transaction to check as well as
// an optional explicitly defined index to denote a change output that is not
// watched by the wallet.
type CheckReservedValueTxReq struct {
// Tx is the transaction to check the outputs for.
Tx *wire.MsgTx
// ChangeIndex denotes an optional output index that can be explicitly
// set for a change that is not being watched by the wallet and would
// otherwise not be recognized as a change output.
ChangeIndex *int
}
// LightningWallet is a domain specific, yet general Bitcoin wallet capable of
// executing workflow required to interact with the Lightning Network. It is
// domain specific in the sense that it understands all the fancy scripts used
// within the Lightning Network, channel lifetimes, etc. However, it embeds a
// general purpose Bitcoin wallet within it. Therefore, it is also able to
// serve as a regular Bitcoin wallet which uses HD keys. The wallet is highly
// concurrent internally. All communication, and requests towards the wallet
// are dispatched as messages over channels, ensuring thread safety across all
// operations. Interaction has been designed independent of any peer-to-peer
// communication protocol, allowing the wallet to be self-contained and
// embeddable within future projects interacting with the Lightning Network.
//
// NOTE: At the moment the wallet requires a btcd full node, as it's dependent
// on btcd's websockets notifications as event triggers during the lifetime of a
// channel. However, once the chainntnfs package is complete, the wallet will
// be compatible with multiple RPC/notification services such as Electrum,
// Bitcoin Core + ZeroMQ, etc. Eventually, the wallet won't require a full-node
// at all, as SPV support is integrated into btcwallet.
type LightningWallet struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
nextFundingID uint64 // To be used atomically.
// Cfg is the configuration struct that will be used by the wallet to
// access the necessary interfaces and default it needs to carry on its
// duties.
Cfg Config
// WalletController is the core wallet, all non Lightning Network
// specific interaction is proxied to the internal wallet.
WalletController
// SecretKeyRing is the interface we'll use to derive any keys related
// to our purpose within the network including: multi-sig keys, node
// keys, revocation keys, etc.
keychain.SecretKeyRing
// This mutex MUST be held when performing coin selection in order to
// avoid inadvertently creating multiple funding transaction which
// double spend inputs across each other.
coinSelectMtx sync.RWMutex
// All messages to the wallet are to be sent across this channel.
msgChan chan interface{}
// Incomplete payment channels are stored in the map below. An intent
// to create a payment channel is tracked as a "reservation" within
// limbo. Once the final signatures have been exchanged, a reservation
// is removed from limbo. Each reservation is tracked by a unique
// monotonically integer. All requests concerning the channel MUST
// carry a valid, active funding ID.
fundingLimbo map[uint64]*ChannelReservation
// reservationIDs maps a pending channel ID to the reservation ID used
// as key in the fundingLimbo map. Used to easily look up a channel
// reservation given a pending channel ID.
reservationIDs map[[32]byte]uint64
limboMtx sync.RWMutex
// lockedOutPoints is a set of the currently locked outpoint. This
// information is kept in order to provide an easy way to unlock all
// the currently locked outpoints.
lockedOutPoints map[wire.OutPoint]struct{}
// fundingIntents houses all the "interception" registered by a caller
// using the RegisterFundingIntent method.
intentMtx sync.RWMutex
fundingIntents map[[32]byte]chanfunding.Intent
quit chan struct{}
wg sync.WaitGroup
}
// NewLightningWallet creates/opens and initializes a LightningWallet instance.
// If the wallet has never been created (according to the passed dataDir), first-time
// setup is executed.
func NewLightningWallet(Cfg Config) (*LightningWallet, error) {
return &LightningWallet{
Cfg: Cfg,
SecretKeyRing: Cfg.SecretKeyRing,
WalletController: Cfg.WalletController,
msgChan: make(chan interface{}, msgBufferSize),
nextFundingID: 0,
fundingLimbo: make(map[uint64]*ChannelReservation),
reservationIDs: make(map[[32]byte]uint64),
lockedOutPoints: make(map[wire.OutPoint]struct{}),
fundingIntents: make(map[[32]byte]chanfunding.Intent),
quit: make(chan struct{}),
}, nil
}
// Startup establishes a connection to the RPC source, and spins up all
// goroutines required to handle incoming messages.
func (l *LightningWallet) Startup() error {
// Already started?
if atomic.AddInt32(&l.started, 1) != 1 {
return nil
}
// Start the underlying wallet controller.
if err := l.Start(); err != nil {
return err
}
if l.Cfg.Rebroadcaster != nil {
go func() {
if err := l.Cfg.Rebroadcaster.Start(); err != nil {
walletLog.Errorf("unable to start "+
"rebroadcaster: %v", err)
}
}()
}
l.wg.Add(1)
go l.requestHandler()
return nil
}
// Shutdown gracefully stops the wallet, and all active goroutines.
func (l *LightningWallet) Shutdown() error {
if atomic.AddInt32(&l.shutdown, 1) != 1 {
return nil
}
// Signal the underlying wallet controller to shutdown, waiting until
// all active goroutines have been shutdown.
if err := l.Stop(); err != nil {
return err
}
if l.Cfg.Rebroadcaster != nil && l.Cfg.Rebroadcaster.Started() {
l.Cfg.Rebroadcaster.Stop()
}
close(l.quit)
l.wg.Wait()
return nil
}
// PublishTransaction wraps the wallet controller tx publish method with an
// extra rebroadcaster layer if the sub-system is configured.
func (l *LightningWallet) PublishTransaction(tx *wire.MsgTx,
label string) error {
sendTxToWallet := func() error {
return l.WalletController.PublishTransaction(tx, label)
}
// If we don't have rebroadcaster then we can exit early (and send only
// to the wallet).
if l.Cfg.Rebroadcaster == nil || !l.Cfg.Rebroadcaster.Started() {
return sendTxToWallet()
}
// We pass this into the rebroadcaster first, so the initial attempt
// will succeed if the transaction isn't yet in the mempool. However we
// ignore the error here as this might be resent on start up and the
// transaction already exists.
_ = l.Cfg.Rebroadcaster.Broadcast(tx)
// Then we pass things into the wallet as normal, which'll add the
// transaction label on disk.
if err := sendTxToWallet(); err != nil {
return err
}
// TODO(roasbeef): want diff height actually? no context though
_, bestHeight, err := l.Cfg.ChainIO.GetBestBlock()
if err != nil {
return err
}
txHash := tx.TxHash()
go func() {
const numConfs = 6
txConf, err := l.Cfg.Notifier.RegisterConfirmationsNtfn(
&txHash, tx.TxOut[0].PkScript, numConfs,
uint32(bestHeight),
)
if err != nil {
return
}
select {
case <-txConf.Confirmed:
// TODO(roasbeef): also want to remove from
// rebroadcaster if conflict happens...deeper wallet
// integration?
l.Cfg.Rebroadcaster.MarkAsConfirmed(tx.TxHash())
case <-l.quit:
return
}
}()
return nil
}
// ConfirmedBalance returns the current confirmed balance of a wallet account.
// This methods wraps the internal WalletController method so we're able to
// properly hold the coin select mutex while we compute the balance.
func (l *LightningWallet) ConfirmedBalance(confs int32,
account string) (btcutil.Amount, error) {
l.coinSelectMtx.Lock()
defer l.coinSelectMtx.Unlock()
return l.WalletController.ConfirmedBalance(confs, account)
}
// ListUnspentWitnessFromDefaultAccount returns all unspent outputs from the
// default wallet account which are version 0 witness programs. The 'minConfs'
// and 'maxConfs' parameters indicate the minimum and maximum number of
// confirmations an output needs in order to be returned by this method. Passing
// -1 as 'minConfs' indicates that even unconfirmed outputs should be returned.
// Using MaxInt32 as 'maxConfs' implies returning all outputs with at least
// 'minConfs'.
//
// NOTE: This method requires the global coin selection lock to be held.
func (l *LightningWallet) ListUnspentWitnessFromDefaultAccount(
minConfs, maxConfs int32) ([]*Utxo, error) {
return l.WalletController.ListUnspentWitness(
minConfs, maxConfs, DefaultAccountName,
)
}
// LockedOutpoints returns a list of all currently locked outpoint.
func (l *LightningWallet) LockedOutpoints() []*wire.OutPoint {
outPoints := make([]*wire.OutPoint, 0, len(l.lockedOutPoints))
for outPoint := range l.lockedOutPoints {
outPoint := outPoint
outPoints = append(outPoints, &outPoint)
}
return outPoints
}
// ResetReservations reset the volatile wallet state which tracks all currently
// active reservations.
func (l *LightningWallet) ResetReservations() {
l.nextFundingID = 0
l.fundingLimbo = make(map[uint64]*ChannelReservation)
l.reservationIDs = make(map[[32]byte]uint64)
for outpoint := range l.lockedOutPoints {
_ = l.ReleaseOutput(chanfunding.LndInternalLockID, outpoint)
}
l.lockedOutPoints = make(map[wire.OutPoint]struct{})
}
// ActiveReservations returns a slice of all the currently active
// (non-canceled) reservations.
func (l *LightningWallet) ActiveReservations() []*ChannelReservation {
reservations := make([]*ChannelReservation, 0, len(l.fundingLimbo))
for _, reservation := range l.fundingLimbo {
reservations = append(reservations, reservation)
}
return reservations
}
// requestHandler is the primary goroutine(s) responsible for handling, and
// dispatching replies to all messages.
func (l *LightningWallet) requestHandler() {
defer l.wg.Done()
out:
for {
select {
case m := <-l.msgChan:
switch msg := m.(type) {
case *InitFundingReserveMsg:
l.handleFundingReserveRequest(msg)
case *fundingReserveCancelMsg:
l.handleFundingCancelRequest(msg)
case *addSingleContributionMsg:
l.handleSingleContribution(msg)
case *addContributionMsg:
l.handleContributionMsg(msg)
case *continueContributionMsg:
l.handleChanPointReady(msg)
case *addSingleFunderSigsMsg:
l.handleSingleFunderSigs(msg)
case *addCounterPartySigsMsg:
l.handleFundingCounterPartySigs(msg)
}
case <-l.quit:
// TODO: do some clean up
break out
}
}
}
// InitChannelReservation kicks off the 3-step workflow required to successfully
// open a payment channel with a remote node. As part of the funding
// reservation, the inputs selected for the funding transaction are 'locked'.
// This ensures that multiple channel reservations aren't double spending the
// same inputs in the funding transaction. If reservation initialization is
// successful, a ChannelReservation containing our completed contribution is
// returned. Our contribution contains all the items necessary to allow the
// counterparty to build the funding transaction, and both versions of the
// commitment transaction. Otherwise, an error occurred and a nil pointer along
// with an error are returned.
//
// Once a ChannelReservation has been obtained, two additional steps must be
// processed before a payment channel can be considered 'open'. The second step
// validates, and processes the counterparty's channel contribution. The third,
// and final step verifies all signatures for the inputs of the funding
// transaction, and that the signature we record for our version of the
// commitment transaction is valid.
func (l *LightningWallet) InitChannelReservation(
req *InitFundingReserveMsg) (*ChannelReservation, error) {
req.resp = make(chan *ChannelReservation, 1)
req.err = make(chan error, 1)
select {
case l.msgChan <- req:
case <-l.quit:
return nil, errors.New("wallet shutting down")
}
return <-req.resp, <-req.err
}
// RegisterFundingIntent allows a caller to signal to the wallet that if a
// pending channel ID of expectedID is found, then it can skip constructing a
// new chanfunding.Assembler, and instead use the specified chanfunding.Intent.
// As an example, this lets some of the parameters for funding transaction to
// be negotiated outside the regular funding protocol.
func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte,
shimIntent chanfunding.Intent) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
// Sanity check the pending channel ID is not empty.
var zeroID [32]byte
if expectedID == zeroID {
return ErrEmptyPendingChanID
}
if _, ok := l.fundingIntents[expectedID]; ok {
return fmt.Errorf("%w: already has intent registered: %x",
ErrDuplicatePendingChanID, expectedID[:])
}
l.fundingIntents[expectedID] = shimIntent
return nil
}
// PsbtFundingVerify looks up a previously registered funding intent by its
// pending channel ID and tries to advance the state machine by verifying the
// passed PSBT.
func (l *LightningWallet) PsbtFundingVerify(pendingChanID [32]byte,
packet *psbt.Packet, skipFinalize bool) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pendingChanID]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pendingChanID[:])
}
psbtIntent, ok := intent.(*chanfunding.PsbtIntent)
if !ok {
return fmt.Errorf("incompatible funding intent")
}
if skipFinalize && psbtIntent.ShouldPublishFundingTX() {
return fmt.Errorf("cannot set skip_finalize for channel that " +
"did not set no_publish")
}
err := psbtIntent.Verify(packet, skipFinalize)
if err != nil {
return fmt.Errorf("error verifying PSBT: %w", err)
}
// Get the channel reservation for that corresponds to this pending
// channel ID.
l.limboMtx.Lock()
pid, ok := l.reservationIDs[pendingChanID]
if !ok {
l.limboMtx.Unlock()
return fmt.Errorf("no channel reservation found for "+
"pendingChannelID(%x)", pendingChanID[:])
}
pendingReservation, ok := l.fundingLimbo[pid]
l.limboMtx.Unlock()
if !ok {
return fmt.Errorf("no channel reservation found for "+
"reservation ID %v", pid)
}
// Now the PSBT has been populated and verified, we can again check
// whether the value reserved for anchor fee bumping is respected.
isPublic := pendingReservation.partialState.ChannelFlags&lnwire.FFAnnounceChannel != 0
hasAnchors := pendingReservation.partialState.ChanType.HasAnchors()
return l.enforceNewReservedValue(intent, isPublic, hasAnchors)
}
// PsbtFundingFinalize looks up a previously registered funding intent by its
// pending channel ID and tries to advance the state machine by finalizing the
// passed PSBT.
func (l *LightningWallet) PsbtFundingFinalize(pid [32]byte, packet *psbt.Packet,
rawTx *wire.MsgTx) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pid]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pid[:])
}
psbtIntent, ok := intent.(*chanfunding.PsbtIntent)
if !ok {
return fmt.Errorf("incompatible funding intent")
}
// Either the PSBT or the raw TX must be set.
switch {
case packet != nil && rawTx == nil:
err := psbtIntent.Finalize(packet)
if err != nil {
return fmt.Errorf("error finalizing PSBT: %w", err)
}
case rawTx != nil && packet == nil:
err := psbtIntent.FinalizeRawTX(rawTx)
if err != nil {
return fmt.Errorf("error finalizing raw TX: %w", err)
}
default:
return fmt.Errorf("either a PSBT or raw TX must be specified")
}
return nil
}
// CancelFundingIntent allows a caller to cancel a previously registered
// funding intent. If no intent was found, then an error will be returned.
func (l *LightningWallet) CancelFundingIntent(pid [32]byte) error {
l.intentMtx.Lock()
defer l.intentMtx.Unlock()
intent, ok := l.fundingIntents[pid]
if !ok {
return fmt.Errorf("no funding intent found for "+
"pendingChannelID(%x)", pid[:])
}
// Give the intent a chance to clean up after itself, removing coin
// locks or similar reserved resources.
intent.Cancel()
delete(l.fundingIntents, pid)
return nil
}
// handleFundingReserveRequest processes a message intending to create, and
// validate a funding reservation request.
func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg) {
noFundsCommitted := req.LocalFundingAmt == 0 &&
req.RemoteFundingAmt == 0 && req.FundUpToMaxAmt == 0
// It isn't possible to create a channel with zero funds committed.
if noFundsCommitted {
err := ErrZeroCapacity()
req.err <- err
req.resp <- nil
return
}
// If the funding request is for a different chain than the one the
// wallet is aware of, then we'll reject the request.
if !bytes.Equal(l.Cfg.NetParams.GenesisHash[:], req.ChainHash[:]) {
err := ErrChainMismatch(
l.Cfg.NetParams.GenesisHash, req.ChainHash,
)
req.err <- err
req.resp <- nil
return
}
// We need to avoid enforcing reserved value in the middle of PSBT
// funding because some of the following steps may add UTXOs funding
// the on-chain wallet.
// The enforcement still happens at the last step - in PsbtFundingVerify
enforceNewReservedValue := true
// If no chanFunder was provided, then we'll assume the default
// assembler, which is backed by the wallet's internal coin selection.
if req.ChanFunder == nil {
// We use the P2WSH dust limit since it is larger than the
// P2WPKH dust limit and to avoid threading through two
// different dust limits.
cfg := chanfunding.WalletConfig{
CoinSource: NewCoinSource(
l, req.AllowUtxoForFunding,
),
CoinSelectLocker: l,
CoinLeaser: l,
Signer: l.Cfg.Signer,
DustLimit: DustLimitForSize(
input.P2WSHSize,
),
CoinSelectionStrategy: l.Cfg.CoinSelectionStrategy,
}
req.ChanFunder = chanfunding.NewWalletAssembler(cfg)
} else {
_, isPsbtFunder := req.ChanFunder.(*chanfunding.PsbtAssembler)
enforceNewReservedValue = !isPsbtFunder
}
localFundingAmt := req.LocalFundingAmt
remoteFundingAmt := req.RemoteFundingAmt
hasAnchors := req.CommitType.HasAnchors()
var (
fundingIntent chanfunding.Intent
err error
)
// If we've just received an inbound funding request that we have a
// registered shim intent to, then we'll obtain the backing intent now.
// In this case, we're doing a special funding workflow that allows
// more advanced constructions such as channel factories to be
// instantiated.
l.intentMtx.Lock()
fundingIntent, ok := l.fundingIntents[req.PendingChanID]
l.intentMtx.Unlock()
// Otherwise, this is a normal funding flow, so we'll use the chan
// funder in the attached request to provision the inputs/outputs
// that'll ultimately be used to construct the funding transaction.
if !ok {
var err error
var numAnchorChans int
// Get the number of anchor channels to determine if there is a
// reserved value that must be respected when funding up to the
// maximum amount. Since private channels (most likely) won't be
// used for routing other than the last hop, they bear a smaller
// risk that we must force close them in order to resolve a HTLC
// up/downstream. Hence we exclude them from the count of anchor
// channels in order to attribute the respective anchor amount
// to the channel capacity.
if req.FundUpToMaxAmt > 0 && req.MinFundAmt > 0 {
numAnchorChans, err = l.CurrentNumAnchorChans()
if err != nil {
req.err <- err
req.resp <- nil
return
}
isPublic := req.Flags&lnwire.FFAnnounceChannel != 0
if hasAnchors && isPublic {
numAnchorChans++
}
}
// Coin selection is done on the basis of sat/kw, so we'll use
// the fee rate passed in to perform coin selection.
fundingReq := &chanfunding.Request{
RemoteAmt: req.RemoteFundingAmt,
LocalAmt: req.LocalFundingAmt,
FundUpToMaxAmt: req.FundUpToMaxAmt,
MinFundAmt: req.MinFundAmt,
RemoteChanReserve: req.RemoteChanReserve,
PushAmt: lnwire.MilliSatoshi.ToSatoshis(
req.PushMSat,
),
WalletReserve: l.RequiredReserve(
uint32(numAnchorChans),
),
Outpoints: req.Outpoints,
MinConfs: req.MinConfs,
SubtractFees: req.SubtractFees,
FeeRate: req.FundingFeePerKw,
ChangeAddr: func() (btcutil.Address, error) {
return l.NewAddress(
TaprootPubkey, true, DefaultAccountName,
)
},
Musig2: req.CommitType.IsTaproot(),
}
fundingIntent, err = req.ChanFunder.ProvisionChannel(
fundingReq,
)
if err != nil {
req.err <- err
req.resp <- nil
return
}
// Register the funding intent now in case we need to access it
// again later, as it's the case for the PSBT state machine for
// example.
err = l.RegisterFundingIntent(req.PendingChanID, fundingIntent)