• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 16313570092

16 Jul 2025 07:46AM UTC coverage: 57.583% (-9.7%) from 67.321%
16313570092

push

github

web-flow
Merge pull request #9751 from starius/bump-deps

multi: update Go to 1.23.10 and update some packages

98678 of 171367 relevant lines covered (57.58%)

1.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.17
/autopilot/prefattach.go
1
package autopilot
2

3
import (
4
        "context"
5
        prand "math/rand"
6
        "time"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/btcutil"
10
)
11

12
// minMedianChanSizeFraction determines the minimum size a channel must have to
13
// count positively when calculating the scores using preferential attachment.
14
// The minimum channel size is calculated as median/minMedianChanSizeFraction,
15
// where median is the median channel size of the entire graph.
16
const minMedianChanSizeFraction = 4
17

18
// PrefAttachment is an implementation of the AttachmentHeuristic interface
19
// that implement a non-linear preferential attachment heuristic. This means
20
// that given a threshold to allocate to automatic channel establishment, the
21
// heuristic will attempt to favor connecting to nodes which already have a set
22
// amount of links, selected by sampling from a power law distribution. The
23
// attachment is non-linear in that it favors nodes with a higher in-degree but
24
// less so than regular linear preferential attachment. As a result, this
25
// creates smaller and less clusters than regular linear preferential
26
// attachment.
27
//
28
// TODO(roasbeef): BA, with k=-3
29
type PrefAttachment struct {
30
}
31

32
// NewPrefAttachment creates a new instance of a PrefAttachment heuristic.
33
func NewPrefAttachment() *PrefAttachment {
3✔
34
        prand.Seed(time.Now().Unix())
3✔
35
        return &PrefAttachment{}
3✔
36
}
3✔
37

38
// A compile time assertion to ensure PrefAttachment meets the
39
// AttachmentHeuristic interface.
40
var _ AttachmentHeuristic = (*PrefAttachment)(nil)
41

42
// NodeID is a simple type that holds an EC public key serialized in compressed
43
// format.
44
type NodeID [33]byte
45

46
// NewNodeID creates a new nodeID from a passed public key.
47
func NewNodeID(pub *btcec.PublicKey) NodeID {
3✔
48
        var n NodeID
3✔
49
        copy(n[:], pub.SerializeCompressed())
3✔
50
        return n
3✔
51
}
3✔
52

53
// Name returns the name of this heuristic.
54
//
55
// NOTE: This is a part of the AttachmentHeuristic interface.
56
func (p *PrefAttachment) Name() string {
3✔
57
        return "preferential"
3✔
58
}
3✔
59

