github.com/decred/dcrlnd@v0.7.6/autopilot/combinedattach.go (about) 1 package autopilot 2 3 import ( 4 "fmt" 5 6 "github.com/decred/dcrd/dcrutil/v4" 7 ) 8 9 // WeightedHeuristic is a tuple that associates a weight to an 10 // AttachmentHeuristic. This is used to determining a node's final score when 11 // querying several heuristics for scores. 12 type WeightedHeuristic struct { 13 // Weight is this AttachmentHeuristic's relative weight factor. It 14 // should be between 0.0 and 1.0. 15 Weight float64 16 17 AttachmentHeuristic 18 } 19 20 // WeightedCombAttachment is an implementation of the AttachmentHeuristic 21 // interface that combines the scores given by several sub-heuristics into one. 22 type WeightedCombAttachment struct { 23 heuristics []*WeightedHeuristic 24 } 25 26 // NewWeightedCombAttachment creates a new instance of a WeightedCombAttachment. 27 func NewWeightedCombAttachment(h ...*WeightedHeuristic) ( 28 *WeightedCombAttachment, error) { 29 30 // The sum of weights given to the sub-heuristics must sum to exactly 31 // 1.0. 32 var sum float64 33 for _, w := range h { 34 sum += w.Weight 35 } 36 37 if sum != 1.0 { 38 return nil, fmt.Errorf("weights MUST sum to 1.0 (was %v)", sum) 39 } 40 41 return &WeightedCombAttachment{ 42 heuristics: h, 43 }, nil 44 } 45 46 // A compile time assertion to ensure WeightedCombAttachment meets the 47 // AttachmentHeuristic and ScoreSettable interfaces. 48 var _ AttachmentHeuristic = (*WeightedCombAttachment)(nil) 49 var _ ScoreSettable = (*WeightedCombAttachment)(nil) 50 51 // Name returns the name of this heuristic. 52 // 53 // NOTE: This is a part of the AttachmentHeuristic interface. 54 func (c *WeightedCombAttachment) Name() string { 55 return "weightedcomb" 56 } 57 58 // NodeScores is a method that given the current channel graph, current set of 59 // local channels and funds available, scores the given nodes according to the 60 // preference of opening a channel with them. The returned channel candidates 61 // maps the NodeID to an attachment directive containing a score and a channel 62 // size. 63 // 64 // The scores is determined by quering the set of sub-heuristics, then 65 // combining these scores into a final score according to the active 66 // configuration. 67 // 68 // The returned scores will be in the range [0, 1.0], where 0 indicates no 69 // improvement in connectivity if a channel is opened to this node, while 1.0 70 // is the maximum possible improvement in connectivity. 71 // 72 // NOTE: This is a part of the AttachmentHeuristic interface. 73 func (c *WeightedCombAttachment) NodeScores(g ChannelGraph, chans []LocalChannel, 74 chanSize dcrutil.Amount, nodes map[NodeID]struct{}) ( 75 map[NodeID]*NodeScore, error) { 76 77 // We now query each heuristic to determine the score they give to the 78 // nodes for the given channel size. 79 var subScores []map[NodeID]*NodeScore 80 for _, h := range c.heuristics { 81 log.Tracef("Getting scores from sub heuristic %v", h.Name()) 82 83 s, err := h.NodeScores( 84 g, chans, chanSize, nodes, 85 ) 86 if err != nil { 87 return nil, fmt.Errorf("unable to get sub score: %v", 88 err) 89 } 90 91 subScores = append(subScores, s) 92 } 93 94 // We combine the scores given by the sub-heuristics by using the 95 // heruistics' given weight factor. 96 scores := make(map[NodeID]*NodeScore) 97 for nID := range nodes { 98 score := &NodeScore{ 99 NodeID: nID, 100 } 101 102 // Each sub-heuristic should have scored the node, if not it is 103 // implicitly given a zero score by that heuristic. 104 for i, h := range c.heuristics { 105 sub, ok := subScores[i][nID] 106 if !ok { 107 log.Tracef("No score given to node %x by sub "+ 108 "heuristic %v", nID[:], h.Name()) 109 continue 110 } 111 // Use the heuristic's weight factor to determine of 112 // how much weight we should give to this particular 113 // score. 114 subScore := h.Weight * sub.Score 115 log.Tracef("Giving node %x a sub score of %v "+ 116 "(%v * %v) from sub heuristic %v", nID[:], 117 subScore, h.Weight, sub.Score, h.Name()) 118 119 score.Score += subScore 120 } 121 122 log.Tracef("Node %x got final combined score %v", nID[:], 123 score.Score) 124 125 switch { 126 // Instead of adding a node with score 0 to the returned set, 127 // we just skip it. 128 case score.Score == 0: 129 continue 130 131 // Sanity check the new score. 132 case score.Score < 0 || score.Score > 1.0: 133 return nil, fmt.Errorf("invalid node score from "+ 134 "combination: %v", score.Score) 135 } 136 137 scores[nID] = score 138 } 139 140 return scores, nil 141 } 142 143 // SetNodeScores is used to set the internal map from NodeIDs to scores. The 144 // passed scores must be in the range [0, 1.0]. The fist parameter is the name 145 // of the targeted heuristic, to allow recursively target specific 146 // sub-heuristics. The returned boolean indicates whether the targeted 147 // heuristic was found. 148 // 149 // Since this heuristic doesn't keep any internal scores, it will recursively 150 // apply the scores to its sub-heuristics. 151 // 152 // NOTE: This is a part of the ScoreSettable interface. 153 func (c *WeightedCombAttachment) SetNodeScores(targetHeuristic string, 154 newScores map[NodeID]float64) (bool, error) { 155 156 found := false 157 for _, h := range c.heuristics { 158 // It must be ScoreSettable to be available for external 159 // scores. 160 s, ok := h.AttachmentHeuristic.(ScoreSettable) 161 if !ok { 162 continue 163 } 164 165 // Heuristic supports scoring, attempt to set them. 166 applied, err := s.SetNodeScores(targetHeuristic, newScores) 167 if err != nil { 168 return false, err 169 } 170 found = found || applied 171 } 172 173 return found, nil 174 }