diff --git a/lnrpc/graphrpc/graph.pb.go b/lnrpc/graphrpc/graph.pb.go index 6026106060..282f6ecc88 100644 --- a/lnrpc/graphrpc/graph.pb.go +++ b/lnrpc/graphrpc/graph.pb.go @@ -114,9 +114,9 @@ type BetweennessCentrality struct { // The public key of the node in question. Node []byte `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` // The normalized betweenness centrality of the node. - Normalized float32 `protobuf:"fixed32,2,opt,name=normalized,proto3" json:"normalized,omitempty"` + Normalized float64 `protobuf:"fixed64,2,opt,name=normalized,proto3" json:"normalized,omitempty"` // The non-normalized betweenness centrality of the node. - NonNormalized float32 `protobuf:"fixed32,3,opt,name=non_normalized,json=nonNormalized,proto3" json:"non_normalized,omitempty"` + NonNormalized float64 `protobuf:"fixed64,3,opt,name=non_normalized,json=nonNormalized,proto3" json:"non_normalized,omitempty"` } func (x *BetweennessCentrality) Reset() { @@ -158,14 +158,14 @@ func (x *BetweennessCentrality) GetNode() []byte { return nil } -func (x *BetweennessCentrality) GetNormalized() float32 { +func (x *BetweennessCentrality) GetNormalized() float64 { if x != nil { return x.Normalized } return 0 } -func (x *BetweennessCentrality) GetNonNormalized() float32 { +func (x *BetweennessCentrality) GetNonNormalized() float64 { if x != nil { return x.NonNormalized } @@ -430,9 +430,9 @@ var file_graphrpc_graph_proto_rawDesc = []byte{ 0x6e, 0x6e, 0x65, 0x73, 0x73, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x72, 0x6d, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0d, 0x6e, 0x6f, 0x6e, + 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0d, 0x6e, 0x6f, 0x6e, 0x4e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x22, 0x53, 0x0a, 0x11, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x41, 0x64, 0x64, 0x72, 0x73, 0x52, 0x65, 0x71, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x75, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, diff --git a/lnrpc/graphrpc/graph.proto b/lnrpc/graphrpc/graph.proto index 216411bd80..2970c57b48 100644 --- a/lnrpc/graphrpc/graph.proto +++ b/lnrpc/graphrpc/graph.proto @@ -43,10 +43,10 @@ message BetweennessCentrality { bytes node = 1; // The normalized betweenness centrality of the node. - float normalized = 2; + double normalized = 2; // The non-normalized betweenness centrality of the node. - float non_normalized = 3; + double non_normalized = 3; } message BootstrapAddrsReq { diff --git a/lnrpc/graphrpc/graph.swagger.json b/lnrpc/graphrpc/graph.swagger.json index 30e75c4bdb..7f3ac7e903 100644 --- a/lnrpc/graphrpc/graph.swagger.json +++ b/lnrpc/graphrpc/graph.swagger.json @@ -131,12 +131,12 @@ }, "normalized": { "type": "number", - "format": "float", + "format": "double", "description": "The normalized betweenness centrality of the node." }, "non_normalized": { "type": "number", - "format": "float", + "format": "double", "description": "The non-normalized betweenness centrality of the node." } } diff --git a/lnrpc/graphrpc/graph_client.go b/lnrpc/graphrpc/graph_client.go index eb161943cf..e9c868453d 100644 --- a/lnrpc/graphrpc/graph_client.go +++ b/lnrpc/graphrpc/graph_client.go @@ -25,7 +25,8 @@ import ( ) // Client is a wrapper that implements the sources.GraphSource interface using a -// grpc connection to a grpc GraphClient client. +// grpc connection which it uses to communicate with a grpc GraphClient client +// along with an lnrpc.LightningClient. type Client struct { // graphConn is a grpc client that implements the GraphClient service. graphConn GraphClient @@ -65,8 +66,8 @@ func (r *Client) BetweennessCentrality(ctx context.Context) ( var id autopilot.NodeID copy(id[:], node.Node) centrality[id] = &models.BetweennessCentrality{ - Normalized: float64(node.Normalized), - NonNormalized: float64(node.NonNormalized), + Normalized: node.Normalized, + NonNormalized: node.NonNormalized, } } @@ -722,7 +723,7 @@ func unmarshalChannelInfo(info *lnrpc.ChannelEdge) (*models.ChannelEdgeInfo, ) if info.Node1Policy != nil { policy1, err = unmarshalPolicy( - info.ChannelId, info.Node1Policy, true, + info.ChannelId, info.Node1Policy, true, info.Node2Pub, ) if err != nil { return nil, nil, nil, err @@ -730,7 +731,7 @@ func unmarshalChannelInfo(info *lnrpc.ChannelEdge) (*models.ChannelEdgeInfo, } if info.Node2Policy != nil { policy2, err = unmarshalPolicy( - info.ChannelId, info.Node2Policy, false, + info.ChannelId, info.Node2Policy, false, info.Node1Pub, ) if err != nil { return nil, nil, nil, err @@ -741,7 +742,7 @@ func unmarshalChannelInfo(info *lnrpc.ChannelEdge) (*models.ChannelEdgeInfo, } func unmarshalPolicy(channelID uint64, rpcPolicy *lnrpc.RoutingPolicy, - node1 bool) (*models.ChannelEdgePolicy, error) { + node1 bool, toNodeStr string) (*models.ChannelEdgePolicy, error) { var chanFlags lnwire.ChanUpdateChanFlags if !node1 { @@ -756,6 +757,15 @@ func unmarshalPolicy(channelID uint64, rpcPolicy *lnrpc.RoutingPolicy, return nil, err } + toNodeB, err := hex.DecodeString(toNodeStr) + if err != nil { + return nil, err + } + toNode, err := route.NewVertexFromBytes(toNodeB) + if err != nil { + return nil, err + } + return &models.ChannelEdgePolicy{ ChannelID: channelID, TimeLockDelta: uint16(rpcPolicy.TimeLockDelta), @@ -773,6 +783,7 @@ func unmarshalPolicy(channelID uint64, rpcPolicy *lnrpc.RoutingPolicy, ), LastUpdate: time.Unix(int64(rpcPolicy.LastUpdate), 0), ChannelFlags: chanFlags, + ToNode: toNode, ExtraOpaqueData: extra, }, nil } diff --git a/lnrpc/graphrpc/graph_client_test.go b/lnrpc/graphrpc/graph_client_test.go new file mode 100644 index 0000000000..f3ad2710fd --- /dev/null +++ b/lnrpc/graphrpc/graph_client_test.go @@ -0,0 +1,906 @@ +package graphrpc + +import ( + "context" + "encoding/hex" + "fmt" + "image/color" + "net" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/autopilot" + graphdb "github.com/lightningnetwork/lnd/graph/db" + "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tor" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + testChanPoint1 = wire.OutPoint{ + Hash: chainhash.Hash{ + 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0x2d, 0xe7, 0x93, 0xe4, + }, + Index: 1, + } + + testChanPoint2 = wire.OutPoint{ + Hash: chainhash.Hash{ + 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0x2d, 0xe7, 0x93, 0xe4, + }, + Index: 2, + } +) + +type testKit struct { + lnConn *lnrpc.MockLNConn + graphConn *MockGraphConn + client *Client +} + +func newTestKit(t *testing.T) *testKit { + var ( + lnConn lnrpc.MockLNConn + graphConn MockGraphConn + ) + t.Cleanup(func() { + lnConn.AssertExpectations(t) + graphConn.AssertExpectations(t) + }) + + return &testKit{ + lnConn: &lnConn, + graphConn: &graphConn, + client: &Client{ + graphConn: &graphConn, + lnConn: &lnConn, + net: &tor.ClearNet{}, + }, + } +} + +func TestGraphClient(t *testing.T) { + t.Parallel() + ctx := context.Background() + + var ( + node1Pub = genPub(t) + node1ID = autopilot.NewNodeID(node1Pub) + node1Hex = hex.EncodeToString(node1Pub.SerializeCompressed()) + + node2Pub = genPub(t) + node2ID = autopilot.NewNodeID(node2Pub) + node2Hex = hex.EncodeToString(node2Pub.SerializeCompressed()) + + node3Pub = genPub(t) + node3ID = autopilot.NewNodeID(node3Pub) + node3Hex = hex.EncodeToString(node3Pub.SerializeCompressed()) + + chanPoint1 = testChanPoint1.String() + chanPoint2 = testChanPoint2.String() + ) + + addr1, err := net.ResolveTCPAddr("tcp", "localhost:34") + require.NoError(t, err) + + addr2, err := net.ResolveTCPAddr("tcp4", "197.0.0.1:5") + require.NoError(t, err) + + t.Run("BetweennessCentrality", func(t *testing.T) { + kit := newTestKit(t) + kit.graphConn.On( + "BetweennessCentrality", mock.Anything, mock.Anything, + mock.Anything, + ).Return(&BetweennessCentralityResp{ + NodeBetweenness: []*BetweennessCentrality{ + { + Node: node1ID[:], + Normalized: 0.1, + NonNormalized: 0.2, + }, + { + Node: node2ID[:], + Normalized: 1.555, + NonNormalized: 3, + }, + }, + }, nil) + + resp, err := kit.client.BetweennessCentrality(ctx) + require.NoError(t, err) + require.Len(t, resp, 2) + require.EqualValues(t, &models.BetweennessCentrality{ + Normalized: 0.1, + NonNormalized: 0.2, + }, resp[node1ID]) + require.EqualValues(t, &models.BetweennessCentrality{ + Normalized: 1.555, + NonNormalized: 3, + }, resp[node2ID]) + }) + + t.Run("GraphBootstrapper", func(t *testing.T) { + kit := newTestKit(t) + kit.graphConn.On( + "BootstrapperName", mock.Anything, mock.Anything, + mock.Anything, + ).Return(&BoostrapperNameResp{ + Name: "bootstrapper_name", + }, nil) + + kit.graphConn.On( + "BootstrapAddrs", mock.Anything, &BootstrapAddrsReq{ + NumAddrs: 5, + IgnoreNodes: [][]byte{ + node2ID[:], + }, + }, mock.Anything, + ).Return(&BootstrapAddrsResp{ + Addresses: map[string]*Addresses{ + node1Hex: { + Addresses: []*lnrpc.NodeAddress{ + { + Network: "tcp", + Addr: "localhost:34", + }, + { + Network: "tcp4", + Addr: "197.0.0.1:5", + }, + }, + }, + }, + }, nil) + + b, err := kit.client.GraphBootstrapper(ctx) + require.NoError(t, err) + + name := b.Name(ctx) + require.Equal(t, "bootstrapper_name", name) + + addrs, err := b.SampleNodeAddrs( + ctx, 5, map[autopilot.NodeID]struct{}{ + node2ID: {}, + }, + ) + require.NoError(t, err) + require.EqualValues(t, []*lnwire.NetAddress{ + { + IdentityKey: node1Pub, + Address: addr1, + }, + { + IdentityKey: node1Pub, + Address: addr2, + }, + }, addrs) + }) + + t.Run("NetworkStats", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On( + "GetNetworkInfo", mock.Anything, + &lnrpc.NetworkInfoRequest{ + ExcludeNodes: [][]byte{node1ID[:]}, + ExcludeChans: []uint64{5}, + }, + mock.Anything, + ).Return(&lnrpc.NetworkInfo{ + GraphDiameter: 1, + AvgOutDegree: 2.2, + MaxOutDegree: 3, + NumNodes: 4, + NumChannels: 5, + TotalNetworkCapacity: 6, + AvgChannelSize: 7, + MinChannelSize: 8, + MaxChannelSize: 9, + MedianChannelSizeSat: 10, + NumZombieChans: 11, + }, nil) + + stats, err := kit.client.NetworkStats( + ctx, map[route.Vertex]struct{}{ + route.NewVertex(node1Pub): {}, + }, map[uint64]struct{}{5: {}}) + require.NoError(t, err) + require.Equal(t, &models.NetworkStats{ + Diameter: 1, + MaxChanOut: 3, + NumNodes: 4, + NumChannels: 5, + TotalNetworkCapacity: 6, + MinChanSize: 8, + MaxChanSize: 9, + MedianChanSize: 10, + NumZombies: 11, + }, stats) + }) + + t.Run("ForEachNodeDirectedChannel", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: true, + }, mock.Anything).Return(&lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + Features: map[uint32]*lnrpc.Feature{ + 0: {Name: "feature1"}, + }, + }, + Channels: []*lnrpc.ChannelEdge{ + { + ChannelId: 1234, + ChanPoint: chanPoint1, + Node1Pub: node1Hex, + Node2Pub: node2Hex, + Capacity: 50000, + Node1Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + CustomRecords: map[uint64][]byte{ + 55555: {0, 0, 0, 10, 0, 0, 0, 20}, + }, + }, + }, + { + ChannelId: 5678, + ChanPoint: chanPoint2, + Node1Pub: node3Hex, + Node2Pub: node1Hex, + Capacity: 100000, + Node2Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + }, + }, + }, + }, nil) + + // Collect all the channels passed to the call-back. + var collectedChans []*graphdb.DirectedChannel + + err := kit.client.ForEachNodeDirectedChannel(ctx, nil, + route.NewVertex(node1Pub), + func(channel *graphdb.DirectedChannel) error { + // Set the call back to nil so we can do an + // equals assertion later. + if channel.InPolicy != nil { + channel.InPolicy.ToNodePubKey = nil + } + + collectedChans = append(collectedChans, channel) + return nil + }, + ) + require.NoError(t, err) + + // Verify that the call-back was passed all the expected + // channels. + require.EqualValues(t, []*graphdb.DirectedChannel{ + { + ChannelID: 1234, + IsNode1: true, + OtherNode: route.NewVertex(node2Pub), + Capacity: 50000, + OutPolicySet: true, + InPolicy: nil, + InboundFee: lnwire.Fee{ + BaseFee: 10, + FeeRate: 20, + }, + }, + { + ChannelID: 5678, + IsNode1: false, + OtherNode: route.NewVertex(node3Pub), + Capacity: 100000, + OutPolicySet: false, + InPolicy: &models.CachedEdgePolicy{ + ChannelID: 5678, + TimeLockDelta: 4, + ChannelFlags: lnwire.ChanUpdateDirection, + ToNodeFeatures: lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(0), + map[lnwire.FeatureBit]string{ + 0: "feature1", + }, + ), + }, + }, + }, collectedChans) + }) + + t.Run("FetchNodeFeatures", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return(&lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + PubKey: node1Hex, + Features: map[uint32]*lnrpc.Feature{ + 0: {Name: "feature1"}, + }, + }, + }, nil) + + features, err := kit.client.FetchNodeFeatures( + ctx, nil, route.NewVertex(node1Pub), + ) + require.NoError(t, err) + require.Equal(t, lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(0), + map[lnwire.FeatureBit]string{ + 0: "feature1", + }, + ), features) + }) + + t.Run("ForEachNode", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On( + "DescribeGraph", mock.Anything, + &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}, + mock.Anything, + ).Return(&lnrpc.ChannelGraph{ + Nodes: []*lnrpc.LightningNode{ + { + PubKey: node1Hex, + Alias: "Node1", + Addresses: []*lnrpc.NodeAddress{ + { + Network: "tcp", + Addr: "localhost:34", + }, + }, + LastUpdate: 1, + Features: make(map[uint32]*lnrpc.Feature), + CustomRecords: map[uint64][]byte{ + 1: {1, 3, 4}, + }, + }, + { + PubKey: node2Hex, + }, + }, + }, nil) + + // Collect the nodes passed to the call-back. + var nodes []*models.LightningNode + err := kit.client.ForEachNode( + ctx, func(node *models.LightningNode) error { + + nodes = append(nodes, node) + return nil + }, + ) + require.NoError(t, err) + + // Verify that the call-back was passed all the expected nodes. + require.EqualValues(t, []*models.LightningNode{ + { + PubKeyBytes: node1ID, + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(1, 0), + Addresses: []net.Addr{addr1}, + Color: color.RGBA{}, + Alias: "Node1", + AuthSigBytes: nil, + Features: lnwire.NewFeatureVector( + nil, map[lnwire.FeatureBit]string{}, + ), + ExtraOpaqueData: []byte{1, 3, 1, 3, 4}, + }, + { + PubKeyBytes: node2ID, + HaveNodeAnnouncement: false, + LastUpdate: time.Unix(0, 0), + Features: lnwire.NewFeatureVector( + nil, map[lnwire.FeatureBit]string{}, + ), + Addresses: make([]net.Addr, 0), + }, + }, nodes) + }) + + t.Run("ForEachChannel", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On( + "DescribeGraph", mock.Anything, + &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}, + mock.Anything, + ).Return(&lnrpc.ChannelGraph{ + Edges: []*lnrpc.ChannelEdge{ + { + ChannelId: 1, + ChanPoint: chanPoint1, + Node1Pub: node1Hex, + Node2Pub: node2Hex, + Capacity: 300, + Node1Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + CustomRecords: map[uint64][]byte{ + 55555: {0, 0, 0, 10, 0, 0, 0, 20}, + }, + }, + Announced: false, + }, + { + ChannelId: 2, + ChanPoint: chanPoint2, + Node1Pub: node3Hex, + Node2Pub: node2Hex, + Capacity: 500, + Node2Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + }, + Announced: true, + }, + }, + }, nil) + + type chanInfo struct { + info *models.ChannelEdgeInfo + policy1 *models.ChannelEdgePolicy + policy2 *models.ChannelEdgePolicy + } + + // Collect the channels passed to the call-back. + var chans []*chanInfo + err := kit.client.ForEachChannel( + ctx, func(info *models.ChannelEdgeInfo, + policy *models.ChannelEdgePolicy, + policy2 *models.ChannelEdgePolicy) error { + + chans = append(chans, &chanInfo{ + info: info, + policy1: policy, + policy2: policy2, + }) + + return nil + }, + ) + require.NoError(t, err) + + // Verify that the call-back was passed all the expected + // channels. + require.EqualValues(t, []*chanInfo{ + { + info: &models.ChannelEdgeInfo{ + ChannelID: 1, + NodeKey1Bytes: node1ID, + NodeKey2Bytes: node2ID, + ChannelPoint: testChanPoint1, + Capacity: 300, + }, + policy1: &models.ChannelEdgePolicy{ + ChannelID: 1, + TimeLockDelta: 4, + ToNode: node2ID, + ExtraOpaqueData: []byte{253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20}, + LastUpdate: time.Unix(0, 0), + }, + }, + { + info: &models.ChannelEdgeInfo{ + ChannelID: 2, + NodeKey1Bytes: node3ID, + NodeKey2Bytes: node2ID, + ChannelPoint: testChanPoint2, + Capacity: 500, + AuthProof: &models.ChannelAuthProof{}, + }, + policy2: &models.ChannelEdgePolicy{ + ChannelID: 2, + TimeLockDelta: 4, + ToNode: node3ID, + ChannelFlags: lnwire.ChanUpdateDirection, + LastUpdate: time.Unix(0, 0), + }, + }, + }, chans) + }) + + t.Run("ForEachNodeChannel", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On( + "GetNodeInfo", mock.Anything, + &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: true, + }, + mock.Anything, + ).Return(&lnrpc.NodeInfo{ + Channels: []*lnrpc.ChannelEdge{ + { + ChannelId: 1, + ChanPoint: chanPoint1, + Node1Pub: node1Hex, + Node2Pub: node2Hex, + Capacity: 300, + Node1Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + CustomRecords: map[uint64][]byte{ + 55555: {0, 0, 0, 10, 0, 0, 0, 20}, + }, + }, + Announced: false, + }, + { + ChannelId: 2, + ChanPoint: chanPoint2, + Node1Pub: node3Hex, + Node2Pub: node1Hex, + Capacity: 500, + Node2Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + }, + Announced: true, + }, + }, + }, nil) + + type chanInfo struct { + info *models.ChannelEdgeInfo + policy1 *models.ChannelEdgePolicy + policy2 *models.ChannelEdgePolicy + } + + // Collect the channels passed to the call-back. + var chans []*chanInfo + err := kit.client.ForEachNodeChannel(ctx, route.Vertex(node1ID), + func(info *models.ChannelEdgeInfo, + policy *models.ChannelEdgePolicy, + policy2 *models.ChannelEdgePolicy) error { + + chans = append(chans, &chanInfo{ + info: info, + policy1: policy, + policy2: policy2, + }) + + return nil + }, + ) + require.NoError(t, err) + + // Verify that the call-back was passed all the expected + // channels. + require.EqualValues(t, []*chanInfo{ + { + info: &models.ChannelEdgeInfo{ + ChannelID: 1, + NodeKey1Bytes: node1ID, + NodeKey2Bytes: node2ID, + ChannelPoint: testChanPoint1, + Capacity: 300, + }, + policy1: &models.ChannelEdgePolicy{ + ChannelID: 1, + TimeLockDelta: 4, + ToNode: node2ID, + ExtraOpaqueData: []byte{253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20}, + LastUpdate: time.Unix(0, 0), + }, + }, + { + info: &models.ChannelEdgeInfo{ + ChannelID: 2, + NodeKey1Bytes: node3ID, + NodeKey2Bytes: node1ID, + ChannelPoint: testChanPoint2, + Capacity: 500, + AuthProof: &models.ChannelAuthProof{}, + }, + policy2: &models.ChannelEdgePolicy{ + ChannelID: 2, + TimeLockDelta: 4, + ToNode: node3ID, + ChannelFlags: lnwire.ChanUpdateDirection, + LastUpdate: time.Unix(0, 0), + }, + }, + }, chans) + }) + + t.Run("FetchLightningNode", func(t *testing.T) { + kit := newTestKit(t) + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return(&lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + PubKey: node1Hex, + Alias: "Node1", + Addresses: []*lnrpc.NodeAddress{ + { + Network: "tcp", + Addr: "localhost:34", + }, + }, + LastUpdate: 1, + Features: make(map[uint32]*lnrpc.Feature), + CustomRecords: map[uint64][]byte{ + 1: {1, 3, 4}, + }, + }, + IsPublic: true, + }, nil) + + node, err := kit.client.FetchLightningNode( + ctx, route.NewVertex(node1Pub), + ) + require.NoError(t, err) + require.Equal(t, &models.LightningNode{ + PubKeyBytes: node1ID, + HaveNodeAnnouncement: true, + LastUpdate: time.Unix(1, 0), + Addresses: []net.Addr{ + addr1, + }, + Color: color.RGBA{}, + Alias: "Node1", + Features: lnwire.NewFeatureVector( + nil, map[lnwire.FeatureBit]string{}, + ), + ExtraOpaqueData: []byte{1, 3, 1, 3, 4}, + }, node) + }) + + t.Run("FetchChannelEdgesByOutpoint", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetChanInfo", mock.Anything, &lnrpc.ChanInfoRequest{ + ChanPoint: testChanPoint2.String(), + }, mock.Anything).Return(&lnrpc.ChannelEdge{ + ChannelId: 1, + ChanPoint: chanPoint1, + Node1Pub: node1Hex, + Node2Pub: node2Hex, + Capacity: 300, + Node1Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + CustomRecords: map[uint64][]byte{ + 55555: {0, 0, 0, 10, 0, 0, 0, 20}, + }, + }, + Announced: false, + }, nil) + + edge, policy1, policy2, err := kit.client.FetchChannelEdgesByOutpoint( + ctx, &testChanPoint2, + ) + require.NoError(t, err) + require.Equal(t, &models.ChannelEdgeInfo{ + ChannelID: 1, + NodeKey1Bytes: node1ID, + NodeKey2Bytes: node2ID, + ChannelPoint: testChanPoint1, + Capacity: 300, + }, edge) + require.Equal(t, &models.ChannelEdgePolicy{ + ChannelID: 1, + TimeLockDelta: 4, + ToNode: node2ID, + ExtraOpaqueData: []byte{253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20}, + LastUpdate: time.Unix(0, 0), + }, policy1) + require.Nil(t, policy2) + }) + + t.Run("FetchChannelEdgesByID", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetChanInfo", mock.Anything, &lnrpc.ChanInfoRequest{ + ChanId: 1, + }, mock.Anything).Return(&lnrpc.ChannelEdge{ + ChannelId: 1, + ChanPoint: chanPoint1, + Node1Pub: node1Hex, + Node2Pub: node2Hex, + Capacity: 300, + Node1Policy: &lnrpc.RoutingPolicy{ + TimeLockDelta: 4, + CustomRecords: map[uint64][]byte{ + 55555: {0, 0, 0, 10, 0, 0, 0, 20}, + }, + }, + Announced: false, + }, nil) + + edge, policy1, policy2, err := kit.client.FetchChannelEdgesByID( + ctx, 1, + ) + require.NoError(t, err) + require.Equal(t, &models.ChannelEdgeInfo{ + ChannelID: 1, + NodeKey1Bytes: node1ID, + NodeKey2Bytes: node2ID, + ChannelPoint: testChanPoint1, + Capacity: 300, + }, edge) + require.Equal(t, &models.ChannelEdgePolicy{ + ChannelID: 1, + TimeLockDelta: 4, + ToNode: node2ID, + ExtraOpaqueData: []byte{253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20}, + LastUpdate: time.Unix(0, 0), + }, policy1) + require.Nil(t, policy2) + }) + + t.Run("IsPublicNode", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return(&lnrpc.NodeInfo{ + IsPublic: true, + }, nil) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node2Hex, + IncludeChannels: false, + }, mock.Anything).Return(&lnrpc.NodeInfo{ + IsPublic: false, + }, nil) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node3Hex, + IncludeChannels: false, + }, mock.Anything).Return(nil, fmt.Errorf("other error")) + + isPublic, err := kit.client.IsPublicNode(ctx, node1ID) + require.NoError(t, err) + require.True(t, isPublic) + + isPublic, err = kit.client.IsPublicNode(ctx, node2ID) + require.NoError(t, err) + require.False(t, isPublic) + + _, err = kit.client.IsPublicNode(ctx, node3ID) + require.ErrorContains(t, err, "other error") + }) + + t.Run("AddrsForNode", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return( + &lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + PubKey: node1Hex, + Alias: "Node1", + Addresses: []*lnrpc.NodeAddress{ + { + Network: "tcp", + Addr: "localhost:34", + }, + { + Network: "tcp4", + Addr: "197.0.0.1:5", + }, + }, + }, + }, nil, + ) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node2Hex, + IncludeChannels: false, + }, mock.Anything).Return( + nil, status.Error(codes.NotFound, "not found"), + ) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node3Hex, + IncludeChannels: false, + }, mock.Anything).Return(nil, fmt.Errorf("other error")) + + found, addrs, err := kit.client.AddrsForNode(ctx, node1Pub) + require.NoError(t, err) + require.True(t, found) + require.EqualValues(t, []net.Addr{addr1, addr2}, addrs) + + found, addrs, err = kit.client.AddrsForNode(ctx, node2Pub) + require.NoError(t, err) + require.False(t, found) + require.Nil(t, addrs) + + found, addrs, err = kit.client.AddrsForNode(ctx, node3Pub) + require.Error(t, err) + require.False(t, found) + }) + + t.Run("HasLightningNode", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return( + &lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + PubKey: node1Hex, + Alias: "Node1", + LastUpdate: 1, + }, + }, nil, + ) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node2Hex, + IncludeChannels: false, + }, mock.Anything).Return( + nil, status.Error(codes.NotFound, "not found"), + ) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node3Hex, + IncludeChannels: false, + }, mock.Anything).Return(nil, fmt.Errorf("other error")) + + lastUpdate, found, err := kit.client.HasLightningNode( + ctx, node1ID, + ) + require.NoError(t, err) + require.True(t, found) + require.EqualValues(t, time.Unix(1, 0), lastUpdate) + + lastUpdate, found, err = kit.client.HasLightningNode( + ctx, node2ID, + ) + require.NoError(t, err) + require.False(t, found) + require.EqualValues(t, time.Time{}, lastUpdate) + + _, found, err = kit.client.HasLightningNode(ctx, node3ID) + require.ErrorContains(t, err, "other error") + require.False(t, found) + }) + + t.Run("LookupAlias", func(t *testing.T) { + kit := newTestKit(t) + + kit.lnConn.On("GetNodeInfo", mock.Anything, &lnrpc.NodeInfoRequest{ + PubKey: node1Hex, + IncludeChannels: false, + }, mock.Anything).Return( + &lnrpc.NodeInfo{ + Node: &lnrpc.LightningNode{ + Alias: "Node1", + }, + }, nil, + ) + + alias, err := kit.client.LookupAlias(ctx, node1Pub) + require.NoError(t, err) + require.Equal(t, "Node1", alias) + }) +} + +func genPub(t *testing.T) *btcec.PublicKey { + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + + return priv.PubKey() +} + +func TestTemp(t *testing.T) { +} diff --git a/lnrpc/graphrpc/mocks.go b/lnrpc/graphrpc/mocks.go new file mode 100644 index 0000000000..ba2e61dca7 --- /dev/null +++ b/lnrpc/graphrpc/mocks.go @@ -0,0 +1,52 @@ +package graphrpc + +import ( + "context" + + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +type MockGraphConn struct { + mock.Mock + GraphClient +} + +func (m *MockGraphConn) BootstrapperName(ctx context.Context, + in *BoostrapperNameReq, opts ...grpc.CallOption) (*BoostrapperNameResp, + error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*BoostrapperNameResp), args.Error(1) +} + +func (m *MockGraphConn) BootstrapAddrs(ctx context.Context, + in *BootstrapAddrsReq, opts ...grpc.CallOption) (*BootstrapAddrsResp, + error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*BootstrapAddrsResp), args.Error(1) +} + +func (m *MockGraphConn) BetweennessCentrality(ctx context.Context, + in *BetweennessCentralityReq, opts ...grpc.CallOption) ( + *BetweennessCentralityResp, error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*BetweennessCentralityResp), args.Error(1) +} diff --git a/lnrpc/mocks.go b/lnrpc/mocks.go new file mode 100644 index 0000000000..142ea9bb47 --- /dev/null +++ b/lnrpc/mocks.go @@ -0,0 +1,61 @@ +package lnrpc + +import ( + "context" + + "github.com/stretchr/testify/mock" + "google.golang.org/grpc" +) + +type MockLNConn struct { + mock.Mock + LightningClient +} + +func (m *MockLNConn) GetNetworkInfo(ctx context.Context, in *NetworkInfoRequest, + opts ...grpc.CallOption) (*NetworkInfo, error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*NetworkInfo), args.Error(1) +} + +func (m *MockLNConn) GetNodeInfo(ctx context.Context, in *NodeInfoRequest, + opts ...grpc.CallOption) (*NodeInfo, error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*NodeInfo), args.Error(1) +} + +func (m *MockLNConn) DescribeGraph(ctx context.Context, in *ChannelGraphRequest, + opts ...grpc.CallOption) (*ChannelGraph, error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*ChannelGraph), args.Error(1) +} + +func (m *MockLNConn) GetChanInfo(ctx context.Context, in *ChanInfoRequest, + opts ...grpc.CallOption) (*ChannelEdge, error) { + + args := m.Called(ctx, in, opts) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*ChannelEdge), args.Error(1) +}