Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add modified greedy topK centrality heuristic to autopilot #4384

Merged
merged 5 commits into from Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 1 addition & 12 deletions autopilot/agent.go
Expand Up @@ -657,17 +657,6 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
log.Tracef("Creating attachment directive for chosen node %x",
nID[:])

// Add addresses to the candidates.
bhandras marked this conversation as resolved.
Show resolved Hide resolved
addrs := addresses[nID]

// If the node has no known addresses, we cannot connect to it,
// so we'll skip it.
if len(addrs) == 0 {
log.Tracef("Skipping scored node %x with no addresses",
nID[:])
continue
}

// Track the available funds we have left.
if availableFunds < chanSize {
chanSize = availableFunds
Expand All @@ -685,7 +674,7 @@ func (a *Agent) openChans(availableFunds btcutil.Amount, numChans uint32,
chanCandidates[nID] = &AttachmentDirective{
NodeID: nID,
ChanAmt: chanSize,
Addrs: addrs,
Addrs: addresses[nID],
}
}

Expand Down
172 changes: 59 additions & 113 deletions autopilot/betweenness_centrality_test.go
Expand Up @@ -4,8 +4,7 @@ import (
"fmt"
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/stretchr/testify/require"
)

func TestBetweennessCentralityMetricConstruction(t *testing.T) {
Expand All @@ -14,173 +13,120 @@ func TestBetweennessCentralityMetricConstruction(t *testing.T) {

for _, workers := range failing {
m, err := NewBetweennessCentralityMetric(workers)
if m != nil || err == nil {
t.Fatalf("construction must fail with <= 0 workers")
}
require.Error(
t, err, "construction must fail with <= 0 workers",
)
require.Nil(t, m)
}

for _, workers := range ok {
m, err := NewBetweennessCentralityMetric(workers)
if m == nil || err != nil {
t.Fatalf("construction must succeed with >= 1 workers")
}
require.NoError(
t, err, "construction must succeed with >= 1 workers",
)
require.NotNil(t, m)
}
}

// Tests that empty graph results in empty centrality result.
func TestBetweennessCentralityEmptyGraph(t *testing.T) {
centralityMetric, err := NewBetweennessCentralityMetric(1)
if err != nil {
t.Fatalf("construction must succeed with positive number of workers")
}
require.NoError(
t, err,
"construction must succeed with positive number of workers",
)

for _, chanGraph := range chanGraphs {
graph, cleanup, err := chanGraph.genFunc()
success := t.Run(chanGraph.name, func(t1 *testing.T) {
if err != nil {
t1.Fatalf("unable to create graph: %v", err)
}
require.NoError(t, err, "unable to create graph")

if cleanup != nil {
defer cleanup()
}

if err := centralityMetric.Refresh(graph); err != nil {
t.Fatalf("unexpected failure during metric refresh: %v", err)
}
err := centralityMetric.Refresh(graph)
require.NoError(t, err)

centrality := centralityMetric.GetMetric(false)
if len(centrality) > 0 {
t.Fatalf("expected empty metric, got: %v", len(centrality))
}
require.Equal(t, 0, len(centrality))

centrality = centralityMetric.GetMetric(true)
if len(centrality) > 0 {
t.Fatalf("expected empty metric, got: %v", len(centrality))
}

require.Equal(t, 0, len(centrality))
})
if !success {
break
}
}
}

// testGraphDesc is a helper type to describe a test graph.
type testGraphDesc struct {
nodes int
edges map[int][]int
}

// buildTestGraph builds a test graph from a passed graph desriptor.
func buildTestGraph(t *testing.T,
graph testGraph, desc testGraphDesc) map[int]*btcec.PublicKey {

nodes := make(map[int]*btcec.PublicKey)

for i := 0; i < desc.nodes; i++ {
key, err := graph.addRandNode()
if err != nil {
t.Fatalf("cannot create random node")
}

nodes[i] = key
}

const chanCapacity = btcutil.SatoshiPerBitcoin
for u, neighbors := range desc.edges {
for _, v := range neighbors {
_, _, err := graph.addRandChannel(nodes[u], nodes[v], chanCapacity)
if err != nil {
t.Fatalf("unexpected error adding random channel: %v", err)
}
}
}

return nodes
}

// Test betweenness centrality calculating using an example graph.
func TestBetweennessCentralityWithNonEmptyGraph(t *testing.T) {
graphDesc := testGraphDesc{
nodes: 9,
edges: map[int][]int{
0: {1, 2, 3},
1: {2},
2: {3},
3: {4, 5},
4: {5, 6, 7},
5: {6, 7},
6: {7, 8},
},
}

workers := []int{1, 3, 9, 100}

results := []struct {
tests := []struct {
normalize bool
centrality []float64
}{
{
normalize: true,
centrality: []float64{
0.2, 0.0, 0.2, 1.0, 0.4, 0.4, 7.0 / 15.0, 0.0, 0.0,
},
normalize: true,
centrality: normalizedTestGraphCentrality,
},
{
normalize: false,
centrality: []float64{
3.0, 0.0, 3.0, 15.0, 6.0, 6.0, 7.0, 0.0, 0.0,
},
normalize: false,
centrality: testGraphCentrality,
},
}

for _, numWorkers := range workers {
for _, chanGraph := range chanGraphs {
numWorkers := numWorkers
graph, cleanup, err := chanGraph.genFunc()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
require.NoError(t, err, "unable to create graph")

if cleanup != nil {
defer cleanup()
}

testName := fmt.Sprintf("%v %d workers", chanGraph.name, numWorkers)
testName := fmt.Sprintf(
"%v %d workers", chanGraph.name, numWorkers,
)

success := t.Run(testName, func(t1 *testing.T) {
centralityMetric, err := NewBetweennessCentralityMetric(
metric, err := NewBetweennessCentralityMetric(
numWorkers,
)
if err != nil {
t.Fatalf("construction must succeed with " +
"positive number of workers")
}
require.NoError(
t, err,
"construction must succeed with "+
"positive number of workers",
)

graphNodes := buildTestGraph(t1, graph, graphDesc)
if err := centralityMetric.Refresh(graph); err != nil {
t1.Fatalf("error while calculating betweeness centrality")
}
for _, expected := range results {
expected := expected
centrality := centralityMetric.GetMetric(expected.normalize)
graphNodes := buildTestGraph(
t1, graph, centralityTestGraph,
)

if len(centrality) != graphDesc.nodes {
t.Fatalf("expected %v values, got: %v",
graphDesc.nodes, len(centrality))
}
err = metric.Refresh(graph)
require.NoError(t, err)

for node, nodeCentrality := range expected.centrality {
nodeID := NewNodeID(graphNodes[node])
calculatedCentrality, ok := centrality[nodeID]
if !ok {
t1.Fatalf("no result for node: %x (%v)",
nodeID, node)
}

if nodeCentrality != calculatedCentrality {
t1.Errorf("centrality for node: %v "+
"should be %v, got: %v",
node, nodeCentrality, calculatedCentrality)
}
for _, expected := range tests {
expected := expected
centrality := metric.GetMetric(
expected.normalize,
)

require.Equal(t,
centralityTestGraph.nodes,
len(centrality),
)

for i, c := range expected.centrality {
nodeID := NewNodeID(
graphNodes[i],
)
result, ok := centrality[nodeID]
require.True(t, ok)
require.Equal(t, c, result)
}
}
})
Expand Down
68 changes: 68 additions & 0 deletions autopilot/centrality_testdata_test.go
@@ -0,0 +1,68 @@
package autopilot

import (
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/stretchr/testify/require"
)

// testGraphDesc is a helper type to describe a test graph.
type testGraphDesc struct {
nodes int
edges map[int][]int
}

var centralityTestGraph = testGraphDesc{
nodes: 9,
edges: map[int][]int{
0: {1, 2, 3},
1: {2},
2: {3},
3: {4, 5},
4: {5, 6, 7},
5: {6, 7},
6: {7, 8},
},
}

var testGraphCentrality = []float64{
3.0, 0.0, 3.0, 15.0, 6.0, 6.0, 7.0, 0.0, 0.0,
}

var normalizedTestGraphCentrality = []float64{
0.2, 0.0, 0.2, 1.0, 0.4, 0.4, 7.0 / 15.0, 0.0, 0.0,
}

// buildTestGraph builds a test graph from a passed graph desriptor.
func buildTestGraph(t *testing.T,
graph testGraph, desc testGraphDesc) map[int]*btcec.PublicKey {

nodes := make(map[int]*btcec.PublicKey)

for i := 0; i < desc.nodes; i++ {
key, err := graph.addRandNode()
require.NoError(t, err, "cannot create random node")

nodes[i] = key
}

const chanCapacity = btcutil.SatoshiPerBitcoin
for u, neighbors := range desc.edges {
for _, v := range neighbors {
_, _, err := graph.addRandChannel(
nodes[u], nodes[v], chanCapacity,
)
require.NoError(t, err,
"unexpected error adding random channel",
)
if err != nil {
t.Fatalf("unexpected error adding"+
"random channel: %v", err)
}
}
}

return nodes
}
1 change: 1 addition & 0 deletions autopilot/interface.go
Expand Up @@ -185,6 +185,7 @@ var (
availableHeuristics = []AttachmentHeuristic{
NewPrefAttachment(),
NewExternalScoreAttachment(),
NewTopCentrality(),
carlaKC marked this conversation as resolved.
Show resolved Hide resolved
}

// AvailableHeuristics is a map that holds the name of available
Expand Down