383 lines
12 KiB
Go
383 lines
12 KiB
Go
package discovery
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/autopilot"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcutil/bech32"
|
|
)
|
|
|
|
// NetworkPeerBootstrapper is an interface that represents an initial peer
|
|
// boostrap mechanism. This interface is to be used to bootstrap a new peer to
|
|
// the connection by providing it with the pubkey+address of a set of existing
|
|
// peers on the network. Several bootstrap mechanisms can be implemented such
|
|
// as DNS, in channel graph, DHT's, etc.
|
|
type NetworkPeerBootstrapper interface {
|
|
// SampleNodeAddrs uniformly samples a set of specified address from
|
|
// the network peer bootstrapper source. The num addrs field passed in
|
|
// denotes how many valid peer addresses to return. The passed set of
|
|
// node nodes allows the caller to ignore a set of nodes perhaps
|
|
// because they already have connections established.
|
|
SampleNodeAddrs(numAddrs uint32,
|
|
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error)
|
|
|
|
// Name returns a human readable string which names the concrete
|
|
// implementation of the NetworkPeerBootstrapper.
|
|
Name() string
|
|
}
|
|
|
|
// MultiSourceBootstrap attempts to utilize a set of NetworkPeerBootstrapper
|
|
// passed in to return the target (numAddrs) number of peer addresses that can
|
|
// be used to bootstrap a peer just joining the Lightning Network. Each
|
|
// bootstrapper will be queried successively until the target amount is met. If
|
|
// the ignore map is populated, then the bootstrappers will be instructed to
|
|
// skip those nodes.
|
|
func MultiSourceBootstrap(ignore map[autopilot.NodeID]struct{}, numAddrs uint32,
|
|
bootStrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) {
|
|
|
|
var addrs []*lnwire.NetAddress
|
|
for _, bootStrapper := range bootStrappers {
|
|
// If we already have enough addresses, then we can exit early
|
|
// w/o querying the additional boostrappers.
|
|
if uint32(len(addrs)) >= numAddrs {
|
|
break
|
|
}
|
|
|
|
log.Infof("Attempting to bootstrap with: %v", bootStrapper.Name())
|
|
|
|
// If we still need additional addresses, then we'll compute
|
|
// the number of address remaining that we need to fetch.
|
|
numAddrsLeft := numAddrs - uint32(len(addrs))
|
|
log.Tracef("Querying for %v addresses", numAddrsLeft)
|
|
netAddrs, err := bootStrapper.SampleNodeAddrs(numAddrsLeft, ignore)
|
|
if err != nil {
|
|
// If we encounter an error with a bootstrapper, then
|
|
// we'll continue on to the next available
|
|
// bootstrapper.
|
|
log.Errorf("Unable to query bootstrapper %v: %v",
|
|
bootStrapper.Name(), err)
|
|
continue
|
|
}
|
|
|
|
addrs = append(addrs, netAddrs...)
|
|
}
|
|
|
|
log.Infof("Obtained %v addrs to bootstrap network with", len(addrs))
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
// ChannelGraphBootstrapper is an implementation of the NetworkPeerBootstrapper
|
|
// which attempts to retrieve advertised peers directly from the active channel
|
|
// graph. This instance requires a backing autopilot.ChannelGraph instance in
|
|
// order to operate properly.
|
|
type ChannelGraphBootstrapper struct {
|
|
chanGraph autopilot.ChannelGraph
|
|
|
|
// hashAccumulator is a set of 32 random bytes that are read upon the
|
|
// creation of the channel graph boostrapper. We use this value to
|
|
// randomly select nodes within the known graph to connect to. After
|
|
// each selection, we rotate the accumulator by hashing it with itself.
|
|
hashAccumulator [32]byte
|
|
|
|
tried map[autopilot.NodeID]struct{}
|
|
}
|
|
|
|
// A compile time assertion to ensure that ChannelGraphBootstrapper meets the
|
|
// NetworkPeerBootstrapper interface.
|
|
var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
|
|
|
|
// NewGraphBootstrapper returns a new instance of a ChannelGraphBootstrapper
|
|
// backed by an active autopilot.ChannelGraph instance. This type of network
|
|
// peer bootstrapper will use the authenticated nodes within the known channel
|
|
// graph to bootstrap connections.
|
|
func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, error) {
|
|
|
|
c := &ChannelGraphBootstrapper{
|
|
chanGraph: cg,
|
|
tried: make(map[autopilot.NodeID]struct{}),
|
|
}
|
|
|
|
if _, err := rand.Read(c.hashAccumulator[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
// SampleNodeAddrs uniformly samples a set of specified address from the
|
|
// network peer bootstrapper source. The num addrs field passed in denotes how
|
|
// many valid peer addresses to return.
|
|
//
|
|
// NOTE: Part of the NetworkPeerBootstrapper interface.
|
|
func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
|
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
|
|
|
|
// We'll merge the ignore map with our currently selected map in order
|
|
// to ensure we don't return any duplicate nodes.
|
|
for n := range ignore {
|
|
c.tried[n] = struct{}{}
|
|
}
|
|
|
|
// In order to bootstrap, we'll iterate all the nodes in the channel
|
|
// graph, accumulating nodes until either we go through all active
|
|
// nodes, or we reach our limit. We ensure that we meet the randomly
|
|
// sample constraint as we maintain an xor accumulator to ensure we
|
|
// randomly sample nodes independent of the iteration of the channel
|
|
// graph.
|
|
sampleAddrs := func() ([]*lnwire.NetAddress, error) {
|
|
var (
|
|
a []*lnwire.NetAddress
|
|
|
|
// We'll create a special error so we can return early
|
|
// and abort the transaction once we find a match.
|
|
errFound = fmt.Errorf("found node")
|
|
)
|
|
|
|
err := c.chanGraph.ForEachNode(func(node autopilot.Node) error {
|
|
nID := autopilot.NewNodeID(node.PubKey())
|
|
if _, ok := c.tried[nID]; ok {
|
|
return nil
|
|
}
|
|
|
|
// We'll select the first node we come across who's
|
|
// public key is less than our current accumulator
|
|
// value. When comparing, we skip the first byte as
|
|
// it's 50/50. If it isn't less, than then we'll
|
|
// continue forward.
|
|
nodePub := node.PubKey().SerializeCompressed()[1:]
|
|
if bytes.Compare(c.hashAccumulator[:], nodePub) > 0 {
|
|
return nil
|
|
}
|
|
|
|
for _, nodeAddr := range node.Addrs() {
|
|
// If we haven't yet reached our limit, then
|
|
// we'll copy over the details of this node
|
|
// into the set of addresses to be returned.
|
|
tcpAddr, ok := nodeAddr.(*net.TCPAddr)
|
|
if !ok {
|
|
// If this isn't a valid TCP address,
|
|
// then we'll ignore it as currently
|
|
// we'll only attempt to connect out to
|
|
// TCP peers.
|
|
return nil
|
|
}
|
|
|
|
// At this point, we've found an eligible node,
|
|
// so we'll return early with our shibboleth
|
|
// error.
|
|
a = append(a, &lnwire.NetAddress{
|
|
IdentityKey: node.PubKey(),
|
|
Address: tcpAddr,
|
|
})
|
|
}
|
|
|
|
c.tried[nID] = struct{}{}
|
|
|
|
return errFound
|
|
})
|
|
if err != nil && err != errFound {
|
|
return nil, err
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
// We'll loop and sample new addresses from the graph source until
|
|
// we've reached our target number of outbound connections or we hit 50
|
|
// attempts, which ever comes first.
|
|
var (
|
|
addrs []*lnwire.NetAddress
|
|
tries uint32
|
|
)
|
|
for tries < 30 && uint32(len(addrs)) < numAddrs {
|
|
sampleAddrs, err := sampleAddrs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tries++
|
|
|
|
// We'll now rotate our hash accumulator one value forwards.
|
|
c.hashAccumulator = sha256.Sum256(c.hashAccumulator[:])
|
|
|
|
// If this attempt didn't yield any addresses, then we'll exit
|
|
// early.
|
|
if len(sampleAddrs) == 0 {
|
|
continue
|
|
}
|
|
|
|
addrs = append(addrs, sampleAddrs...)
|
|
}
|
|
|
|
log.Tracef("Ending hash accumulator state: %x", c.hashAccumulator)
|
|
|
|
return addrs, nil
|
|
}
|
|
|
|
// Name returns a human readable string which names the concrete implementation
|
|
// of the NetworkPeerBootstrapper.
|
|
//
|
|
// NOTE: Part of the NetworkPeerBootstrapper interface.
|
|
func (c *ChannelGraphBootstrapper) Name() string {
|
|
return "Authenticated Channel Graph"
|
|
}
|
|
|
|
// DNSSeedBootstrapper as an implementation of the NetworkPeerBootstrapper
|
|
// interface which implements peer bootstrapping via a spcial DNS seed as
|
|
// defined in BOLT-0010. For further details concerning Lightning's current DNS
|
|
// boot strapping protocol, see this link:
|
|
// * https://github.com/lightningnetwork/lightning-rfc/blob/master/10-dns-bootstrap.md
|
|
type DNSSeedBootstrapper struct {
|
|
dnsSeeds []string
|
|
}
|
|
|
|
// A compile time assertion to ensure that DNSSeedBootstrapper meets the
|
|
// NetworkPeerjBootstrapper interface.
|
|
var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
|
|
|
|
// NewDNSSeedBootstrapper returns a new instance of the DNSSeedBootstrapper.
|
|
// The set of passed seeds should point to DNS servers that properly implement
|
|
// Lighting's DNS peer bootstrapping protocol as defined in BOLT-0010.
|
|
//
|
|
//
|
|
// TODO(roasbeef): add a lookUpFunc param to pass in, so can divert queries
|
|
// over Tor in future
|
|
func NewDNSSeedBootstrapper(seeds []string) (NetworkPeerBootstrapper, error) {
|
|
return &DNSSeedBootstrapper{
|
|
dnsSeeds: seeds,
|
|
}, nil
|
|
}
|
|
|
|
// SampleNodeAddrs uniformly samples a set of specified address from the
|
|
// network peer bootstrapper source. The num addrs field passed in denotes how
|
|
// many valid peer addresses to return. The set of DNS seeds are used
|
|
// successively to retrieve eligible target nodes.
|
|
func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32,
|
|
ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) {
|
|
|
|
var netAddrs []*lnwire.NetAddress
|
|
|
|
// We'll continue this loop until we reach our target address limit.
|
|
// Each SRV query to the seed will return 25 random nodes, so we can
|
|
// continue to query until we reach our target.
|
|
search:
|
|
for uint32(len(netAddrs)) < numAddrs {
|
|
for _, dnsSeed := range d.dnsSeeds {
|
|
// We'll first query the seed with an SRV record so we
|
|
// can obtain a random sample of the encoded public
|
|
// keys of nodes.
|
|
_, addrs, err := net.LookupSRV("nodes", "tcp", dnsSeed)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Tracef("Retrieved SRV records from dns seed: %v",
|
|
spew.Sdump(addrs))
|
|
|
|
// Next, we'll need to issue an A record request for
|
|
// each of the nodes, skipping it if nothing comes
|
|
// back.
|
|
for _, nodeSrv := range addrs {
|
|
if uint32(len(netAddrs)) >= numAddrs {
|
|
break search
|
|
}
|
|
|
|
// With the SRV target obtained, we'll now
|
|
// perform another query to obtain the IP
|
|
// address for the matching bech32 encoded node
|
|
// key.
|
|
bechNodeHost := nodeSrv.Target
|
|
addrs, err := net.LookupHost(bechNodeHost)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(addrs) == 0 {
|
|
log.Tracef("No addresses for %v, skipping",
|
|
bechNodeHost)
|
|
continue
|
|
}
|
|
|
|
log.Tracef("Attempting to convert: %v", bechNodeHost)
|
|
|
|
// If we have a set of valid addresses, then
|
|
// we'll need to parse the public key from the
|
|
// original bech32 encoded string.
|
|
bechNode := strings.Split(bechNodeHost, ".")
|
|
_, nodeBytes5Bits, err := bech32.Decode(bechNode[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Once we have the bech32 decoded pubkey,
|
|
// we'll need to convert the 5-bit word
|
|
// grouping into our regular 8-bit word
|
|
// grouping so we can convert it into a public
|
|
// key.
|
|
nodeBytes, err := bech32.ConvertBits(
|
|
nodeBytes5Bits, 5, 8, false,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nodeKey, err := btcec.ParsePubKey(
|
|
nodeBytes, btcec.S256(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If we have an ignore list, and this node is
|
|
// in the ignore list, then we'll go to the
|
|
// next candidate.
|
|
if ignore != nil {
|
|
nID := autopilot.NewNodeID(nodeKey)
|
|
if _, ok := ignore[nID]; ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Finally we'll convert the host:port peer to
|
|
// a proper TCP address to use within the
|
|
// lnwire.NetAddress.
|
|
addr := fmt.Sprintf("%v:%v", addrs[0],
|
|
nodeSrv.Port)
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Finally, with all the information parsed,
|
|
// we'll return this fully valid address as a
|
|
// connection attempt.
|
|
lnAddr := &lnwire.NetAddress{
|
|
IdentityKey: nodeKey,
|
|
Address: tcpAddr,
|
|
}
|
|
|
|
log.Tracef("Obtained %v as valid reachable "+
|
|
"node", lnAddr)
|
|
|
|
netAddrs = append(netAddrs, lnAddr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return netAddrs, nil
|
|
}
|
|
|
|
// Name returns a human readable string which names the concrete
|
|
// implementation of the NetworkPeerBootstrapper.
|
|
func (d *DNSSeedBootstrapper) Name() string {
|
|
return fmt.Sprintf("BOLT-0010 DNS Seed: %v", d.dnsSeeds)
|
|
}
|