autopilot/interface+prefattach: scale node scores to range [0.0, 1.0]

To prepare for combinning scores from multiple heuristics, we require the
scores returned from the NodeSores API to be in the range [0.0, 1.0].

The prefAttach heuristic is altered to scale the returned scores such
that the most connected node in the grpah is given a score of 1.0.
This commit is contained in:
Johan T. Halseth 2019-01-09 09:14:45 +01:00
parent 4537c63dbd
commit 592ce92c72
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
3 changed files with 24 additions and 17 deletions

@ -125,11 +125,12 @@ type AttachmentHeuristic interface {
// returned channel candidates maps the NodeID to a NodeScore for the // returned channel candidates maps the NodeID to a NodeScore for the
// node. // node.
// //
// The scores will be in the range [0, M], where 0 indicates no // The returned scores will be in the range [0, 1.0], where 0 indicates
// improvement in connectivity if a channel is opened to this node, // no improvement in connectivity if a channel is opened to this node,
// while M is the maximum possible improvement in connectivity. The // while 1.0 is the maximum possible improvement in connectivity. The
// size of M is up to the implementation of this interface, so scores // implementation of this interface must return scores in this range to
// must be normalized if compared against other implementations. // properly allow the autopilot agent to make a reasonable choice based
// on the score from multiple heuristics.
// //
// NOTE: A NodeID not found in the returned map is implicitly given a // NOTE: A NodeID not found in the returned map is implicitly given a
// score of 0. // score of 0.

@ -43,9 +43,10 @@ func NewNodeID(pub *btcec.PublicKey) NodeID {
return n return n
} }
// NodeScores is a method that given the current channel graph and // NodeScores is a method that given the current channel graph and current set
// current set of local channels, scores the given nodes according to // of local channels, scores the given nodes according to the preference of
// the preference of opening a channel of the given size with them. // opening a channel of the given size with them. The returned channel
// candidates maps the NodeID to a NodeScore for the node.
// //
// The heuristic employed by this method is one that attempts to promote a // The heuristic employed by this method is one that attempts to promote a
// scale-free network globally, via local attachment preferences for new nodes // scale-free network globally, via local attachment preferences for new nodes
@ -64,21 +65,25 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []Channel,
chanSize btcutil.Amount, nodes map[NodeID]struct{}) ( chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
map[NodeID]*NodeScore, error) { map[NodeID]*NodeScore, error) {
// Count the number of channels in the graph. We'll also count the // Count the number of channels for each particular node in the graph.
// number of channels as we go for the nodes we are interested in. var maxChans int
var graphChans int
nodeChanNum := make(map[NodeID]int) nodeChanNum := make(map[NodeID]int)
if err := g.ForEachNode(func(n Node) error { if err := g.ForEachNode(func(n Node) error {
var nodeChans int var nodeChans int
err := n.ForEachChannel(func(_ ChannelEdge) error { err := n.ForEachChannel(func(_ ChannelEdge) error {
nodeChans++ nodeChans++
graphChans++
return nil return nil
}) })
if err != nil { if err != nil {
return err return err
} }
// We keep track of the highest-degree node we've seen, as this
// will be given the max score.
if nodeChans > maxChans {
maxChans = nodeChans
}
// If this node is not among our nodes to score, we can return // If this node is not among our nodes to score, we can return
// early. // early.
nID := NodeID(n.PubKey()) nID := NodeID(n.PubKey())
@ -97,7 +102,7 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []Channel,
// If there are no channels in the graph we cannot determine any // If there are no channels in the graph we cannot determine any
// preferences, so we return, indicating all candidates get a score of // preferences, so we return, indicating all candidates get a score of
// zero. // zero.
if graphChans == 0 { if maxChans == 0 {
return nil, nil return nil, nil
} }
@ -127,8 +132,9 @@ func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []Channel,
} }
// Otherwise we score the node according to its fraction of // Otherwise we score the node according to its fraction of
// channels in the graph. // channels in the graph, scaled such that the highest-degree
score := float64(nodeChans) / float64(graphChans) // node will be given a score of 1.0.
score := float64(nodeChans) / float64(maxChans)
candidates[nID] = &NodeScore{ candidates[nID] = &NodeScore{
NodeID: nID, NodeID: nID,
Score: score, Score: score,

@ -249,8 +249,8 @@ func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
// Since each of the nodes has 1 channel, out // Since each of the nodes has 1 channel, out
// of only one channel in the graph, we expect // of only one channel in the graph, we expect
// their score to be 0.5. // their score to be 1.0.
expScore := float64(0.5) expScore := float64(1.0)
if candidate.Score != expScore { if candidate.Score != expScore {
t1.Fatalf("expected candidate score "+ t1.Fatalf("expected candidate score "+
"to be %v, instead was %v", "to be %v, instead was %v",