@@ -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,33 @@ 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 ,
142
+ deterministicSampling bool ) (NetworkPeerBootstrapper , 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
+ var (
145
+ hashAccumulator hashAccumulator
146
+ err error
147
+ )
148
+ if deterministicSampling {
149
+ // If we're using deterministic sampling, then we'll use a
150
+ // no-op hash accumulator that will always return false for
151
+ // skipNode.
152
+ hashAccumulator = newNoOpHashAccumulator ()
153
+ } else {
154
+ // Otherwise, we'll use a random hash accumulator to sample
155
+ // nodes from the channel graph.
156
+ hashAccumulator , err = newRandomHashAccumulator ()
157
+ if err != nil {
158
+ return nil , fmt .Errorf ("unable to create hash " +
159
+ "accumulator: %w" , err )
160
+ }
150
161
}
151
162
152
- return c , nil
163
+ return & ChannelGraphBootstrapper {
164
+ chanGraph : cg ,
165
+ tried : make (map [autopilot.NodeID ]struct {}),
166
+ hashAccumulator : hashAccumulator ,
167
+ }, nil
153
168
}
154
169
155
170
// SampleNodeAddrs uniformly samples a set of specified address from the
@@ -199,7 +214,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
199
214
// it's 50/50. If it isn't less, than then we'll
200
215
// continue forward.
201
216
nodePubKeyBytes := node .PubKey ()
202
- if bytes . Compare ( c .hashAccumulator [:], nodePubKeyBytes [ 1 :]) > 0 {
217
+ if c .hashAccumulator . skipNode ( nodePubKeyBytes ) {
203
218
return nil
204
219
}
205
220
@@ -259,7 +274,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
259
274
tries ++
260
275
261
276
// We'll now rotate our hash accumulator one value forwards.
262
- c .hashAccumulator = sha256 . Sum256 ( c . hashAccumulator [:] )
277
+ c .hashAccumulator . rotate ( )
263
278
264
279
// If this attempt didn't yield any addresses, then we'll exit
265
280
// early.
@@ -546,3 +561,83 @@ search:
546
561
func (d * DNSSeedBootstrapper ) Name () string {
547
562
return fmt .Sprintf ("BOLT-0010 DNS Seed: %v" , d .dnsSeeds )
548
563
}
564
+
565
+ // hashAccumulator is an interface that defines the methods required for
566
+ // a hash accumulator used to sample nodes from the channel graph.
567
+ type hashAccumulator interface {
568
+ // rotate rotates the hash accumulator value.
569
+ rotate ()
570
+
571
+ // skipNode returns true if the node with the given public key
572
+ // should be skipped based on the current hash accumulator state.
573
+ skipNode (pubKey route.Vertex ) bool
574
+ }
575
+
576
+ // randomHashAccumulator is an implementation of the hashAccumulator
577
+ // interface that uses a random hash to sample nodes from the channel graph.
578
+ type randomHashAccumulator struct {
579
+ hash [32 ]byte
580
+ }
581
+
582
+ // A compile time assertion to ensure that randomHashAccumulator meets the
583
+ // hashAccumulator interface.
584
+ var _ hashAccumulator = (* randomHashAccumulator )(nil )
585
+
586
+ // newRandomHashAccumulator returns a new instance of a randomHashAccumulator.
587
+ // This accumulator is used to randomly sample nodes from the channel graph.
588
+ func newRandomHashAccumulator () (* randomHashAccumulator , error ) {
589
+ var r randomHashAccumulator
590
+
591
+ if _ , err := rand .Read (r .hash [:]); err != nil {
592
+ return nil , fmt .Errorf ("unable to read random bytes: %w" , err )
593
+ }
594
+
595
+ return & r , nil
596
+ }
597
+
598
+ // rotate rotates the hash accumulator by hashing the current value
599
+ // with itself. This ensures that we have a new random value to compare
600
+ // against when we sample nodes from the channel graph.
601
+ //
602
+ // NOTE: this is part of the hashAccumulator interface.
603
+ func (r * randomHashAccumulator ) rotate () {
604
+ r .hash = sha256 .Sum256 (r .hash [:])
605
+ }
606
+
607
+ // skipNode returns true if the node with the given public key should be skipped
608
+ // based on the current hash accumulator state. It will return false for the
609
+ // pub key if it is lexicographically less than our current accumulator value.
610
+ // It does so by comparing the current hash accumulator value with the passed
611
+ // byte slice. When comparing, we skip the first byte as it's 50/50 between 02
612
+ // and 03 for compressed pub keys.
613
+ //
614
+ // NOTE: this is part of the hashAccumulator interface.
615
+ func (r * randomHashAccumulator ) skipNode (pub route.Vertex ) bool {
616
+ return bytes .Compare (r .hash [:], pub [1 :]) > 0
617
+ }
618
+
619
+ // noOpHashAccumulator is a no-op implementation of the hashAccumulator
620
+ // interface. This is used when we want deterministic behavior and don't
621
+ // want to sample nodes randomly from the channel graph.
622
+ type noOpHashAccumulator struct {}
623
+
624
+ // newNoOpHashAccumulator returns a new instance of a noOpHashAccumulator.
625
+ func newNoOpHashAccumulator () * noOpHashAccumulator {
626
+ return & noOpHashAccumulator {}
627
+ }
628
+
629
+ // rotate is a no-op for the noOpHashAccumulator.
630
+ //
631
+ // NOTE: this is part of the hashAccumulator interface.
632
+ func (* noOpHashAccumulator ) rotate () {}
633
+
634
+ // skipNode always returns false, meaning that no nodes will be skipped.
635
+ //
636
+ // NOTE: this is part of the hashAccumulator interface.
637
+ func (* noOpHashAccumulator ) skipNode (route.Vertex ) bool {
638
+ return false
639
+ }
640
+
641
+ // A compile-time assertion to ensure that noOpHashAccumulator meets the
642
+ // hashAccumulator interface.
643
+ var _ hashAccumulator = (* noOpHashAccumulator )(nil )
0 commit comments