github.com/decred/dcrlnd@v0.7.6/autopilot/externalscoreattach.go (about)

     1  package autopilot
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  )
     9  
    10  // ExternalScoreAttachment is an implementation of the AttachmentHeuristic
    11  // interface that allows an external source to provide it with node scores.
    12  type ExternalScoreAttachment struct {
    13  	// TODO(halseth): persist across restarts.
    14  	nodeScores map[NodeID]float64
    15  
    16  	sync.Mutex
    17  }
    18  
    19  // NewExternalScoreAttachment creates a new instance of an
    20  // ExternalScoreAttachment.
    21  func NewExternalScoreAttachment() *ExternalScoreAttachment {
    22  	return &ExternalScoreAttachment{}
    23  }
    24  
    25  // A compile time assertion to ensure ExternalScoreAttachment meets the
    26  // AttachmentHeuristic and ScoreSettable interfaces.
    27  var _ AttachmentHeuristic = (*ExternalScoreAttachment)(nil)
    28  var _ ScoreSettable = (*ExternalScoreAttachment)(nil)
    29  
    30  // Name returns the name of this heuristic.
    31  //
    32  // NOTE: This is a part of the AttachmentHeuristic interface.
    33  func (s *ExternalScoreAttachment) Name() string {
    34  	return "externalscore"
    35  }
    36  
    37  // SetNodeScores is used to set the internal map from NodeIDs to scores. The
    38  // passed scores must be in the range [0, 1.0]. The fist parameter is the name
    39  // of the targeted heuristic, to allow recursively target specific
    40  // sub-heuristics. The returned boolean indicates whether the targeted
    41  // heuristic was found.
    42  //
    43  // NOTE: This is a part of the ScoreSettable interface.
    44  func (s *ExternalScoreAttachment) SetNodeScores(targetHeuristic string,
    45  	newScores map[NodeID]float64) (bool, error) {
    46  
    47  	// Return if this heuristic wasn't targeted.
    48  	if targetHeuristic != s.Name() {
    49  		return false, nil
    50  	}
    51  
    52  	// Since there's a requirement that all score are in the range [0,
    53  	// 1.0], we validate them before setting the internal list.
    54  	for nID, s := range newScores {
    55  		if s < 0 || s > 1.0 {
    56  			return false, fmt.Errorf("invalid score %v for "+
    57  				"nodeID %v", s, nID)
    58  		}
    59  	}
    60  
    61  	s.Lock()
    62  	defer s.Unlock()
    63  
    64  	s.nodeScores = newScores
    65  	log.Tracef("Setting %v external scores", len(s.nodeScores))
    66  
    67  	return true, nil
    68  }
    69  
    70  // NodeScores is a method that given the current channel graph and current set
    71  // of local channels, scores the given nodes according to the preference of
    72  // opening a channel of the given size with them. The returned channel
    73  // candidates maps the NodeID to a NodeScore for the node.
    74  //
    75  // The returned scores will be in the range [0, 1.0], where 0 indicates no
    76  // improvement in connectivity if a channel is opened to this node, while 1.0
    77  // is the maximum possible improvement in connectivity.
    78  //
    79  // The scores are determined by checking the internal node scores list. Nodes
    80  // not known will get a score of 0.
    81  //
    82  // NOTE: This is a part of the AttachmentHeuristic interface.
    83  func (s *ExternalScoreAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
    84  	chanSize dcrutil.Amount, nodes map[NodeID]struct{}) (
    85  	map[NodeID]*NodeScore, error) {
    86  
    87  	existingPeers := make(map[NodeID]struct{})
    88  	for _, c := range chans {
    89  		existingPeers[c.Node] = struct{}{}
    90  	}
    91  
    92  	s.Lock()
    93  	defer s.Unlock()
    94  
    95  	log.Tracef("External scoring %v nodes, from %v set scores",
    96  		len(nodes), len(s.nodeScores))
    97  
    98  	// Fill the map of candidates to return.
    99  	candidates := make(map[NodeID]*NodeScore)
   100  	for nID := range nodes {
   101  		var score float64
   102  		if nodeScore, ok := s.nodeScores[nID]; ok {
   103  			score = nodeScore
   104  		}
   105  
   106  		// If the node is among or existing channel peers, we don't
   107  		// need another channel.
   108  		if _, ok := existingPeers[nID]; ok {
   109  			log.Tracef("Skipping existing peer %x from external "+
   110  				"score results", nID[:])
   111  			continue
   112  		}
   113  
   114  		log.Tracef("External score %v given to node %x", score, nID[:])
   115  
   116  		// Instead of adding a node with score 0 to the returned set,
   117  		// we just skip it.
   118  		if score == 0 {
   119  			continue
   120  		}
   121  
   122  		candidates[nID] = &NodeScore{
   123  			NodeID: nID,
   124  			Score:  score,
   125  		}
   126  	}
   127  
   128  	return candidates, nil
   129  }