@@ -18,6 +18,7 @@ import (
18
18
"github.com/lightningnetwork/lnd/autopilot"
19
19
"github.com/lightningnetwork/lnd/lnutils"
20
20
"github.com/lightningnetwork/lnd/lnwire"
21
+ "github.com/lightningnetwork/lnd/routing/route"
21
22
"github.com/lightningnetwork/lnd/tor"
22
23
"github.com/miekg/dns"
23
24
)
@@ -121,11 +122,10 @@ func shuffleBootstrappers(candidates []NetworkPeerBootstrapper) []NetworkPeerBoo
121
122
type ChannelGraphBootstrapper struct {
122
123
chanGraph autopilot.ChannelGraph
123
124
124
- // hashAccumulator is a set of 32 random bytes that are read upon the
125
- // creation of the channel graph bootstrapper. We use this value to
126
- // randomly select nodes within the known graph to connect to. After
127
- // each selection, we rotate the accumulator by hashing it with itself.
128
- hashAccumulator [32 ]byte
125
+ // hashAccumulator is used to determine which nodes to use for
126
+ // bootstrapping. It allows us to potentially introduce some randomness
127
+ // into the selection process.
128
+ hashAccumulator hashAccumulator
129
129
130
130
tried map [autopilot.NodeID ]struct {}
131
131
}
@@ -138,18 +138,20 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
138
138
// backed by an active autopilot.ChannelGraph instance. This type of network
139
139
// peer bootstrapper will use the authenticated nodes within the known channel
140
140
// graph to bootstrap connections.
141
- func NewGraphBootstrapper (cg autopilot.ChannelGraph ) (NetworkPeerBootstrapper , error ) {
141
+ func NewGraphBootstrapper (cg autopilot.ChannelGraph ) (NetworkPeerBootstrapper ,
142
+ error ) {
142
143
143
- c := & ChannelGraphBootstrapper {
144
- chanGraph : cg ,
145
- tried : make (map [autopilot.NodeID ]struct {}),
146
- }
147
-
148
- if _ , err := rand .Read (c .hashAccumulator [:]); err != nil {
149
- return nil , err
144
+ hashAccumulator , err := newRandomHashAccumulator ()
145
+ if err != nil {
146
+ return nil , fmt .Errorf ("unable to create hash accumulator: %w" ,
147
+ err )
150
148
}
151
149
152
- return c , nil
150
+ return & ChannelGraphBootstrapper {
151
+ chanGraph : cg ,
152
+ tried : make (map [autopilot.NodeID ]struct {}),
153
+ hashAccumulator : hashAccumulator ,
154
+ }, nil
153
155
}
154
156
155
157
// SampleNodeAddrs uniformly samples a set of specified address from the
@@ -199,7 +201,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
199
201
// it's 50/50. If it isn't less, than then we'll
200
202
// continue forward.
201
203
nodePubKeyBytes := node .PubKey ()
202
- if bytes . Compare ( c .hashAccumulator [:], nodePubKeyBytes [ 1 :]) > 0 {
204
+ if c .hashAccumulator . skipNode ( nodePubKeyBytes ) {
203
205
return nil
204
206
}
205
207
@@ -259,7 +261,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
259
261
tries ++
260
262
261
263
// We'll now rotate our hash accumulator one value forwards.
262
- c .hashAccumulator = sha256 . Sum256 ( c . hashAccumulator [:] )
264
+ c .hashAccumulator . rotate ( )
263
265
264
266
// If this attempt didn't yield any addresses, then we'll exit
265
267
// early.
@@ -546,3 +548,57 @@ search:
546
548
func (d * DNSSeedBootstrapper ) Name () string {
547
549
return fmt .Sprintf ("BOLT-0010 DNS Seed: %v" , d .dnsSeeds )
548
550
}
551
+
552
+ // hashAccumulator is an interface that defines the methods required for
553
+ // a hash accumulator used to sample nodes from the channel graph.
554
+ type hashAccumulator interface {
555
+ // rotate rotates the hash accumulator value.
556
+ rotate ()
557
+
558
+ // skipNode returns true if the node with the given public key
559
+ // should be skipped based on the current hash accumulator state.
560
+ skipNode (pubKey route.Vertex ) bool
561
+ }
562
+
563
+ // randomHashAccumulator is an implementation of the hashAccumulator
564
+ // interface that uses a random hash to sample nodes from the channel graph.
565
+ type randomHashAccumulator struct {
566
+ hash [32 ]byte
567
+ }
568
+
569
+ // A compile time assertion to ensure that randomHashAccumulator meets the
570
+ // hashAccumulator interface.
571
+ var _ hashAccumulator = (* randomHashAccumulator )(nil )
572
+
573
+ // newRandomHashAccumulator returns a new instance of a randomHashAccumulator.
574
+ // This accumulator is used to randomly sample nodes from the channel graph.
575
+ func newRandomHashAccumulator () (* randomHashAccumulator , error ) {
576
+ var r randomHashAccumulator
577
+
578
+ if _ , err := rand .Read (r .hash [:]); err != nil {
579
+ return nil , fmt .Errorf ("unable to read random bytes: %w" , err )
580
+ }
581
+
582
+ return & r , nil
583
+ }
584
+
585
+ // rotate rotates the hash accumulator by hashing the current value
586
+ // with itself. This ensures that we have a new random value to compare
587
+ // against when we sample nodes from the channel graph.
588
+ //
589
+ // NOTE: this is part of the hashAccumulator interface.
590
+ func (r * randomHashAccumulator ) rotate () {
591
+ r .hash = sha256 .Sum256 (r .hash [:])
592
+ }
593
+
594
+ // skipNode returns true if the node with the given public key should be skipped
595
+ // based on the current hash accumulator state. It will return false for the
596
+ // pub key if it is lexicographically less than our current accumulator value.
597
+ // It does so by comparing the current hash accumulator value with the passed
598
+ // byte slice. When comparing, we skip the first byte as it's 50/50 between 02
599
+ // and 03 for compressed pub keys.
600
+ //
601
+ // NOTE: this is part of the hashAccumulator interface.
602
+ func (r * randomHashAccumulator ) skipNode (pub route.Vertex ) bool {
603
+ return bytes .Compare (r .hash [:], pub [1 :]) > 0
604
+ }
0 commit comments