2019-01-09 11:14:45 +03:00
|
|
|
package autopilot
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// WeightedHeuristic is a tuple that associates a weight to an
|
|
|
|
// AttachmentHeuristic. This is used to determining a node's final score when
|
|
|
|
// querying several heuristics for scores.
|
|
|
|
type WeightedHeuristic struct {
|
|
|
|
// Weight is this AttachmentHeuristic's relative weight factor. It
|
|
|
|
// should be between 0.0 and 1.0.
|
|
|
|
Weight float64
|
|
|
|
|
|
|
|
AttachmentHeuristic
|
|
|
|
}
|
|
|
|
|
|
|
|
// WeightedCombAttachment is an implementation of the AttachmentHeuristic
|
|
|
|
// interface that combines the scores given by several sub-heuristics into one.
|
|
|
|
type WeightedCombAttachment struct {
|
|
|
|
heuristics []*WeightedHeuristic
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWeightedCombAttachment creates a new instance of a WeightedCombAttachment.
|
|
|
|
func NewWeightedCombAttachment(h ...*WeightedHeuristic) (
|
2019-02-14 13:37:47 +03:00
|
|
|
*WeightedCombAttachment, error) {
|
2019-01-09 11:14:45 +03:00
|
|
|
|
|
|
|
// The sum of weights given to the sub-heuristics must sum to exactly
|
|
|
|
// 1.0.
|
|
|
|
var sum float64
|
|
|
|
for _, w := range h {
|
|
|
|
sum += w.Weight
|
|
|
|
}
|
|
|
|
|
|
|
|
if sum != 1.0 {
|
|
|
|
return nil, fmt.Errorf("weights MUST sum to 1.0 (was %v)", sum)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &WeightedCombAttachment{
|
|
|
|
heuristics: h,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// A compile time assertion to ensure WeightedCombAttachment meets the
|
2019-02-14 13:37:47 +03:00
|
|
|
// AttachmentHeuristic and ScoreSettable interfaces.
|
2019-01-09 11:14:45 +03:00
|
|
|
var _ AttachmentHeuristic = (*WeightedCombAttachment)(nil)
|
2019-02-14 13:37:47 +03:00
|
|
|
var _ ScoreSettable = (*WeightedCombAttachment)(nil)
|
2019-01-09 11:14:45 +03:00
|
|
|
|
2018-12-19 16:54:54 +03:00
|
|
|
// Name returns the name of this heuristic.
|
|
|
|
//
|
|
|
|
// NOTE: This is a part of the AttachmentHeuristic interface.
|
|
|
|
func (c *WeightedCombAttachment) Name() string {
|
|
|
|
return "weightedcomb"
|
|
|
|
}
|
|
|
|
|
2019-01-09 11:14:45 +03:00
|
|
|
// 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 attachment directive containing a score and a channel
|
|
|
|
// size.
|
|
|
|
//
|
|
|
|
// The scores is determined by quering the set of sub-heuristics, then
|
|
|
|
// combining these scores into a final score according to the active
|
|
|
|
// configuration.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// NOTE: This is a part of the AttachmentHeuristic interface.
|
|
|
|
func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []Channel,
|
|
|
|
chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
|
|
|
|
map[NodeID]*NodeScore, error) {
|
|
|
|
|
|
|
|
// We now query each heuristic to determine the score they give to the
|
|
|
|
// nodes for the given channel size.
|
|
|
|
var subScores []map[NodeID]*NodeScore
|
|
|
|
for _, h := range c.heuristics {
|
2019-08-27 10:57:25 +03:00
|
|
|
log.Tracef("Getting scores from sub heuristic %v", h.Name())
|
|
|
|
|
2019-01-09 11:14:45 +03:00
|
|
|
s, err := h.NodeScores(
|
|
|
|
g, chans, chanSize, nodes,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to get sub score: %v",
|
|
|
|
err)
|
|
|
|
}
|
|
|
|
|
|
|
|
subScores = append(subScores, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We combine the scores given by the sub-heuristics by using the
|
|
|
|
// heruistics' given weight factor.
|
|
|
|
scores := make(map[NodeID]*NodeScore)
|
|
|
|
for nID := range nodes {
|
|
|
|
score := &NodeScore{
|
|
|
|
NodeID: nID,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each sub-heuristic should have scored the node, if not it is
|
|
|
|
// implicitly given a zero score by that heuristic.
|
|
|
|
for i, h := range c.heuristics {
|
|
|
|
sub, ok := subScores[i][nID]
|
|
|
|
if !ok {
|
2019-08-27 10:57:25 +03:00
|
|
|
log.Tracef("No score given to node %x by sub "+
|
|
|
|
"heuristic %v", nID[:], h.Name())
|
2019-01-09 11:14:45 +03:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Use the heuristic's weight factor to determine of
|
|
|
|
// how much weight we should give to this particular
|
|
|
|
// score.
|
2019-08-27 10:57:25 +03:00
|
|
|
subScore := h.Weight * sub.Score
|
|
|
|
log.Tracef("Giving node %x a sub score of %v "+
|
|
|
|
"(%v * %v) from sub heuristic %v", nID[:],
|
|
|
|
subScore, h.Weight, sub.Score, h.Name())
|
|
|
|
|
|
|
|
score.Score += subScore
|
2019-01-09 11:14:45 +03:00
|
|
|
}
|
|
|
|
|
2019-08-27 10:57:25 +03:00
|
|
|
log.Tracef("Node %x got final combined score %v", nID[:],
|
|
|
|
score.Score)
|
|
|
|
|
2019-01-09 11:14:45 +03:00
|
|
|
switch {
|
|
|
|
// Instead of adding a node with score 0 to the returned set,
|
|
|
|
// we just skip it.
|
|
|
|
case score.Score == 0:
|
|
|
|
continue
|
|
|
|
|
|
|
|
// Sanity check the new score.
|
|
|
|
case score.Score < 0 || score.Score > 1.0:
|
2020-04-14 20:56:05 +03:00
|
|
|
return nil, fmt.Errorf("invalid node score from "+
|
2019-01-09 11:14:45 +03:00
|
|
|
"combination: %v", score.Score)
|
|
|
|
}
|
|
|
|
|
|
|
|
scores[nID] = score
|
|
|
|
}
|
|
|
|
|
|
|
|
return scores, nil
|
|
|
|
}
|
2019-02-14 13:37:47 +03:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// Since this heuristic doesn't keep any internal scores, it will recursively
|
|
|
|
// apply the scores to its sub-heuristics.
|
2019-02-14 13:37:47 +03:00
|
|
|
//
|
|
|
|
// NOTE: This is a part of the ScoreSettable interface.
|
2019-02-14 13:37:47 +03:00
|
|
|
func (c *WeightedCombAttachment) SetNodeScores(targetHeuristic string,
|
|
|
|
newScores map[NodeID]float64) (bool, error) {
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for _, h := range c.heuristics {
|
|
|
|
// It must be ScoreSettable to be available for external
|
|
|
|
// scores.
|
|
|
|
s, ok := h.AttachmentHeuristic.(ScoreSettable)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Heuristic supports scoring, attempt to set them.
|
|
|
|
applied, err := s.SetNodeScores(targetHeuristic, newScores)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
found = found || applied
|
|
|
|
}
|
|
|
|
|
|
|
|
return found, nil
|
|
|
|
}
|