From 25de66d27bd1c24ae41283e393ae428be4c9d8b1 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Wed, 19 Dec 2018 15:22:33 +0100 Subject: [PATCH] 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. --- autopilot/agent.go | 39 ++++++++++++++++++++++++++++++--------- autopilot/choice.go | 10 +++++----- autopilot/choice_test.go | 14 +++++++------- autopilot/interface.go | 12 ++++++++++++ 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/autopilot/agent.go b/autopilot/agent.go index 44d99ec9..0e121d7a 100644 --- a/autopilot/agent.go +++ b/autopilot/agent.go @@ -571,22 +571,43 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32, 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)) - // Now use the score to make a weighted choice which - // nodes to attempt to open channels to. - chanCandidates, err := chooseN(numChans, scores) + // Temporary convert to NodeScore. + nodeScores := make(map[NodeID]*NodeScore) + 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 { return fmt.Errorf("Unable to make weighted choice: %v", 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 { log.Infof("No eligible candidates to connect to") return nil diff --git a/autopilot/choice.go b/autopilot/choice.go index e3052535..661f58d9 100644 --- a/autopilot/choice.go +++ b/autopilot/choice.go @@ -46,10 +46,10 @@ func weightedChoice(w []float64) (int, error) { return 0, fmt.Errorf("unable to make choice") } -// chooseN picks at random min[n, len(s)] nodes if from the -// AttachmentDirectives map, with a probability weighted by their score. -func chooseN(n uint32, s map[NodeID]*AttachmentDirective) ( - map[NodeID]*AttachmentDirective, error) { +// chooseN picks at random min[n, len(s)] nodes if from the NodeScore map, with +// a probability weighted by their score. +func chooseN(n uint32, s map[NodeID]*NodeScore) ( + map[NodeID]*NodeScore, error) { // Keep track of the number of nodes not yet chosen, in addition to // 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 // 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 { choice, err := weightedChoice(scores) if err == ErrNoPositive { diff --git a/autopilot/choice_test.go b/autopilot/choice_test.go index 6c17c609..d984d1b4 100644 --- a/autopilot/choice_test.go +++ b/autopilot/choice_test.go @@ -173,7 +173,7 @@ func TestWeightedChoiceDistribution(t *testing.T) { func TestChooseNEmptyMap(t *testing.T) { t.Parallel() - nodes := map[NodeID]*AttachmentDirective{} + nodes := map[NodeID]*NodeScore{} property := func(n uint32) bool { res, err := chooseN(n, nodes) 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 // 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 // QuickTests. 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. n := uint8(rand.Uint32()) @@ -212,7 +212,7 @@ func (candidateMapVarLen) Generate(rand *rand.Rand, size int) reflect.Value { var nID [33]byte binary.BigEndian.PutUint32(nID[:], uint32(i)) - nodes[nID] = &AttachmentDirective{ + nodes[nID] = &NodeScore{ Score: s, } } @@ -226,7 +226,7 @@ func TestChooseNMinimum(t *testing.T) { t.Parallel() // 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 for _, v := range nodes { if v.Score > 0 { @@ -274,7 +274,7 @@ func TestChooseNSample(t *testing.T) { const maxIterations = 100000 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 // 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 binary.BigEndian.PutUint32(nID[:], i) - nodes[nID] = &AttachmentDirective{ + nodes[nID] = &NodeScore{ Score: s, } } diff --git a/autopilot/interface.go b/autopilot/interface.go index cb9ff9ab..75e15e6d 100644 --- a/autopilot/interface.go +++ b/autopilot/interface.go @@ -81,6 +81,18 @@ type ChannelGraph interface { 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 // AttachmentHeuristic. It details to which node a channel should be created // to, and also the parameters which should be used in the channel creation.