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

lightningnetwork / lnd / 16777740336

06 Aug 2025 01:04PM UTC coverage: 54.85% (-12.1%) from 66.954%
16777740336

Pull #10135

github

web-flow
Merge 429aa830c into e512770f1
Pull Request #10135: docs: move v0.19.3 items to correct file

108702 of 198181 relevant lines covered (54.85%)

22045.97 hits per line

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

89.74
/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 {
18✔
34
        prand.Seed(time.Now().Unix())
18✔
35
        return &PrefAttachment{}
18✔
36
}
18✔
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 {
1,584✔
48
        var n NodeID
1,584✔
49
        copy(n[:], pub.SerializeCompressed())
1,584✔
50
        return n
1,584✔
51
}
1,584✔
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 {
11✔
57
        return "preferential"
11✔
58
}
11✔
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) {
12✔
85

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

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

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

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

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

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

28✔
136
                                nodeChans--
28✔
137
                                return nil
28✔
138
                        }
28✔
139

×
140
                        // Larger channels we count.
×
141
                        nodeChans++
×
142
                        return nil
×
143
                })
144
                if err != nil {
145
                        return err
146
                }
28✔
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
40✔
152
                }
14✔
153

14✔
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 {
26✔
158
                        log.Tracef("Node %x not among nodes to score, "+
26✔
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[:])
26✔
166

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

175
        // If there are no channels in the graph we cannot determine any
12✔
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

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

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

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

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

2✔
209
                // Otherwise we score the node according to its fraction of
2✔
210
                // channels in the graph, scaled such that the highest-degree
2✔
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{
20✔
217
                        NodeID: nID,
20✔
218
                        Score:  score,
20✔
219
                }
20✔
220
        }
20✔
221

20✔
222
        return candidates, nil
20✔
223
}
20✔
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