60
// NodeScores is a method that given the current channel graph and current set
61
// of local channels, scores the given nodes according to the preference of
62
// opening a channel of the given size with them. The returned channel
63
// candidates maps the NodeID to a NodeScore for the node.
64
//
65
// The heuristic employed by this method is one that attempts to promote a
66
// scale-free network globally, via local attachment preferences for new nodes
67
// joining the network with an amount of available funds to be allocated to
68
// channels. Specifically, we consider the degree of each node (and the flow
69
// in/out of the node available via its open channels) and utilize the
70
// Barabási–Albert model to drive our recommended attachment heuristics. If
71
// implemented globally for each new participant, this results in a channel
72
// graph that is scale-free and follows a power law distribution with k=-3.
73
//
74
// To avoid assigning a high score to nodes with a large number of small
75
// channels, we only count channels at least as large as a given fraction of
76
// the graph's median channel size.
77
//
78
// The returned scores will be in the range [0.0, 1.0], where higher scores are
79
// given to nodes already having high connectivity in the graph.
80
//
81
// NOTE: This is a part of the AttachmentHeuristic interface.
82
func (p *PrefAttachment) NodeScores(ctx context.Context, g ChannelGraph,
83
        chans []LocalChannel, chanSize btcutil.Amount,
84
        nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
×
85

×
86
        // We first run though the graph once in order to find the median
×
87
        // channel size.
×
88
        var (
×
89
                allChans  []btcutil.Amount
×
90
                seenChans = make(map[uint64]struct{})
×
91
        )
×
92
        if err := g.ForEachNode(ctx, func(ctx context.Context, n Node) error {
×
93
                err := n.ForEachChannel(ctx, func(_ context.Context,
×
94
                        e ChannelEdge) error {
×
95

×
96
                        if _, ok := seenChans[e.ChanID.ToUint64()]; ok {
×
97
                                return nil
×
98
                        }
×
99
                        seenChans[e.ChanID.ToUint64()] = struct{}{}
×
100
                        allChans = append(allChans, e.Capacity)
×
101
                        return nil
×
102
                })
103
                if err != nil {
×
104
                        return err
×
105
                }
×
106

107
                return nil
×
108
        }, func() {
×
109
                allChans = nil
×
110
                clear(seenChans)
×
111
        }); err != nil {
×
112
                return nil, err
×
113
        }
×
114

115
        medianChanSize := Median(allChans)
×
116
        log.Tracef("Found channel median %v for preferential score heuristic",
×
117
                medianChanSize)
×
118

×
119
        // Count the number of large-ish channels for each particular node in
×
120
        // the graph.
×
121
        var maxChans int
×
122
        nodeChanNum := make(map[NodeID]int)
×
123
        if err := g.ForEachNode(ctx, func(ctx context.Context, n Node) error {
×
124
                var nodeChans int
×
125
                err := n.ForEachChannel(ctx, func(_ context.Context,
×
126
                        e ChannelEdge) error {
×
127

×
128
                        // Since connecting to nodes with a lot of small
×
129
                        // channels actually worsens our connectivity in the
×
130
                        // graph (we will potentially waste time trying to use
×
131
                        // these useless channels in path finding), we decrease
×
132
                        // the counter for such channels.
×
133
                        if e.Capacity <
×
134
                                medianChanSize/minMedianChanSizeFraction {
×
135

×
136
                                nodeChans--
×
137
                                return nil
×
138
                        }
×
139

140
                        // Larger channels we count.
141
                        nodeChans++
×
142
                        return nil
×
143
                })
144
                if err != nil {
×
145
                        return err
×
146
                }
×
147

148
                // We keep track of the highest-degree node we've seen, as this
149
                // will be given the max score.
150
                if nodeChans > maxChans {
×
151
                        maxChans = nodeChans
×
152
                }
×
153

154
                // If this node is not among our nodes to score, we can return
155
                // early.
156
                nID := NodeID(n.PubKey())
×
157
                if _, ok := nodes[nID]; !ok {
×
158
                        log.Tracef("Node %x not among nodes to score, "+
×
159
                                "ignoring", nID[:])
×
160
                        return nil
×
161
                }
×
162

163
                // Otherwise we'll record the number of channels.
164
                nodeChanNum[nID] = nodeChans
×
165
                log.Tracef("Counted %v channels for node %x", nodeChans, nID[:])
×
166

×
167
                return nil
×
168
        }, func() {
×
169
                maxChans = 0
×
170
                clear(nodeChanNum)
×
171
        }); err != nil {
×
172
                return nil, err
×
173
        }
×
174

175
        // If there are no channels in the graph we cannot determine any
176
        // preferences, so we return, indicating all candidates get a score of
177
        // zero.
178
        if maxChans == 0 {
×
179
                log.Tracef("No channels in the graph")
×
180
                return nil, nil
×
181
        }
×
182

183
        existingPeers := make(map[NodeID]struct{})
×
184
        for _, c := range chans {
×
185
                existingPeers[c.Node] = struct{}{}
×
186
        }
×
187

188
        // For each node in the set of nodes, count their fraction of channels
189
        // in the graph, and use that as the score.
190
        candidates := make(map[NodeID]*NodeScore)
×
191
        for nID, nodeChans := range nodeChanNum {
×
192

×
193
                // If the node is among or existing channel peers, we don't
×
194
                // need another channel.
×
195
                if _, ok := existingPeers[nID]; ok {
×
196
                        log.Tracef("Node %x among existing peers for pref "+
×
197
                                "attach heuristic, giving zero score", nID[:])
×
198
                        continue
×
199
                }
200

201
                // If the node had no large channels, we skip it, since it
202
                // would have gotten a zero score anyway.
203
                if nodeChans <= 0 {
×
204
                        log.Tracef("Skipping node %x with channel count %v",
×
205
                                nID[:], nodeChans)
×
206
                        continue
×
207
                }
208

209
                // Otherwise we score the node according to its fraction of
210
                // channels in the graph, scaled such that the highest-degree
211
                // node will be given a score of 1.0.
212
                score := float64(nodeChans) / float64(maxChans)
×
213
                log.Tracef("Giving node %x a pref attach score of %v",
×
214
                        nID[:], score)
×
215

×
216
                candidates[nID] = &NodeScore{
×
217
                        NodeID: nID,
×
218
                        Score:  score,
×
219
                }
×
220
        }
221

222
        return candidates, nil
×
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc