-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
brontide.go
4350 lines (3660 loc) · 136 KB
/
brontide.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 peer
import (
"bytes"
"container/list"
"errors"
"fmt"
"math/rand"
"net"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btclog"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/buffer"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnutils"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/pool"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/subscribe"
"github.com/lightningnetwork/lnd/ticker"
"github.com/lightningnetwork/lnd/tlv"
"github.com/lightningnetwork/lnd/watchtower/wtclient"
)
const (
// pingInterval is the interval at which ping messages are sent.
pingInterval = 1 * time.Minute
// pingTimeout is the amount of time we will wait for a pong response
// before considering the peer to be unresponsive.
//
// This MUST be a smaller value than the pingInterval.
pingTimeout = 30 * time.Second
// idleTimeout is the duration of inactivity before we time out a peer.
idleTimeout = 5 * time.Minute
// writeMessageTimeout is the timeout used when writing a message to the
// peer.
writeMessageTimeout = 5 * time.Second
// readMessageTimeout is the timeout used when reading a message from a
// peer.
readMessageTimeout = 5 * time.Second
// handshakeTimeout is the timeout used when waiting for the peer's init
// message.
handshakeTimeout = 15 * time.Second
// ErrorBufferSize is the number of historic peer errors that we store.
ErrorBufferSize = 10
// pongSizeCeiling is the upper bound on a uniformly distributed random
// variable that we use for requesting pong responses. We don't use the
// MaxPongBytes (upper bound accepted by the protocol) because it is
// needlessly wasteful of precious Tor bandwidth for little to no gain.
pongSizeCeiling = 4096
// torTimeoutMultiplier is the scaling factor we use on network timeouts
// for Tor peers.
torTimeoutMultiplier = 3
)
var (
// ErrChannelNotFound is an error returned when a channel is queried and
// either the Brontide doesn't know of it, or the channel in question
// is pending.
ErrChannelNotFound = fmt.Errorf("channel not found")
)
// outgoingMsg packages an lnwire.Message to be sent out on the wire, along with
// a buffered channel which will be sent upon once the write is complete. This
// buffered channel acts as a semaphore to be used for synchronization purposes.
type outgoingMsg struct {
priority bool
msg lnwire.Message
errChan chan error // MUST be buffered.
}
// newChannelMsg packages a channeldb.OpenChannel with a channel that allows
// the receiver of the request to report when the channel creation process has
// completed.
type newChannelMsg struct {
// channel is used when the pending channel becomes active.
channel *lnpeer.NewChannel
// channelID is used when there's a new pending channel.
channelID lnwire.ChannelID
err chan error
}
type customMsg struct {
peer [33]byte
msg lnwire.Custom
}
// closeMsg is a wrapper struct around any wire messages that deal with the
// cooperative channel closure negotiation process. This struct includes the
// raw channel ID targeted along with the original message.
type closeMsg struct {
cid lnwire.ChannelID
msg lnwire.Message
}
// PendingUpdate describes the pending state of a closing channel.
type PendingUpdate struct {
Txid []byte
OutputIndex uint32
}
// ChannelCloseUpdate contains the outcome of the close channel operation.
type ChannelCloseUpdate struct {
ClosingTxid []byte
Success bool
}
// TimestampedError is a timestamped error that is used to store the most recent
// errors we have experienced with our peers.
type TimestampedError struct {
Error error
Timestamp time.Time
}
// Config defines configuration fields that are necessary for a peer object
// to function.
type Config struct {
// Conn is the underlying network connection for this peer.
Conn MessageConn
// ConnReq stores information related to the persistent connection request
// for this peer.
ConnReq *connmgr.ConnReq
// PubKeyBytes is the serialized, compressed public key of this peer.
PubKeyBytes [33]byte
// Addr is the network address of the peer.
Addr *lnwire.NetAddress
// Inbound indicates whether or not the peer is an inbound peer.
Inbound bool
// Features is the set of features that we advertise to the remote party.
Features *lnwire.FeatureVector
// LegacyFeatures is the set of features that we advertise to the remote
// peer for backwards compatibility. Nodes that have not implemented
// flat features will still be able to read our feature bits from the
// legacy global field, but we will also advertise everything in the
// default features field.
LegacyFeatures *lnwire.FeatureVector
// OutgoingCltvRejectDelta defines the number of blocks before expiry of
// an htlc where we don't offer it anymore.
OutgoingCltvRejectDelta uint32
// ChanActiveTimeout specifies the duration the peer will wait to request
// a channel reenable, beginning from the time the peer was started.
ChanActiveTimeout time.Duration
// ErrorBuffer stores a set of errors related to a peer. It contains error
// messages that our peer has recently sent us over the wire and records of
// unknown messages that were sent to us so that we can have a full track
// record of the communication errors we have had with our peer. If we
// choose to disconnect from a peer, it also stores the reason we had for
// disconnecting.
ErrorBuffer *queue.CircularBuffer
// WritePool is the task pool that manages reuse of write buffers. Write
// tasks are submitted to the pool in order to conserve the total number of
// write buffers allocated at any one time, and decouple write buffer
// allocation from the peer life cycle.
WritePool *pool.Write
// ReadPool is the task pool that manages reuse of read buffers.
ReadPool *pool.Read
// Switch is a pointer to the htlcswitch. It is used to setup, get, and
// tear-down ChannelLinks.
Switch messageSwitch
// InterceptSwitch is a pointer to the InterceptableSwitch, a wrapper around
// the regular Switch. We only export it here to pass ForwardPackets to the
// ChannelLinkConfig.
InterceptSwitch *htlcswitch.InterceptableSwitch
// ChannelDB is used to fetch opened channels, and closed channels.
ChannelDB *channeldb.ChannelStateDB
// ChannelGraph is a pointer to the channel graph which is used to
// query information about the set of known active channels.
ChannelGraph *channeldb.ChannelGraph
// ChainArb is used to subscribe to channel events, update contract signals,
// and force close channels.
ChainArb *contractcourt.ChainArbitrator
// AuthGossiper is needed so that the Brontide impl can register with the
// gossiper and process remote channel announcements.
AuthGossiper *discovery.AuthenticatedGossiper
// ChanStatusMgr is used to set or un-set the disabled bit in channel
// updates.
ChanStatusMgr *netann.ChanStatusManager
// ChainIO is used to retrieve the best block.
ChainIO lnwallet.BlockChainIO
// FeeEstimator is used to compute our target ideal fee-per-kw when
// initializing the coop close process.
FeeEstimator chainfee.Estimator
// Signer is used when creating *lnwallet.LightningChannel instances.
Signer input.Signer
// SigPool is used when creating *lnwallet.LightningChannel instances.
SigPool *lnwallet.SigPool
// Wallet is used to publish transactions and generates delivery
// scripts during the coop close process.
Wallet *lnwallet.LightningWallet
// ChainNotifier is used to receive confirmations of a coop close
// transaction.
ChainNotifier chainntnfs.ChainNotifier
// BestBlockView is used to efficiently query for up-to-date
// blockchain state information
BestBlockView chainntnfs.BestBlockView
// RoutingPolicy is used to set the forwarding policy for links created by
// the Brontide.
RoutingPolicy models.ForwardingPolicy
// Sphinx is used when setting up ChannelLinks so they can decode sphinx
// onion blobs.
Sphinx *hop.OnionProcessor
// WitnessBeacon is used when setting up ChannelLinks so they can add any
// preimages that they learn.
WitnessBeacon contractcourt.WitnessBeacon
// Invoices is passed to the ChannelLink on creation and handles all
// invoice-related logic.
Invoices *invoices.InvoiceRegistry
// ChannelNotifier is used by the link to notify other sub-systems about
// channel-related events and by the Brontide to subscribe to
// ActiveLinkEvents.
ChannelNotifier *channelnotifier.ChannelNotifier
// HtlcNotifier is used when creating a ChannelLink.
HtlcNotifier *htlcswitch.HtlcNotifier
// TowerClient is used to backup revoked states.
TowerClient wtclient.ClientManager
// DisconnectPeer is used to disconnect this peer if the cooperative close
// process fails.
DisconnectPeer func(*btcec.PublicKey) error
// GenNodeAnnouncement is used to send our node announcement to the remote
// on startup.
GenNodeAnnouncement func(...netann.NodeAnnModifier) (
lnwire.NodeAnnouncement, error)
// PrunePersistentPeerConnection is used to remove all internal state
// related to this peer in the server.
PrunePersistentPeerConnection func([33]byte)
// FetchLastChanUpdate fetches our latest channel update for a target
// channel.
FetchLastChanUpdate func(lnwire.ShortChannelID) (*lnwire.ChannelUpdate,
error)
// FundingManager is an implementation of the funding.Controller interface.
FundingManager funding.Controller
// Hodl is used when creating ChannelLinks to specify HodlFlags as
// breakpoints in dev builds.
Hodl *hodl.Config
// UnsafeReplay is used when creating ChannelLinks to specify whether or
// not to replay adds on its commitment tx.
UnsafeReplay bool
// MaxOutgoingCltvExpiry is used when creating ChannelLinks and is the max
// number of blocks that funds could be locked up for when forwarding
// payments.
MaxOutgoingCltvExpiry uint32
// MaxChannelFeeAllocation is used when creating ChannelLinks and is the
// maximum percentage of total funds that can be allocated to a channel's
// commitment fee. This only applies for the initiator of the channel.
MaxChannelFeeAllocation float64
// MaxAnchorsCommitFeeRate is the maximum fee rate we'll use as an
// initiator for anchor channel commitments.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// CoopCloseTargetConfs is the confirmation target that will be used
// to estimate the fee rate to use during a cooperative channel
// closure initiated by the remote peer.
CoopCloseTargetConfs uint32
// ServerPubKey is the serialized, compressed public key of our lnd node.
// It is used to determine which policy (channel edge) to pass to the
// ChannelLink.
ServerPubKey [33]byte
// ChannelCommitInterval is the maximum time that is allowed to pass between
// receiving a channel state update and signing the next commitment.
// Setting this to a longer duration allows for more efficient channel
// operations at the cost of latency.
ChannelCommitInterval time.Duration
// PendingCommitInterval is the maximum time that is allowed to pass
// while waiting for the remote party to revoke a locally initiated
// commitment state. Setting this to a longer duration if a slow
// response is expected from the remote party or large number of
// payments are attempted at the same time.
PendingCommitInterval time.Duration
// ChannelCommitBatchSize is the maximum number of channel state updates
// that is accumulated before signing a new commitment.
ChannelCommitBatchSize uint32
// HandleCustomMessage is called whenever a custom message is received
// from the peer.
HandleCustomMessage func(peer [33]byte, msg *lnwire.Custom) error
// GetAliases is passed to created links so the Switch and link can be
// aware of the channel's aliases.
GetAliases func(base lnwire.ShortChannelID) []lnwire.ShortChannelID
// RequestAlias allows the Brontide struct to request an alias to send
// to the peer.
RequestAlias func() (lnwire.ShortChannelID, error)
// AddLocalAlias persists an alias to an underlying alias store.
AddLocalAlias func(alias, base lnwire.ShortChannelID,
gossip, liveUpdate bool) error
// AuxLeafStore is an optional store that can be used to store auxiliary
// leaves for certain custom channel types.
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
// PongBuf is a slice we'll reuse instead of allocating memory on the
// heap. Since only reads will occur and no writes, there is no need
// for any synchronization primitives. As a result, it's safe to share
// this across multiple Peer struct instances.
PongBuf []byte
// Adds the option to disable forwarding payments in blinded routes
// by failing back any blinding-related payloads as if they were
// invalid.
DisallowRouteBlinding bool
// MaxFeeExposure limits the number of outstanding fees in a channel.
// This value will be passed to created links.
MaxFeeExposure lnwire.MilliSatoshi
// MsgRouter is an optional instance of the main message router that
// the peer will use. If None, then a new default version will be used
// in place.
MsgRouter fn.Option[msgmux.Router]
// Quit is the server's quit channel. If this is closed, we halt operation.
Quit chan struct{}
}
// Brontide is an active peer on the Lightning Network. This struct is responsible
// for managing any channel state related to this peer. To do so, it has
// several helper goroutines to handle events such as HTLC timeouts, new
// funding workflow, and detecting an uncooperative closure of any active
// channels.
// TODO(roasbeef): proper reconnection logic.
type Brontide struct {
// MUST be used atomically.
started int32
disconnect int32
// MUST be used atomically.
bytesReceived uint64
bytesSent uint64
// isTorConnection is a flag that indicates whether or not we believe
// the remote peer is a tor connection. It is not always possible to
// know this with certainty but we have heuristics we use that should
// catch most cases.
//
// NOTE: We judge the tor-ness of a connection by if the remote peer has
// ".onion" in the address OR if it's connected over localhost.
// This will miss cases where our peer is connected to our clearnet
// address over the tor network (via exit nodes). It will also misjudge
// actual localhost connections as tor. We need to include this because
// inbound connections to our tor address will appear to come from the
// local socks5 proxy. This heuristic is only used to expand the timeout
// window for peers so it is OK to misjudge this. If you use this field
// for any other purpose you should seriously consider whether or not
// this heuristic is good enough for your use case.
isTorConnection bool
pingManager *PingManager
// lastPingPayload stores an unsafe pointer wrapped as an atomic
// variable which points to the last payload the remote party sent us
// as their ping.
//
// MUST be used atomically.
lastPingPayload atomic.Value
cfg Config
// activeSignal when closed signals that the peer is now active and
// ready to process messages.
activeSignal chan struct{}
// startTime is the time this peer connection was successfully established.
// It will be zero for peers that did not successfully call Start().
startTime time.Time
// sendQueue is the channel which is used to queue outgoing messages to be
// written onto the wire. Note that this channel is unbuffered.
sendQueue chan outgoingMsg
// outgoingQueue is a buffered channel which allows second/third party
// objects to queue messages to be sent out on the wire.
outgoingQueue chan outgoingMsg
// activeChannels is a map which stores the state machines of all
// active channels. Channels are indexed into the map by the txid of
// the funding transaction which opened the channel.
//
// NOTE: On startup, pending channels are stored as nil in this map.
// Confirmed channels have channel data populated in the map. This means
// that accesses to this map should nil-check the LightningChannel to
// see if this is a pending channel or not. The tradeoff here is either
// having two maps everywhere (one for pending, one for confirmed chans)
// or having an extra nil-check per access.
activeChannels *lnutils.SyncMap[
lnwire.ChannelID, *lnwallet.LightningChannel]
// addedChannels tracks any new channels opened during this peer's
// lifecycle. We use this to filter out these new channels when the time
// comes to request a reenable for active channels, since they will have
// waited a shorter duration.
addedChannels *lnutils.SyncMap[lnwire.ChannelID, struct{}]
// newActiveChannel is used by the fundingManager to send fully opened
// channels to the source peer which handled the funding workflow.
newActiveChannel chan *newChannelMsg
// newPendingChannel is used by the fundingManager to send pending open
// channels to the source peer which handled the funding workflow.
newPendingChannel chan *newChannelMsg
// removePendingChannel is used by the fundingManager to cancel pending
// open channels to the source peer when the funding flow is failed.
removePendingChannel chan *newChannelMsg
// activeMsgStreams is a map from channel id to the channel streams that
// proxy messages to individual, active links.
activeMsgStreams map[lnwire.ChannelID]*msgStream
// activeChanCloses is a map that keeps track of all the active
// cooperative channel closures. Any channel closing messages are directed
// to one of these active state machines. Once the channel has been closed,
// the state machine will be deleted from the map.
activeChanCloses map[lnwire.ChannelID]*chancloser.ChanCloser
// localCloseChanReqs is a channel in which any local requests to close
// a particular channel are sent over.
localCloseChanReqs chan *htlcswitch.ChanClose
// linkFailures receives all reported channel failures from the switch,
// and instructs the channelManager to clean remaining channel state.
linkFailures chan linkFailureReport
// chanCloseMsgs is a channel that any message related to channel
// closures are sent over. This includes lnwire.Shutdown message as
// well as lnwire.ClosingSigned messages.
chanCloseMsgs chan *closeMsg
// remoteFeatures is the feature vector received from the peer during
// the connection handshake.
remoteFeatures *lnwire.FeatureVector
// resentChanSyncMsg is a set that keeps track of which channels we
// have re-sent channel reestablishment messages for. This is done to
// avoid getting into loop where both peers will respond to the other
// peer's chansync message with its own over and over again.
resentChanSyncMsg map[lnwire.ChannelID]struct{}
// channelEventClient is the channel event subscription client that's
// used to assist retry enabling the channels. This client is only
// created when the reenableTimeout is no greater than 1 minute. Once
// created, it is canceled once the reenabling has been finished.
//
// NOTE: we choose to create the client conditionally to avoid
// potentially holding lots of un-consumed events.
channelEventClient *subscribe.Client
// msgRouter is an instance of the msgmux.Router which is used to send
// off new wire messages for handing.
msgRouter fn.Option[msgmux.Router]
// globalMsgRouter is a flag that indicates whether we have a global
// msg router. If so, then we don't worry about stopping the msg router
// when a peer disconnects.
globalMsgRouter bool
startReady chan struct{}
quit chan struct{}
wg sync.WaitGroup
// log is a peer-specific logging instance.
log btclog.Logger
}
// A compile-time check to ensure that Brontide satisfies the lnpeer.Peer interface.
var _ lnpeer.Peer = (*Brontide)(nil)
// NewBrontide creates a new Brontide from a peer.Config struct.
func NewBrontide(cfg Config) *Brontide {
logPrefix := fmt.Sprintf("Peer(%x):", cfg.PubKeyBytes)
// We have a global message router if one was passed in via the config.
// In this case, we don't need to attempt to tear it down when the peer
// is stopped.
globalMsgRouter := cfg.MsgRouter.IsSome()
// We'll either use the msg router instance passed in, or create a new
// blank instance.
msgRouter := cfg.MsgRouter.Alt(fn.Some[msgmux.Router](
msgmux.NewMultiMsgRouter(),
))
p := &Brontide{
cfg: cfg,
activeSignal: make(chan struct{}),
sendQueue: make(chan outgoingMsg),
outgoingQueue: make(chan outgoingMsg),
addedChannels: &lnutils.SyncMap[lnwire.ChannelID, struct{}]{},
activeChannels: &lnutils.SyncMap[
lnwire.ChannelID, *lnwallet.LightningChannel,
]{},
newActiveChannel: make(chan *newChannelMsg, 1),
newPendingChannel: make(chan *newChannelMsg, 1),
removePendingChannel: make(chan *newChannelMsg),
activeMsgStreams: make(map[lnwire.ChannelID]*msgStream),
activeChanCloses: make(map[lnwire.ChannelID]*chancloser.ChanCloser),
localCloseChanReqs: make(chan *htlcswitch.ChanClose),
linkFailures: make(chan linkFailureReport),
chanCloseMsgs: make(chan *closeMsg),
resentChanSyncMsg: make(map[lnwire.ChannelID]struct{}),
startReady: make(chan struct{}),
quit: make(chan struct{}),
log: build.NewPrefixLog(logPrefix, peerLog),
msgRouter: msgRouter,
globalMsgRouter: globalMsgRouter,
}
if cfg.Conn != nil && cfg.Conn.RemoteAddr() != nil {
remoteAddr := cfg.Conn.RemoteAddr().String()
p.isTorConnection = strings.Contains(remoteAddr, ".onion") ||
strings.Contains(remoteAddr, "127.0.0.1")
}
var (
lastBlockHeader *wire.BlockHeader
lastSerializedBlockHeader [wire.MaxBlockHeaderPayload]byte
)
newPingPayload := func() []byte {
// We query the BestBlockHeader from our BestBlockView each time
// this is called, and update our serialized block header if
// they differ. Over time, we'll use this to disseminate the
// latest block header between all our peers, which can later be
// used to cross-check our own view of the network to mitigate
// various types of eclipse attacks.
header, err := p.cfg.BestBlockView.BestBlockHeader()
if err != nil && header == lastBlockHeader {
return lastSerializedBlockHeader[:]
}
buf := bytes.NewBuffer(lastSerializedBlockHeader[0:0])
err = header.Serialize(buf)
if err == nil {
lastBlockHeader = header
} else {
p.log.Warn("unable to serialize current block" +
"header for ping payload generation." +
"This should be impossible and means" +
"there is an implementation bug.")
}
return lastSerializedBlockHeader[:]
}
// TODO(roasbeef): make dynamic in order to create fake cover traffic.
//
// NOTE(proofofkeags): this was changed to be dynamic to allow better
// pong identification, however, more thought is needed to make this
// actually usable as a traffic decoy.
randPongSize := func() uint16 {
return uint16(
// We don't need cryptographic randomness here.
/* #nosec */
rand.Intn(pongSizeCeiling) + 1,
)
}
p.pingManager = NewPingManager(&PingManagerConfig{
NewPingPayload: newPingPayload,
NewPongSize: randPongSize,
IntervalDuration: p.scaleTimeout(pingInterval),
TimeoutDuration: p.scaleTimeout(pingTimeout),
SendPing: func(ping *lnwire.Ping) {
p.queueMsg(ping, nil)
},
OnPongFailure: func(err error) {
eStr := "pong response failure for %s: %v " +
"-- disconnecting"
p.log.Warnf(eStr, p, err)
go p.Disconnect(fmt.Errorf(eStr, p, err))
},
})
return p
}
// Start starts all helper goroutines the peer needs for normal operations. In
// the case this peer has already been started, then this function is a noop.
func (p *Brontide) Start() error {
if atomic.AddInt32(&p.started, 1) != 1 {
return nil
}
// Once we've finished starting up the peer, we'll signal to other
// goroutines that the they can move forward to tear down the peer, or
// carry out other relevant changes.
defer close(p.startReady)
p.log.Tracef("starting with conn[%v->%v]",
p.cfg.Conn.LocalAddr(), p.cfg.Conn.RemoteAddr())
// Fetch and then load all the active channels we have with this remote
// peer from the database.
activeChans, err := p.cfg.ChannelDB.FetchOpenChannels(
p.cfg.Addr.IdentityKey,
)
if err != nil {
p.log.Errorf("Unable to fetch active chans "+
"for peer: %v", err)
return err
}
if len(activeChans) == 0 {
go p.cfg.PrunePersistentPeerConnection(p.cfg.PubKeyBytes)
}
// Quickly check if we have any existing legacy channels with this
// peer.
haveLegacyChan := false
for _, c := range activeChans {
if c.ChanType.IsTweakless() {
continue
}
haveLegacyChan = true
break
}
// Exchange local and global features, the init message should be very
// first between two nodes.
if err := p.sendInitMsg(haveLegacyChan); err != nil {
return fmt.Errorf("unable to send init msg: %w", err)
}
// Before we launch any of the helper goroutines off the peer struct,
// we'll first ensure proper adherence to the p2p protocol. The init
// message MUST be sent before any other message.
readErr := make(chan error, 1)
msgChan := make(chan lnwire.Message, 1)
p.wg.Add(1)
go func() {
defer p.wg.Done()
msg, err := p.readNextMessage()
if err != nil {
readErr <- err
msgChan <- nil
return
}
readErr <- nil
msgChan <- msg
}()
select {
// In order to avoid blocking indefinitely, we'll give the other peer
// an upper timeout to respond before we bail out early.
case <-time.After(handshakeTimeout):
return fmt.Errorf("peer did not complete handshake within %v",
handshakeTimeout)
case err := <-readErr:
if err != nil {
return fmt.Errorf("unable to read init msg: %w", err)
}
}
// Once the init message arrives, we can parse it so we can figure out
// the negotiation of features for this session.
msg := <-msgChan
if msg, ok := msg.(*lnwire.Init); ok {
if err := p.handleInitMsg(msg); err != nil {
p.storeError(err)
return err
}
} else {
return errors.New("very first message between nodes " +
"must be init message")
}
// Next, load all the active channels we have with this peer,
// registering them with the switch and launching the necessary
// goroutines required to operate them.
p.log.Debugf("Loaded %v active channels from database",
len(activeChans))
// Conditionally subscribe to channel events before loading channels so
// we won't miss events. This subscription is used to listen to active
// channel event when reenabling channels. Once the reenabling process
// is finished, this subscription will be canceled.
//
// NOTE: ChannelNotifier must be started before subscribing events
// otherwise we'd panic here.
if err := p.attachChannelEventSubscription(); err != nil {
return err
}
// Register the message router now as we may need to register some
// endpoints while loading the channels below.
p.msgRouter.WhenSome(func(router msgmux.Router) {
router.Start()
})
msgs, err := p.loadActiveChannels(activeChans)
if err != nil {
return fmt.Errorf("unable to load channels: %w", err)
}
p.startTime = time.Now()
// Before launching the writeHandler goroutine, we send any channel
// sync messages that must be resent for borked channels. We do this to
// avoid data races with WriteMessage & Flush calls.
if len(msgs) > 0 {
p.log.Infof("Sending %d channel sync messages to peer after "+
"loading active channels", len(msgs))
// Send the messages directly via writeMessage and bypass the
// writeHandler goroutine.
for _, msg := range msgs {
if err := p.writeMessage(msg); err != nil {
return fmt.Errorf("unable to send "+
"reestablish msg: %v", err)
}
}
}
err = p.pingManager.Start()
if err != nil {
return fmt.Errorf("could not start ping manager %w", err)
}
p.wg.Add(4)
go p.queueHandler()
go p.writeHandler()
go p.channelManager()
go p.readHandler()
// Signal to any external processes that the peer is now active.
close(p.activeSignal)
// Node announcements don't propagate very well throughout the network
// as there isn't a way to efficiently query for them through their
// timestamp, mostly affecting nodes that were offline during the time
// of broadcast. We'll resend our node announcement to the remote peer
// as a best-effort delivery such that it can also propagate to their
// peers. To ensure they can successfully process it in most cases,
// we'll only resend it as long as we have at least one confirmed
// advertised channel with the remote peer.
//
// TODO(wilmer): Remove this once we're able to query for node
// announcements through their timestamps.
p.wg.Add(2)
go p.maybeSendNodeAnn(activeChans)
go p.maybeSendChannelUpdates()
return nil
}
// initGossipSync initializes either a gossip syncer or an initial routing
// dump, depending on the negotiated synchronization method.
func (p *Brontide) initGossipSync() {
// If the remote peer knows of the new gossip queries feature, then
// we'll create a new gossipSyncer in the AuthenticatedGossiper for it.
if p.remoteFeatures.HasFeature(lnwire.GossipQueriesOptional) {
p.log.Info("Negotiated chan series queries")
if p.cfg.AuthGossiper == nil {
// This should only ever be hit in the unit tests.
p.log.Warn("No AuthGossiper configured. Abandoning " +
"gossip sync.")
return
}
// Register the peer's gossip syncer with the gossiper.
// This blocks synchronously to ensure the gossip syncer is
// registered with the gossiper before attempting to read
// messages from the remote peer.
//
// TODO(wilmer): Only sync updates from non-channel peers. This
// requires an improved version of the current network
// bootstrapper to ensure we can find and connect to non-channel
// peers.
p.cfg.AuthGossiper.InitSyncState(p)
}
}
// taprootShutdownAllowed returns true if both parties have negotiated the
// shutdown-any-segwit feature.
func (p *Brontide) taprootShutdownAllowed() bool {
return p.RemoteFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional) &&
p.LocalFeatures().HasFeature(lnwire.ShutdownAnySegwitOptional)
}
// QuitSignal is a method that should return a channel which will be sent upon
// or closed once the backing peer exits. This allows callers using the
// interface to cancel any processing in the event the backing implementation
// exits.
//
// NOTE: Part of the lnpeer.Peer interface.
func (p *Brontide) QuitSignal() <-chan struct{} {
return p.quit
}
// loadActiveChannels creates indexes within the peer for tracking all active
// channels returned by the database. It returns a slice of channel reestablish
// messages that should be sent to the peer immediately, in case we have borked
// channels that haven't been closed yet.
func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
[]lnwire.Message, error) {
// Return a slice of messages to send to the peers in case the channel
// cannot be loaded normally.
var msgs []lnwire.Message
scidAliasNegotiated := p.hasNegotiatedScidAlias()
for _, dbChan := range chans {
hasScidFeature := dbChan.ChanType.HasScidAliasFeature()
if scidAliasNegotiated && !hasScidFeature {
// We'll request and store an alias, making sure that a
// gossiper mapping is not created for the alias to the
// real SCID. This is done because the peer and funding
// manager are not aware of each other's states and if
// we did not do this, we would accept alias channel
// updates after 6 confirmations, which would be buggy.
// We'll queue a channel_ready message with the new
// alias. This should technically be done *after* the
// reestablish, but this behavior is pre-existing since
// the funding manager may already queue a
// channel_ready before the channel_reestablish.
if !dbChan.IsPending {
aliasScid, err := p.cfg.RequestAlias()
if err != nil {
return nil, err
}
err = p.cfg.AddLocalAlias(
aliasScid, dbChan.ShortChanID(), false,
false,
)
if err != nil {
return nil, err
}
chanID := lnwire.NewChanIDFromOutPoint(
dbChan.FundingOutpoint,
)
// Fetch the second commitment point to send in
// the channel_ready message.
second, err := dbChan.SecondCommitmentPoint()
if err != nil {
return nil, err
}
channelReadyMsg := lnwire.NewChannelReady(
chanID, second,
)
channelReadyMsg.AliasScid = &aliasScid
msgs = append(msgs, channelReadyMsg)
}
// If we've negotiated the option-scid-alias feature
// and this channel does not have ScidAliasFeature set
// to true due to an upgrade where the feature bit was
// turned on, we'll update the channel's database
// state.
err := dbChan.MarkScidAliasNegotiated()
if err != nil {
return nil, err
}
}
var chanOpts []lnwallet.ChannelOpt
p.cfg.AuxLeafStore.WhenSome(func(s lnwallet.AuxLeafStore) {
chanOpts = append(chanOpts, lnwallet.WithLeafStore(s))
})
lnChan, err := lnwallet.NewLightningChannel(
p.cfg.Signer, dbChan, p.cfg.SigPool, chanOpts...,
)
if err != nil {
return nil, fmt.Errorf("unable to create channel "+
"state machine: %w", err)
}
chanPoint := dbChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(chanPoint)
p.log.Infof("Loading ChannelPoint(%v), isPending=%v",
chanPoint, lnChan.IsPending())
// Skip adding any permanently irreconcilable channels to the
// htlcswitch.
if !dbChan.HasChanStatus(channeldb.ChanStatusDefault) &&
!dbChan.HasChanStatus(channeldb.ChanStatusRestored) {
p.log.Warnf("ChannelPoint(%v) has status %v, won't "+
"start.", chanPoint, dbChan.ChanStatus())
// To help our peer recover from a potential data loss,
// we resend our channel reestablish message if the
// channel is in a borked state. We won't process any
// channel reestablish message sent from the peer, but
// that's okay since the assumption is that we did when
// marking the channel borked.
chanSync, err := dbChan.ChanSyncMsg()
if err != nil {
p.log.Errorf("Unable to create channel "+
"reestablish message for channel %v: "+
"%v", chanPoint, err)
continue
}
msgs = append(msgs, chanSync)
// Check if this channel needs to have the cooperative
// close process restarted. If so, we'll need to send
// the Shutdown message that is returned.
if dbChan.HasChanStatus(
channeldb.ChanStatusCoopBroadcasted,
) {