autopilot/interface+choice: add NodeScore type

We create a new type NodeScore which is a tuple (NodeID, score). The
weightedChoice and chooseN algorithms are altered to expect this type.

This is done in order to simplify the types we are using, since we were
only using a subset of the fields in AttachmentDirective.
This commit is contained in:
Johan T. Halseth 2018-12-19 15:22:33 +01:00
parent 3739c19ef8
commit 25de66d27b
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
4 changed files with 54 additions and 21 deletions

@ -571,22 +571,43 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
return fmt.Errorf("unable to calculate node scores : %v", err) return fmt.Errorf("unable to calculate node scores : %v", err)
} }
// Add addresses to the candidates.
for nID, c := range scores {
addrs := addresses[nID]
c.Addrs = addrs
}
log.Debugf("Got scores for %d nodes", len(scores)) log.Debugf("Got scores for %d nodes", len(scores))
// Now use the score to make a weighted choice which // Temporary convert to NodeScore.
// nodes to attempt to open channels to. nodeScores := make(map[NodeID]*NodeScore)
chanCandidates, err := chooseN(numChans, scores) for k, v := range scores {
nodeScores[k] = &NodeScore{
NodeID: v.NodeID,
Score: v.Score,
}
}
// Now use the score to make a weighted choice which nodes to attempt
// to open channels to.
nodeScores, err = chooseN(numChans, nodeScores)
if err != nil { if err != nil {
return fmt.Errorf("Unable to make weighted choice: %v", return fmt.Errorf("Unable to make weighted choice: %v",
err) err)
} }
chanCandidates := make(map[NodeID]*AttachmentDirective)
for nID := range nodeScores {
// Add addresses to the candidates.
addrs := addresses[nID]
// If the node has no known addresses, we cannot connect to it,
// so we'll skip it.
if len(addrs) == 0 {
continue
}
chanCandidates[nID] = &AttachmentDirective{
NodeID: nID,
ChanAmt: chanSize,
Addrs: addrs,
}
}
if len(chanCandidates) == 0 { if len(chanCandidates) == 0 {
log.Infof("No eligible candidates to connect to") log.Infof("No eligible candidates to connect to")
return nil return nil

@ -46,10 +46,10 @@ func weightedChoice(w []float64) (int, error) {
return 0, fmt.Errorf("unable to make choice") return 0, fmt.Errorf("unable to make choice")
} }
// chooseN picks at random min[n, len(s)] nodes if from the // chooseN picks at random min[n, len(s)] nodes if from the NodeScore map, with
// AttachmentDirectives map, with a probability weighted by their score. // a probability weighted by their score.
func chooseN(n uint32, s map[NodeID]*AttachmentDirective) ( func chooseN(n uint32, s map[NodeID]*NodeScore) (
map[NodeID]*AttachmentDirective, error) { map[NodeID]*NodeScore, error) {
// Keep track of the number of nodes not yet chosen, in addition to // Keep track of the number of nodes not yet chosen, in addition to
// their scores and NodeIDs. // their scores and NodeIDs.
@ -65,7 +65,7 @@ func chooseN(n uint32, s map[NodeID]*AttachmentDirective) (
// Pick a weighted choice from the remaining nodes as long as there are // Pick a weighted choice from the remaining nodes as long as there are
// nodes left, and we haven't already picked n. // nodes left, and we haven't already picked n.
chosen := make(map[NodeID]*AttachmentDirective) chosen := make(map[NodeID]*NodeScore)
for len(chosen) < int(n) && rem > 0 { for len(chosen) < int(n) && rem > 0 {
choice, err := weightedChoice(scores) choice, err := weightedChoice(scores)
if err == ErrNoPositive { if err == ErrNoPositive {

@ -173,7 +173,7 @@ func TestWeightedChoiceDistribution(t *testing.T) {
func TestChooseNEmptyMap(t *testing.T) { func TestChooseNEmptyMap(t *testing.T) {
t.Parallel() t.Parallel()
nodes := map[NodeID]*AttachmentDirective{} nodes := map[NodeID]*NodeScore{}
property := func(n uint32) bool { property := func(n uint32) bool {
res, err := chooseN(n, nodes) res, err := chooseN(n, nodes)
if err != nil { if err != nil {
@ -191,12 +191,12 @@ func TestChooseNEmptyMap(t *testing.T) {
// candidateMapVarLen is a type we'll use to generate maps of various lengths // candidateMapVarLen is a type we'll use to generate maps of various lengths
// up to 255 to be used during QuickTests. // up to 255 to be used during QuickTests.
type candidateMapVarLen map[NodeID]*AttachmentDirective type candidateMapVarLen map[NodeID]*NodeScore
// Generate generates a value of type candidateMapVarLen to be used during // Generate generates a value of type candidateMapVarLen to be used during
// QuickTests. // QuickTests.
func (candidateMapVarLen) Generate(rand *rand.Rand, size int) reflect.Value { func (candidateMapVarLen) Generate(rand *rand.Rand, size int) reflect.Value {
nodes := make(map[NodeID]*AttachmentDirective) nodes := make(map[NodeID]*NodeScore)
// To avoid creating huge maps, we restrict them to max uint8 len. // To avoid creating huge maps, we restrict them to max uint8 len.
n := uint8(rand.Uint32()) n := uint8(rand.Uint32())
@ -212,7 +212,7 @@ func (candidateMapVarLen) Generate(rand *rand.Rand, size int) reflect.Value {
var nID [33]byte var nID [33]byte
binary.BigEndian.PutUint32(nID[:], uint32(i)) binary.BigEndian.PutUint32(nID[:], uint32(i))
nodes[nID] = &AttachmentDirective{ nodes[nID] = &NodeScore{
Score: s, Score: s,
} }
} }
@ -226,7 +226,7 @@ func TestChooseNMinimum(t *testing.T) {
t.Parallel() t.Parallel()
// Helper to count the number of positive scores in the given map. // Helper to count the number of positive scores in the given map.
numPositive := func(nodes map[NodeID]*AttachmentDirective) int { numPositive := func(nodes map[NodeID]*NodeScore) int {
cnt := 0 cnt := 0
for _, v := range nodes { for _, v := range nodes {
if v.Score > 0 { if v.Score > 0 {
@ -274,7 +274,7 @@ func TestChooseNSample(t *testing.T) {
const maxIterations = 100000 const maxIterations = 100000
fifth := uint32(numNodes / 5) fifth := uint32(numNodes / 5)
nodes := make(map[NodeID]*AttachmentDirective) nodes := make(map[NodeID]*NodeScore)
// we make 5 buckets of nodes: 0, 0.1, 0.2, 0.4 and 0.8 score. We want // we make 5 buckets of nodes: 0, 0.1, 0.2, 0.4 and 0.8 score. We want
// to check that zero scores never gets chosen, while a doubling the // to check that zero scores never gets chosen, while a doubling the
@ -299,7 +299,7 @@ func TestChooseNSample(t *testing.T) {
var nID [33]byte var nID [33]byte
binary.BigEndian.PutUint32(nID[:], i) binary.BigEndian.PutUint32(nID[:], i)
nodes[nID] = &AttachmentDirective{ nodes[nID] = &NodeScore{
Score: s, Score: s,
} }
} }

@ -81,6 +81,18 @@ type ChannelGraph interface {
ForEachNode(func(Node) error) error ForEachNode(func(Node) error) error
} }
// NodeScore is a tuple mapping a NodeID to a score indicating the preference
// of opening a channel with it.
type NodeScore struct {
// NodeID is the serialized compressed pubkey of the node that is being
// scored.
NodeID NodeID
// Score is the score given by the heuristic for opening a channel of
// the given size to this node.
Score float64
}
// AttachmentDirective describes a channel attachment proscribed by an // AttachmentDirective describes a channel attachment proscribed by an
// AttachmentHeuristic. It details to which node a channel should be created // AttachmentHeuristic. It details to which node a channel should be created
// to, and also the parameters which should be used in the channel creation. // to, and also the parameters which should be used in the channel creation.