autopilot/prefattach+interface: add API NodeScores

This commit adds a new method NodeScores to the AttachementHeuristic
interface. Its intended use is to score a set of nodes according to
their preference as channel counterparties.

The PrefAttach heuristic gets a NodeScores method that will score the
ndoes according to their number of already existing channels, similar to
what is done already in Select.
This commit is contained in:
Johan T. Halseth 2018-11-22 23:18:09 +01:00
parent 5ecc209c41
commit 5e8e54083f
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
3 changed files with 135 additions and 0 deletions

@ -93,6 +93,13 @@ func (m *mockHeuristic) Select(self *btcec.PublicKey, graph ChannelGraph,
}
}
func (m *mockHeuristic) NodeScores(g ChannelGraph, chans []Channel,
fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*AttachmentDirective, error) {
return nil, nil
}
var _ AttachmentHeuristic = (*mockHeuristic)(nil)
type openChanIntent struct {

@ -98,6 +98,10 @@ type AttachmentDirective struct {
// Addrs is a list of addresses that the target peer may be reachable
// at.
Addrs []net.Addr
// Score is the score given by the heuristic for opening a channel of
// the given size to this node.
Score float64
}
// AttachmentHeuristic is one of the primary interfaces within this package.
@ -127,6 +131,24 @@ type AttachmentHeuristic interface {
Select(self *btcec.PublicKey, graph ChannelGraph,
amtToUse btcutil.Amount, numNewChans uint32,
skipNodes map[NodeID]struct{}) ([]AttachmentDirective, error)
// NodeScores is a method that given the current channel graph, current
// set of local channels and funds available, scores the given nodes
// according to the preference of opening a channel with them. The
// returned channel candidates maps the NodeID to an attachemnt
// directive containing a score and a channel size.
//
// The scores will be in the range [0, M], where 0 indicates no
// improvement in connectivity if a channel is opened to this node,
// while M is the maximum possible improvement in connectivity. The
// size of M is up to the implementation of this interface, so scores
// must be normalized if compared against other implementations.
//
// NOTE: A NodeID not found in the returned map is implicitly given a
// score of 0.
NodeScores(g ChannelGraph, chans []Channel,
fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*AttachmentDirective, error)
}
// ChannelController is a simple interface that allows an auto-pilot agent to

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
prand "math/rand"
"net"
"time"
"github.com/btcsuite/btcd/btcec"
@ -249,3 +250,108 @@ func (p *ConstrainedPrefAttachment) Select(self *btcec.PublicKey, g ChannelGraph
return nil, fmt.Errorf("err")
}
}
// NodeScores is a method that given the current channel graph, current set of
// local channels and funds available, scores the given nodes according the the
// preference of opening a channel with them.
//
// The heuristic employed by this method is one that attempts to promote a
// scale-free network globally, via local attachment preferences for new nodes
// joining the network with an amount of available funds to be allocated to
// channels. Specifically, we consider the degree of each node (and the flow
// in/out of the node available via its open channels) and utilize the
// BarabásiAlbert model to drive our recommended attachment heuristics. If
// implemented globally for each new participant, this results in a channel
// graph that is scale-free and follows a power law distribution with k=-3.
//
// The returned scores will be in the range [0.0, 1.0], where higher scores are
// given to nodes already having high connectivity in the graph.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (p *ConstrainedPrefAttachment) NodeScores(g ChannelGraph, chans []Channel,
fundsAvailable btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*AttachmentDirective, error) {
// Count the number of channels in the graph. We'll also count the
// number of channels as we go for the nodes we are interested in, and
// record their addresses found in the db.
var graphChans int
nodeChanNum := make(map[NodeID]int)
addresses := make(map[NodeID][]net.Addr)
if err := g.ForEachNode(func(n Node) error {
var nodeChans int
err := n.ForEachChannel(func(_ ChannelEdge) error {
nodeChans++
graphChans++
return nil
})
if err != nil {
return err
}
// If this node is not among our nodes to score, we can return
// early.
nID := NodeID(n.PubKey())
if _, ok := nodes[nID]; !ok {
return nil
}
// Otherwise we'll record the number of channels, and also
// populate the address in our channel candidates map.
nodeChanNum[nID] = nodeChans
addresses[nID] = n.Addrs()
return nil
}); err != nil {
return nil, err
}
// If there are no channels in the graph we cannot determine any
// preferences, so we return, indicating all candidates get a score of
// zero.
if graphChans == 0 {
return nil, nil
}
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
// For each node in the set of nodes, count their fraction of channels
// in the graph, and use that as the score.
candidates := make(map[NodeID]*AttachmentDirective)
for nID, nodeChans := range nodeChanNum {
// As channel size we'll use the maximum channel size available.
chanSize := p.constraints.MaxChanSize
if fundsAvailable-chanSize < 0 {
chanSize = fundsAvailable
}
_, ok := existingPeers[nID]
switch {
// If the node is among or existing channel peers, we don't
// need another channel.
case ok:
continue
// If the amount is too small, we don't want to attempt opening
// another channel.
case chanSize == 0 || chanSize < p.constraints.MinChanSize:
continue
}
// Otherwise we score the node according to its fraction of
// channels in the graph.
score := float64(nodeChans) / float64(graphChans)
candidates[nID] = &AttachmentDirective{
NodeID: nID,
ChanAmt: chanSize,
Score: score,
}
}
return candidates, nil
}