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.