prefattach.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package autopilot
  2. import (
  3. prand "math/rand"
  4. "time"
  5. "github.com/btcsuite/btcd/btcec/v2"
  6. "github.com/btcsuite/btcd/btcutil"
  7. )
  8. // minMedianChanSizeFraction determines the minimum size a channel must have to
  9. // count positively when calculating the scores using preferential attachment.
  10. // The minimum channel size is calculated as median/minMedianChanSizeFraction,
  11. // where median is the median channel size of the entire graph.
  12. const minMedianChanSizeFraction = 4
  13. // PrefAttachment is an implementation of the AttachmentHeuristic interface
  14. // that implement a non-linear preferential attachment heuristic. This means
  15. // that given a threshold to allocate to automatic channel establishment, the
  16. // heuristic will attempt to favor connecting to nodes which already have a set
  17. // amount of links, selected by sampling from a power law distribution. The
  18. // attachment is non-linear in that it favors nodes with a higher in-degree but
  19. // less so than regular linear preferential attachment. As a result, this
  20. // creates smaller and less clusters than regular linear preferential
  21. // attachment.
  22. //
  23. // TODO(roasbeef): BA, with k=-3
  24. type PrefAttachment struct {
  25. }
  26. // NewPrefAttachment creates a new instance of a PrefAttachment heuristic.
  27. func NewPrefAttachment() *PrefAttachment {
  28. prand.Seed(time.Now().Unix())
  29. return &PrefAttachment{}
  30. }
  31. // A compile time assertion to ensure PrefAttachment meets the
  32. // AttachmentHeuristic interface.
  33. var _ AttachmentHeuristic = (*PrefAttachment)(nil)
  34. // NodeID is a simple type that holds an EC public key serialized in compressed
  35. // format.
  36. type NodeID [33]byte
  37. // NewNodeID creates a new nodeID from a passed public key.
  38. func NewNodeID(pub *btcec.PublicKey) NodeID {
  39. var n NodeID
  40. copy(n[:], pub.SerializeCompressed())
  41. return n
  42. }
  43. // Name returns the name of this heuristic.
  44. //
  45. // NOTE: This is a part of the AttachmentHeuristic interface.
  46. func (p *PrefAttachment) Name() string {
  47. return "preferential"
  48. }
  49. // NodeScores is a method that given the current channel graph and current set
  50. // of local channels, scores the given nodes according to the preference of
  51. // opening a channel of the given size with them. The returned channel
  52. // candidates maps the NodeID to a NodeScore for the node.
  53. //
  54. // The heuristic employed by this method is one that attempts to promote a
  55. // scale-free network globally, via local attachment preferences for new nodes
  56. // joining the network with an amount of available funds to be allocated to
  57. // channels. Specifically, we consider the degree of each node (and the flow
  58. // in/out of the node available via its open channels) and utilize the
  59. // Barabási–Albert model to drive our recommended attachment heuristics. If
  60. // implemented globally for each new participant, this results in a channel
  61. // graph that is scale-free and follows a power law distribution with k=-3.
  62. //
  63. // To avoid assigning a high score to nodes with a large number of small
  64. // channels, we only count channels at least as large as a given fraction of
  65. // the graph's median channel size.
  66. //
  67. // The returned scores will be in the range [0.0, 1.0], where higher scores are
  68. // given to nodes already having high connectivity in the graph.
  69. //
  70. // NOTE: This is a part of the AttachmentHeuristic interface.
  71. func (p *PrefAttachment) NodeScores(g ChannelGraph, chans []LocalChannel,
  72. chanSize btcutil.Amount, nodes map[NodeID]struct{}) (
  73. map[NodeID]*NodeScore, error) {
  74. // We first run though the graph once in order to find the median
  75. // channel size.
  76. var (
  77. allChans []btcutil.Amount
  78. seenChans = make(map[uint64]struct{})
  79. )
  80. if err := g.ForEachNode(func(n Node) error {
  81. err := n.ForEachChannel(func(e ChannelEdge) error {
  82. if _, ok := seenChans[e.ChanID.ToUint64()]; ok {
  83. return nil
  84. }
  85. seenChans[e.ChanID.ToUint64()] = struct{}{}
  86. allChans = append(allChans, e.Capacity)
  87. return nil
  88. })
  89. if err != nil {
  90. return err
  91. }
  92. return nil
  93. }); err != nil {
  94. return nil, err
  95. }
  96. medianChanSize := Median(allChans)
  97. log.Tracef("Found channel median %v for preferential score heuristic",
  98. medianChanSize)
  99. // Count the number of large-ish channels for each particular node in
  100. // the graph.
  101. var maxChans int
  102. nodeChanNum := make(map[NodeID]int)
  103. if err := g.ForEachNode(func(n Node) error {
  104. var nodeChans int
  105. err := n.ForEachChannel(func(e ChannelEdge) error {
  106. // Since connecting to nodes with a lot of small
  107. // channels actually worsens our connectivity in the
  108. // graph (we will potentially waste time trying to use
  109. // these useless channels in path finding), we decrease
  110. // the counter for such channels.
  111. if e.Capacity < medianChanSize/minMedianChanSizeFraction {
  112. nodeChans--
  113. return nil
  114. }
  115. // Larger channels we count.
  116. nodeChans++
  117. return nil
  118. })
  119. if err != nil {
  120. return err
  121. }
  122. // We keep track of the highest-degree node we've seen, as this
  123. // will be given the max score.
  124. if nodeChans > maxChans {
  125. maxChans = nodeChans
  126. }
  127. // If this node is not among our nodes to score, we can return
  128. // early.
  129. nID := NodeID(n.PubKey())
  130. if _, ok := nodes[nID]; !ok {
  131. log.Tracef("Node %x not among nodes to score, "+
  132. "ignoring", nID[:])
  133. return nil
  134. }
  135. // Otherwise we'll record the number of channels.
  136. nodeChanNum[nID] = nodeChans
  137. log.Tracef("Counted %v channels for node %x", nodeChans, nID[:])
  138. return nil
  139. }); err != nil {
  140. return nil, err
  141. }
  142. // If there are no channels in the graph we cannot determine any
  143. // preferences, so we return, indicating all candidates get a score of
  144. // zero.
  145. if maxChans == 0 {
  146. log.Tracef("No channels in the graph")
  147. return nil, nil
  148. }
  149. existingPeers := make(map[NodeID]struct{})
  150. for _, c := range chans {
  151. existingPeers[c.Node] = struct{}{}
  152. }
  153. // For each node in the set of nodes, count their fraction of channels
  154. // in the graph, and use that as the score.
  155. candidates := make(map[NodeID]*NodeScore)
  156. for nID, nodeChans := range nodeChanNum {
  157. // If the node is among or existing channel peers, we don't
  158. // need another channel.
  159. if _, ok := existingPeers[nID]; ok {
  160. log.Tracef("Node %x among existing peers for pref "+
  161. "attach heuristic, giving zero score", nID[:])
  162. continue
  163. }
  164. // If the node had no large channels, we skip it, since it
  165. // would have gotten a zero score anyway.
  166. if nodeChans <= 0 {
  167. log.Tracef("Skipping node %x with channel count %v",
  168. nID[:], nodeChans)
  169. continue
  170. }
  171. // Otherwise we score the node according to its fraction of
  172. // channels in the graph, scaled such that the highest-degree
  173. // node will be given a score of 1.0.
  174. score := float64(nodeChans) / float64(maxChans)
  175. log.Tracef("Giving node %x a pref attach score of %v",
  176. nID[:], score)
  177. candidates[nID] = &NodeScore{
  178. NodeID: nID,
  179. Score: score,
  180. }
  181. }
  182. return candidates, nil
  183. }