autopilot: add ExternalScoreAttachment heuristic

This commit adds a new autopilot heuristic that is scoring based. It is
a simple heuristic that will keep a list of pubkeys and scores, and will
try opening channels with the nodes with the largest score first.
This commit is contained in:
Johan T. Halseth 2019-02-14 11:37:47 +01:00
parent e10fe91f10
commit dff61facf2
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
2 changed files with 120 additions and 0 deletions

@ -0,0 +1,119 @@
package autopilot
import (
"fmt"
"sync"
"github.com/btcsuite/btcutil"
)
// ExternalScoreAttachment is an implementation of the AttachmentHeuristic
// interface that allows an external source to provide it with node scores.
type ExternalScoreAttachment struct {
// TODO(halseth): persist across restarts.
nodeScores map[NodeID]float64
sync.Mutex
}
// NewExternalScoreAttachment creates a new instance of an
// ExternalScoreAttachment.
func NewExternalScoreAttachment() *ExternalScoreAttachment {
return &ExternalScoreAttachment{}
}
// A compile time assertion to ensure ExternalScoreAttachment meets the
// AttachmentHeuristic interface.
var _ AttachmentHeuristic = (*ExternalScoreAttachment)(nil)
// Name returns the name of this heuristic.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) Name() string {
return "externalscore"
}
// SetNodeScores is used to set the internal map from NodeIDs to scores. The
// passed scores must be in the range [0, 1.0]. The fist parameter is the name
// of the targeted heuristic, to allow recursively target specific
// sub-heuristics. The returned boolean indicates whether the targeted
// heuristic was found.
func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
newScores map[NodeID]float64) (bool, error) {
// Return if this heuristic wasn't targeted.
if targetHeuristic != s.Name() {
return false, nil
}
// Since there's a requirement that all score are in the range [0,
// 1.0], we validate them before setting the internal list.
for nID, s := range newScores {
if s < 0 || s > 1.0 {
return false, fmt.Errorf("invalid score %v for "+
"nodeID %v", s, nID)
}
}
s.Lock()
defer s.Unlock()
s.nodeScores = newScores
return true, nil
}
// NodeScores is a method that given the current channel graph and current set
// of local channels, scores the given nodes according to the preference of
// opening a channel of the given size with them. The returned channel
// candidates maps the NodeID to a NodeScore for the node.
//
// The returned scores will be in the range [0, 1.0], where 0 indicates no
// improvement in connectivity if a channel is opened to this node, while 1.0
// is the maximum possible improvement in connectivity.
//
// The scores are determined by checking the internal node scores list. Nodes
// not known will get a score of 0.
//
// NOTE: This is a part of the AttachmentHeuristic interface.
func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []Channel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) {
existingPeers := make(map[NodeID]struct{})
for _, c := range chans {
existingPeers[c.Node] = struct{}{}
}
s.Lock()
defer s.Unlock()
// Fill the map of candidates to return.
candidates := make(map[NodeID]*NodeScore)
for nID := range nodes {
var score float64
if nodeScore, ok := s.nodeScores[nID]; ok {
score = float64(nodeScore)
}
_, ok := existingPeers[nID]
switch {
// If the node is among or existing channel peers, we don't
// need another channel.
case ok:
continue
// Instead of adding a node with score 0 to the returned set,
// we just skip it.
case score == 0:
continue
}
candidates[nID] = &NodeScore{
NodeID: nID,
Score: score,
}
}
return candidates, nil
}

@ -147,6 +147,7 @@ var (
// with the autopilot agent.
availableHeuristics = []AttachmentHeuristic{
NewPrefAttachment(),
NewExternalScoreAttachment(),
}
// AvailableHeuristics is a map that holds the name of available