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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

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

6.25
/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.
UNCOV
47
func NewNodeID(pub *btcec.PublicKey) NodeID {
×
UNCOV
48
        var n NodeID
×
UNCOV
49
        copy(n[:], pub.SerializeCompressed())
×
UNCOV
50
        return n
×
UNCOV
51
}
×
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,
UNCOV
84
        nodes map[NodeID]struct{}) (map[NodeID]*NodeScore, error) {
×
UNCOV
85

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

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

UNCOV
107
                return nil
×
108
        }); err != nil {
×
109
                return nil, err
×
110
        }
×
111

UNCOV
112
        medianChanSize := Median(allChans)
×
UNCOV
113
        log.Tracef("Found channel median %v for preferential score heuristic",
×
UNCOV
114
                medianChanSize)
×
UNCOV
115

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

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

×
133
                                nodeChans--
×
134
                                return nil
×
135
                        }
×
136

137
                        // Larger channels we count.
UNCOV
138
                        nodeChans++
×
UNCOV
139
                        return nil
×
140
                })
UNCOV
141
                if err != nil {
×
142
                        return err
×
143
                }
×
144

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

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

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

×
UNCOV
164
                return nil
×
165
        }); err != nil {
×
166
                return nil, err
×
167
        }
×
168

169
        // If there are no channels in the graph we cannot determine any
170
        // preferences, so we return, indicating all candidates get a score of
171
        // zero.
UNCOV
172
        if maxChans == 0 {
×
UNCOV
173
                log.Tracef("No channels in the graph")
×
UNCOV
174
                return nil, nil
×
UNCOV
175
        }
×
176

UNCOV
177
        existingPeers := make(map[NodeID]struct{})
×
UNCOV
178
        for _, c := range chans {
×
UNCOV
179
                existingPeers[c.Node] = struct{}{}
×
UNCOV
180
        }
×
181

182
        // For each node in the set of nodes, count their fraction of channels
183
        // in the graph, and use that as the score.
UNCOV
184
        candidates := make(map[NodeID]*NodeScore)
×
UNCOV
185
        for nID, nodeChans := range nodeChanNum {
×
UNCOV
186

×
UNCOV
187
                // If the node is among or existing channel peers, we don't
×
UNCOV
188
                // need another channel.
×
UNCOV
189
                if _, ok := existingPeers[nID]; ok {
×
UNCOV
190
                        log.Tracef("Node %x among existing peers for pref "+
×
UNCOV
191
                                "attach heuristic, giving zero score", nID[:])
×
UNCOV
192
                        continue
×
193
                }
194

195
                // If the node had no large channels, we skip it, since it
196
                // would have gotten a zero score anyway.
UNCOV
197
                if nodeChans <= 0 {
×
UNCOV
198
                        log.Tracef("Skipping node %x with channel count %v",
×
UNCOV
199
                                nID[:], nodeChans)
×
UNCOV
200
                        continue
×
201
                }
202

203
                // Otherwise we score the node according to its fraction of
204
                // channels in the graph, scaled such that the highest-degree
205
                // node will be given a score of 1.0.
UNCOV
206
                score := float64(nodeChans) / float64(maxChans)
×
UNCOV
207
                log.Tracef("Giving node %x a pref attach score of %v",
×
UNCOV
208
                        nID[:], score)
×
UNCOV
209

×
UNCOV
210
                candidates[nID] = &NodeScore{
×
UNCOV
211
                        NodeID: nID,
×
UNCOV
212
                        Score:  score,
×
UNCOV
213
                }
×
214
        }
215

UNCOV
216
        return candidates, nil
×
217
}
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