Skip to content

Commit 339dd0c

Browse files
committed
discovery: introduce hashAccumulator interface
Create an abstract hashAccumulator interface for the Channel graph bootstrapper so that we can later introduce a deterministic accumulator to be used during testing.
1 parent bca6c0d commit 339dd0c

File tree

1 file changed

+72
-16
lines changed

1 file changed

+72
-16
lines changed

discovery/bootstrapper.go

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/lightningnetwork/lnd/autopilot"
1919
"github.com/lightningnetwork/lnd/lnutils"
2020
"github.com/lightningnetwork/lnd/lnwire"
21+
"github.com/lightningnetwork/lnd/routing/route"
2122
"github.com/lightningnetwork/lnd/tor"
2223
"github.com/miekg/dns"
2324
)
@@ -121,11 +122,10 @@ func shuffleBootstrappers(candidates []NetworkPeerBootstrapper) []NetworkPeerBoo
121122
type ChannelGraphBootstrapper struct {
122123
chanGraph autopilot.ChannelGraph
123124

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
129129

130130
tried map[autopilot.NodeID]struct{}
131131
}
@@ -138,18 +138,20 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
138138
// backed by an active autopilot.ChannelGraph instance. This type of network
139139
// peer bootstrapper will use the authenticated nodes within the known channel
140140
// graph to bootstrap connections.
141-
func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, error) {
141+
func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper,
142+
error) {
142143

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)
150148
}
151149

152-
return c, nil
150+
return &ChannelGraphBootstrapper{
151+
chanGraph: cg,
152+
tried: make(map[autopilot.NodeID]struct{}),
153+
hashAccumulator: hashAccumulator,
154+
}, nil
153155
}
154156

155157
// SampleNodeAddrs uniformly samples a set of specified address from the
@@ -199,7 +201,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
199201
// it's 50/50. If it isn't less, than then we'll
200202
// continue forward.
201203
nodePubKeyBytes := node.PubKey()
202-
if bytes.Compare(c.hashAccumulator[:], nodePubKeyBytes[1:]) > 0 {
204+
if c.hashAccumulator.skipNode(nodePubKeyBytes) {
203205
return nil
204206
}
205207

@@ -259,7 +261,7 @@ func (c *ChannelGraphBootstrapper) SampleNodeAddrs(_ context.Context,
259261
tries++
260262

261263
// We'll now rotate our hash accumulator one value forwards.
262-
c.hashAccumulator = sha256.Sum256(c.hashAccumulator[:])
264+
c.hashAccumulator.rotate()
263265

264266
// If this attempt didn't yield any addresses, then we'll exit
265267
// early.
@@ -546,3 +548,57 @@ search:
546548
func (d *DNSSeedBootstrapper) Name() string {
547549
return fmt.Sprintf("BOLT-0010 DNS Seed: %v", d.dnsSeeds)
548550
}
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

Comments
 (0)