forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 1
/
prefattach_test.go
365 lines (300 loc) · 9.37 KB
/
prefattach_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package autopilot
import (
"bytes"
prand "math/rand"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/stretchr/testify/require"
)
type genGraphFunc func(t *testing.T) (testGraph, error)
type testGraph interface {
ChannelGraph
addRandChannel(*btcec.PublicKey, *btcec.PublicKey,
btcutil.Amount) (*ChannelEdge, *ChannelEdge, error)
addRandNode() (*btcec.PublicKey, error)
}
func newDiskChanGraph(t *testing.T) (testGraph, error) {
// Next, create channeldb for the first time.
cdb, err := channeldb.Open(t.TempDir())
if err != nil {
return nil, err
}
t.Cleanup(func() {
require.NoError(t, cdb.Close())
})
return &databaseChannelGraph{
db: cdb.ChannelGraph(),
}, nil
}
var _ testGraph = (*databaseChannelGraph)(nil)
func newMemChanGraph(_ *testing.T) (testGraph, error) {
return newMemChannelGraph(), nil
}
var _ testGraph = (*memChannelGraph)(nil)
var chanGraphs = []struct {
name string
genFunc genGraphFunc
}{
{
name: "disk_graph",
genFunc: newDiskChanGraph,
},
{
name: "mem_graph",
genFunc: newMemChanGraph,
},
}
// TestPrefAttachmentSelectEmptyGraph ensures that when passed an
// empty graph, the NodeSores function always returns a score of 0.
func TestPrefAttachmentSelectEmptyGraph(t *testing.T) {
prefAttach := NewPrefAttachment()
// Create a random public key, which we will query to get a score for.
pub, err := randKey()
require.NoError(t, err, "unable to generate key")
nodes := map[NodeID]struct{}{
NewNodeID(pub): {},
}
for _, chanGraph := range chanGraphs {
chanGraph := chanGraph
graph, err := chanGraph.genFunc(t)
require.NoError(t, err, "unable to create graph")
success := t.Run(chanGraph.name, func(t1 *testing.T) {
// With the necessary state initialized, we'll now
// attempt to get the score for this one node.
const walletFunds = btcutil.SatoshiPerBitcoin
scores, err := prefAttach.NodeScores(
graph, nil, walletFunds, nodes,
)
require.NoError(t1, err)
// Since the graph is empty, we expect the score to be
// 0, giving an empty return map.
require.Empty(t1, scores)
})
if !success {
break
}
}
}
// TestPrefAttachmentSelectTwoVertexes ensures that when passed a
// graph with only two eligible vertexes, then both are given the same score,
// and the funds are appropriately allocated across each peer.
func TestPrefAttachmentSelectTwoVertexes(t *testing.T) {
t.Parallel()
prand.Seed(time.Now().Unix())
const (
maxChanSize = btcutil.Amount(btcutil.SatoshiPerBitcoin)
)
for _, chanGraph := range chanGraphs {
chanGraph := chanGraph
graph, err := chanGraph.genFunc(t)
require.NoError(t, err, "unable to create graph")
success := t.Run(chanGraph.name, func(t1 *testing.T) {
prefAttach := NewPrefAttachment()
// For this set, we'll load the memory graph with two
// nodes, and a random channel connecting them.
const chanCapacity = btcutil.SatoshiPerBitcoin
edge1, edge2, err := graph.addRandChannel(
nil, nil, chanCapacity,
)
require.NoError(t1, err)
// We also add a third, non-connected node to the graph.
_, err = graph.addRandNode()
require.NoError(t1, err)
// Get the score for all nodes found in the graph at
// this point.
nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error {
nodes[n.PubKey()] = struct{}{}
return nil
})
require.NoError(t1, err)
require.Len(t1, nodes, 3)
// With the necessary state initialized, we'll now
// attempt to get our candidates channel score given
// the current state of the graph.
candidates, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes,
)
require.NoError(t1, err)
// We expect two candidates, since one of the nodes
// doesn't have any channels.
require.Len(t1, candidates, 2)
// The candidates should be amongst the two edges
// created above.
for nodeID, candidate := range candidates {
edge1Pub := edge1.Peer.PubKey()
edge2Pub := edge2.Peer.PubKey()
switch {
case bytes.Equal(nodeID[:], edge1Pub[:]):
case bytes.Equal(nodeID[:], edge2Pub[:]):
default:
t1.Fatalf("attached to unknown node: %x",
nodeID[:])
}
// Since each of the nodes has 1 channel, out
// of only one channel in the graph, we expect
// their score to be 1.0.
require.EqualValues(t1, 1, candidate.Score)
}
})
if !success {
break
}
}
}
// TestPrefAttachmentSelectGreedyAllocation tests that if upon
// returning node scores, the NodeScores method will attempt to greedily
// allocate all funds to each vertex (up to the max channel size).
func TestPrefAttachmentSelectGreedyAllocation(t *testing.T) {
t.Parallel()
prand.Seed(time.Now().Unix())
const (
maxChanSize = btcutil.Amount(btcutil.SatoshiPerBitcoin)
)
for _, chanGraph := range chanGraphs {
chanGraph := chanGraph
graph, err := chanGraph.genFunc(t)
require.NoError(t, err, "unable to create graph")
success := t.Run(chanGraph.name, func(t1 *testing.T) {
prefAttach := NewPrefAttachment()
const chanCapacity = btcutil.SatoshiPerBitcoin
// Next, we'll add 3 nodes to the graph, creating an
// "open triangle topology".
edge1, _, err := graph.addRandChannel(
nil, nil, chanCapacity,
)
require.NoError(t1, err)
peerPubBytes := edge1.Peer.PubKey()
peerPub, err := btcec.ParsePubKey(peerPubBytes[:])
require.NoError(t1, err)
_, _, err = graph.addRandChannel(
peerPub, nil, chanCapacity,
)
require.NoError(t1, err)
// At this point, there should be three nodes in the
// graph, with node having two edges.
numNodes := 0
twoChans := false
nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error {
numNodes++
nodes[n.PubKey()] = struct{}{}
numChans := 0
err := n.ForEachChannel(func(c ChannelEdge) error {
numChans++
return nil
})
if err != nil {
return err
}
twoChans = twoChans || (numChans == 2)
return nil
})
require.NoError(t1, err)
require.EqualValues(t1, 3, numNodes)
require.True(t1, twoChans, "have two chans")
// We'll now begin our test, modeling the available
// wallet balance to be 5.5 BTC. We're shooting for a
// 50/50 allocation, and have 3 BTC in channels. As a
// result, the heuristic should try to greedily
// allocate funds to channels.
scores, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes,
)
require.NoError(t1, err)
require.Equal(t1, len(nodes), len(scores))
// The candidates should have a non-zero score, and
// have the max chan size funds recommended channel
// size.
for _, candidate := range scores {
require.NotZero(t1, candidate.Score)
}
// Imagine a few channels are being opened, and there's
// only 0.5 BTC left. That should leave us with channel
// candidates of that size.
const remBalance = btcutil.SatoshiPerBitcoin * 0.5
scores, err = prefAttach.NodeScores(
graph, nil, remBalance, nodes,
)
require.NoError(t1, err)
require.Equal(t1, len(nodes), len(scores))
// Check that the recommended channel sizes are now the
// remaining channel balance.
for _, candidate := range scores {
require.NotZero(t1, candidate.Score)
}
})
if !success {
break
}
}
}
// TestPrefAttachmentSelectSkipNodes ensures that if a node was
// already selected as a channel counterparty, then that node will get a score
// of zero during scoring.
func TestPrefAttachmentSelectSkipNodes(t *testing.T) {
t.Parallel()
prand.Seed(time.Now().Unix())
const (
maxChanSize = btcutil.Amount(btcutil.SatoshiPerBitcoin)
)
for _, chanGraph := range chanGraphs {
chanGraph := chanGraph
graph, err := chanGraph.genFunc(t)
require.NoError(t, err, "unable to create graph")
success := t.Run(chanGraph.name, func(t1 *testing.T) {
prefAttach := NewPrefAttachment()
// Next, we'll create a simple topology of two nodes,
// with a single channel connecting them.
const chanCapacity = btcutil.SatoshiPerBitcoin
_, _, err = graph.addRandChannel(nil, nil, chanCapacity)
require.NoError(t1, err)
nodes := make(map[NodeID]struct{})
err = graph.ForEachNode(func(n Node) error {
nodes[n.PubKey()] = struct{}{}
return nil
})
require.NoError(t1, err)
require.Len(t1, nodes, 2)
// With our graph created, we'll now get the scores for
// all nodes in the graph.
scores, err := prefAttach.NodeScores(
graph, nil, maxChanSize, nodes,
)
require.NoError(t1, err)
require.Equal(t1, len(nodes), len(scores))
// THey should all have a score, and a maxChanSize
// channel size recommendation.
for _, candidate := range scores {
require.NotZero(t1, candidate.Score)
}
// We'll simulate a channel update by adding the nodes
// to our set of channels.
var chans []LocalChannel
for _, candidate := range scores {
chans = append(chans,
LocalChannel{
Node: candidate.NodeID,
},
)
}
// If we attempt to make a call to the NodeScores
// function, without providing any new information,
// then all nodes should have a score of zero, since we
// already got channels to them.
scores, err = prefAttach.NodeScores(
graph, chans, maxChanSize, nodes,
)
require.NoError(t1, err)
// Since all should be given a score of 0, the map
// should be empty.
require.Empty(t1, scores)
})
if !success {
break
}
}
}