diff --git a/autopilot/externalscoreattach.go b/autopilot/externalscoreattach.go new file mode 100644 index 00000000..817ca769 --- /dev/null +++ b/autopilot/externalscoreattach.go @@ -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 +} diff --git a/autopilot/interface.go b/autopilot/interface.go index ac2be6bb..04a2ecdc 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -147,6 +147,7 @@ var ( // with the autopilot agent. availableHeuristics = []AttachmentHeuristic{ NewPrefAttachment(), + NewExternalScoreAttachment(), } // AvailableHeuristics is a map that holds the name of available