routing: rewrite package to conform to BOLT07 and factor in fees+timelocks

This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.

As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.

Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.

On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.

Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
This commit is contained in:
Olaoluwa Osuntokun 2016-12-26 23:20:26 -06:00
parent b5dd462e13
commit e327ffe954
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
26 changed files with 1810 additions and 2690 deletions

23
routing/errors.go Normal file

@ -0,0 +1,23 @@
package routing
import "errors"
var (
// ErrNoPathFound is returned when a path to the target destination
// does not exist in the graph.
ErrNoPathFound = errors.New("unable to find a path to " +
"destination")
// ErrInsufficientCapacity is returned when a path if found, yet the
// capacity of one of the channels in the path is insufficient to carry
// the payment.
ErrInsufficientCapacity = errors.New("channel graph has " +
"insufficient capacity for the payment")
// ErrMaxHopsExceeded is returned when a candidate path is found, but
// the length of that path exceeds HopLimit.
ErrMaxHopsExceeded = errors.New("potential path has too many hops")
// ErrTargetNotInNetwork is returned when a
ErrTargetNotInNetwork = errors.New("target not found")
)

4
routing/graph.go Normal file

@ -0,0 +1,4 @@
package routing
// TODO(roasbeef): abstract out graph to interface
// * add in-memory version of graph for tests

70
routing/log.go Normal file

@ -0,0 +1,70 @@
package routing
import (
"errors"
"io"
"github.com/btcsuite/btclog"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
DisableLog()
}
// DisableLog disables all library log output. Logging output is disabled
// by default until either UseLogger or SetLogWriter are called.
func DisableLog() {
log = btclog.Disabled
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}
// SetLogWriter uses a specified io.Writer to output package logging info.
// This allows a caller to direct package logging output without needing a
// dependency on seelog. If the caller is also using btclog, UseLogger should
// be used instead.
func SetLogWriter(w io.Writer, level string) error {
if w == nil {
return errors.New("nil writer")
}
lvl, ok := btclog.LogLevelFromString(level)
if !ok {
return errors.New("invalid log level")
}
l, err := btclog.NewLoggerFromWriter(w, lvl)
if err != nil {
return err
}
UseLogger(l)
return nil
}
// logClosure is used to provide a closure over expensive logging operations
// so don't have to be performed when the logging level doesn't warrant it.
type logClosure func() string
// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}
// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

@ -1,470 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package routing
import (
"fmt"
"github.com/lightningnetwork/lnd/routing/rt"
"github.com/lightningnetwork/lnd/routing/rt/graph"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/wire"
)
func channelOperationsFromRT(r *rt.RoutingTable) []lnwire.ChannelOperation {
channels := r.AllChannels()
chOps := make([]lnwire.ChannelOperation, len(channels))
for i:=0; i<len(channels); i++ {
var info *graph.ChannelInfo
if channels[i].Info != nil {
info = channels[i].Info
} else {
info = new(graph.ChannelInfo)
}
chOp := lnwire.ChannelOperation{
NodePubKey1: channels[i].Src.ToByte33(),
NodePubKey2: channels[i].Tgt.ToByte33(),
ChannelId: (*wire.OutPoint)(&channels[i].Id),
Capacity: info.Cpt,
Weight: info.Wgt,
Operation: byte(rt.AddChannelOP),
}
chOps[i] = chOp
}
return chOps
}
func channelOperationsFromDiffBuff(r rt.DifferenceBuffer) []lnwire.ChannelOperation {
chOps := make([]lnwire.ChannelOperation, len(r))
for i:=0; i<len(r); i++ {
var info *graph.ChannelInfo
if r[i].Info != nil {
info = r[i].Info
} else {
info = new(graph.ChannelInfo)
}
chOp := lnwire.ChannelOperation{
NodePubKey1: r[i].Src.ToByte33(),
NodePubKey2: r[i].Tgt.ToByte33(),
ChannelId: (*wire.OutPoint)(&r[i].Id),
Capacity: info.Cpt,
Weight: info.Wgt,
Operation: byte(r[i].Operation),
}
chOps[i] = chOp
}
return chOps
}
func rtFromChannelOperations(chOps []lnwire.ChannelOperation) *rt.RoutingTable {
r := rt.NewRoutingTable()
for i := 0; i<len(chOps); i++{
r.AddChannel(
graph.NewVertex(chOps[i].NodePubKey1[:]),
graph.NewVertex(chOps[i].NodePubKey2[:]),
graph.EdgeID(*chOps[i].ChannelId),
&graph.ChannelInfo{
Cpt: chOps[i].Capacity,
Wgt: chOps[i].Weight,
},
)
}
return r
}
func diffBuffFromChannelOperations(chOps []lnwire.ChannelOperation) *rt.DifferenceBuffer {
d := rt.NewDifferenceBuffer()
for i := 0; i<len(chOps); i++ {
op := rt.NewChannelOperation(
graph.NewVertex(chOps[i].NodePubKey1[:]),
graph.NewVertex(chOps[i].NodePubKey2[:]),
graph.EdgeID(*chOps[i].ChannelId),
&graph.ChannelInfo{
Cpt: chOps[i].Capacity,
Wgt: chOps[i].Weight,
},
rt.OperationType(chOps[i].Operation),
)
*d = append(*d, op)
}
return d
}
// RoutingMessage is a wrapper around lnwire.Message which
// includes sender and receiver.
type RoutingMessage struct {
SenderID graph.Vertex
ReceiverID graph.Vertex
Msg lnwire.Message
}
type addChannelCmd struct {
Id1, Id2 graph.Vertex
TxID graph.EdgeID
Info *graph.ChannelInfo
err chan error
}
type removeChannelCmd struct {
Id1, Id2 graph.Vertex
TxID graph.EdgeID
err chan error
}
type hasChannelCmd struct {
Id1, Id2 graph.Vertex
TxID graph.EdgeID
rez chan bool
err chan error
}
type openChannelCmd struct {
Id graph.Vertex
TxID graph.EdgeID
info *graph.ChannelInfo
err chan error
}
type findPathCmd struct {
Id graph.Vertex
rez chan []graph.Vertex
err chan error
}
type findKShortestPathsCmd struct {
Id graph.Vertex
k int
rez chan [][]graph.Vertex
err chan error
}
type getRTCopyCmd struct {
rez chan *rt.RoutingTable
}
type NeighborState int
const (
StateINIT NeighborState = 0
StateACK NeighborState = 1
StateWAIT NeighborState = 2
)
type neighborDescription struct {
Id graph.Vertex
DiffBuff *rt.DifferenceBuffer
State NeighborState
}
// RoutingConfig contains configuration information for RoutingManager.
type RoutingConfig struct {
// SendMessage is used by the routing manager to send a
// message to a direct neighbor.
SendMessage func([33]byte, lnwire.Message) error
}
// RoutingManager implements routing functionality.
type RoutingManager struct {
// Current node.
Id graph.Vertex
// Neighbors of the current node.
neighbors map[graph.Vertex]*neighborDescription
// Routing table.
rT *rt.RoutingTable
// Configuration parameters.
config *RoutingConfig
// Channel for input messages
chIn chan interface{}
// Closing this channel will stop RoutingManager.
chQuit chan struct{}
// When RoutingManager stops this channel is closed.
ChDone chan struct{}
}
// NewRoutingManager creates new RoutingManager
// with empyt routing table.
func NewRoutingManager(Id graph.Vertex, config *RoutingConfig) *RoutingManager {
return &RoutingManager{
Id: Id,
neighbors: make(map[graph.Vertex]*neighborDescription),
rT: rt.NewRoutingTable(),
config: config,
chIn: make(chan interface{}, 10),
chQuit: make(chan struct{}, 1),
ChDone: make(chan struct{}),
}
}
// Start - start message loop.
func (r *RoutingManager) Start() {
go func() {
out:
for {
// Prioritise quit.
select {
case <-r.chQuit:
break out
default:
}
select {
case msg := <-r.chIn:
r.handleMessage(msg)
case <-r.chQuit:
break out
}
}
close(r.ChDone)
}()
}
// Stop stops RoutingManager.
// Note if some messages were not processed they will be skipped.
func (r *RoutingManager) Stop() {
close(r.chQuit)
}
func (r *RoutingManager) handleMessage(msg interface{}) {
switch msg := msg.(type) {
case *openChannelCmd:
r.handleOpenChannelCmdMessage(msg)
case *addChannelCmd:
r.handleAddChannelCmdMessage(msg)
case *hasChannelCmd:
r.handleHasChannelCmdMessage(msg)
case *removeChannelCmd:
r.handleRemoveChannelCmdMessage(msg)
case *findPathCmd:
r.handleFindPath(msg)
case *findKShortestPathsCmd:
r.handleFindKShortestPaths(msg)
case *getRTCopyCmd:
r.handleGetRTCopy(msg)
case *RoutingMessage:
r.handleRoutingMessage(msg)
default:
fmt.Println("Unknown message type ", msg)
}
}
// notifyNeighbors checks if there are
// pending changes for each neighbor and send them.
// Each neighbor has three states
// StateINIT - initial state. No messages has been send to this neighbor
// StateWAIT - node waits fo acknowledgement.
// StateACK - acknowledgement has been obtained. New updates can be send.
func (r *RoutingManager) notifyNeighbors() {
for _, neighbor := range r.neighbors {
if neighbor.State == StateINIT {
neighbor.DiffBuff.Clear()
msg := &lnwire.NeighborHelloMessage{
Channels: channelOperationsFromRT(r.rT),
}
r.sendRoutingMessage(msg, neighbor.Id)
neighbor.State = StateWAIT
continue
}
if neighbor.State == StateACK && !neighbor.DiffBuff.IsEmpty() {
msg := &lnwire.NeighborUpdMessage{
Updates: channelOperationsFromDiffBuff(*neighbor.DiffBuff),
}
r.sendRoutingMessage(msg, neighbor.Id)
neighbor.DiffBuff.Clear()
neighbor.State = StateWAIT
}
}
}
// AddChannel add channel to routing tables.
func (r *RoutingManager) AddChannel(Id1, Id2 graph.Vertex, TxID graph.EdgeID, info *graph.ChannelInfo) error {
msg := &addChannelCmd{
Id1: Id1,
Id2: Id2,
TxID: TxID,
Info: info,
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.err
}
// HasChannel checks if there are channel in routing table
func (r *RoutingManager) HasChannel(Id1, Id2 graph.Vertex, TxID graph.EdgeID) bool {
msg := &hasChannelCmd{
Id1: Id1,
Id2: Id2,
TxID: TxID,
rez: make(chan bool, 1),
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.rez
}
// RemoveChannel removes channel from routing table
func (r *RoutingManager) RemoveChannel(Id1, Id2 graph.Vertex, TxID graph.EdgeID) error {
msg := &removeChannelCmd{
Id1: Id1,
Id2: Id2,
TxID: TxID,
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.err
}
// OpenChannel is used to open channel from this node to other node.
// It adds node to neighbors and starts routing tables exchange.
func (r *RoutingManager) OpenChannel(Id graph.Vertex, TxID graph.EdgeID, info *graph.ChannelInfo) error {
msg := &openChannelCmd{
Id: Id,
TxID: TxID,
info: info,
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.err
}
// FindPath finds path from this node to some other node
func (r *RoutingManager) FindPath(destId graph.Vertex) ([]graph.Vertex, error) {
msg := &findPathCmd{
Id: destId,
rez: make(chan []graph.Vertex, 1),
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.rez, <-msg.err
}
func (r *RoutingManager) handleFindPath(msg *findPathCmd) {
path, err := r.rT.ShortestPath(r.Id, msg.Id)
msg.rez <- path
msg.err <- err
}
// FindKShortesPaths tries to find k paths from this node to destination.
// If timeouts returns all found paths
func (r *RoutingManager) FindKShortestPaths(destId graph.Vertex, k int) ([][]graph.Vertex, error) {
msg := &findKShortestPathsCmd{
Id: destId,
k: k,
rez: make(chan [][]graph.Vertex, 1),
err: make(chan error, 1),
}
r.chIn <- msg
return <-msg.rez, <-msg.err
}
// Find k-shortest path.
func (r *RoutingManager) handleFindKShortestPaths(msg *findKShortestPathsCmd) {
paths, err := r.rT.KShortestPaths(r.Id, msg.Id, msg.k)
msg.rez <- paths
msg.err <- err
}
func (r *RoutingManager) handleGetRTCopy(msg *getRTCopyCmd) {
msg.rez <- r.rT.Copy()
}
// GetRTCopy - returns copy of current node routing table.
// Note: difference buffers are not copied.
func (r *RoutingManager) GetRTCopy() *rt.RoutingTable {
msg := &getRTCopyCmd{
rez: make(chan *rt.RoutingTable, 1),
}
r.chIn <- msg
return <-msg.rez
}
func (r *RoutingManager) handleOpenChannelCmdMessage(msg *openChannelCmd) {
// TODO: validate that channel do not exist
r.rT.AddChannel(r.Id, msg.Id, msg.TxID, msg.info)
// TODO(mkl): what to do if neighbot already exists.
r.neighbors[msg.Id] = &neighborDescription{
Id: msg.Id,
DiffBuff: r.rT.NewDiffBuff(),
State: StateINIT,
}
r.notifyNeighbors()
msg.err <- nil
}
func (r *RoutingManager) handleAddChannelCmdMessage(msg *addChannelCmd) {
r.rT.AddChannel(msg.Id1, msg.Id2, msg.TxID, msg.Info)
msg.err <- nil
}
func (r *RoutingManager) handleHasChannelCmdMessage(msg *hasChannelCmd) {
msg.rez <- r.rT.HasChannel(msg.Id1, msg.Id2, msg.TxID)
msg.err <- nil
}
func (r *RoutingManager) handleRemoveChannelCmdMessage(msg *removeChannelCmd) {
r.rT.RemoveChannel(msg.Id1, msg.Id2, msg.TxID)
r.notifyNeighbors()
msg.err <- nil
}
func (r *RoutingManager) handleNeighborHelloMessage(msg *lnwire.NeighborHelloMessage, senderID graph.Vertex) {
// Sometimes we can obtain NeighborHello message from node that is
// not our neighbor yet. Because channel creation workflow
// end in different times for nodes.
t := rtFromChannelOperations(msg.Channels)
r.rT.AddTable(t)
r.sendRoutingMessage(&lnwire.NeighborAckMessage{}, senderID)
r.notifyNeighbors()
}
func (r *RoutingManager) handleNeighborUpdMessage(msg *lnwire.NeighborUpdMessage, senderID graph.Vertex) {
if _, ok := r.neighbors[senderID]; ok {
diffBuff := diffBuffFromChannelOperations(msg.Updates)
r.rT.ApplyDiffBuff(diffBuff)
r.sendRoutingMessage(&lnwire.NeighborAckMessage{}, senderID)
r.notifyNeighbors()
}
}
func (r *RoutingManager) handleNeighborRstMessage(msg *lnwire.NeighborRstMessage, senderID graph.Vertex) {
if _, ok := r.neighbors[senderID]; ok {
r.neighbors[senderID].State = StateINIT
r.notifyNeighbors()
}
}
func (r *RoutingManager) handleNeighborAckMessage(msg *lnwire.NeighborAckMessage, senderID graph.Vertex) {
if _, ok := r.neighbors[senderID]; ok && r.neighbors[senderID].State == StateWAIT {
r.neighbors[senderID].State = StateACK
// In case there are new updates for node which
// appears between sending NeighborUpd and NeighborAck
r.notifyNeighbors()
}
}
func (r *RoutingManager) handleRoutingMessage(rmsg *RoutingMessage) {
msg := rmsg.Msg
switch msg := msg.(type) {
case *lnwire.NeighborHelloMessage:
r.handleNeighborHelloMessage(msg, rmsg.SenderID)
case *lnwire.NeighborUpdMessage:
r.handleNeighborUpdMessage(msg, rmsg.SenderID)
case *lnwire.NeighborRstMessage:
r.handleNeighborRstMessage(msg, rmsg.SenderID)
case *lnwire.NeighborAckMessage:
r.handleNeighborAckMessage(msg, rmsg.SenderID)
default:
fmt.Printf("Unknown message type %T\n inside RoutingMessage", msg)
}
}
func (r *RoutingManager) sendRoutingMessage(msg lnwire.Message, receiverId graph.Vertex) {
r.config.SendMessage(receiverId.ToByte33(), msg)
}
func (r *RoutingManager) ReceiveRoutingMessage(msg lnwire.Message, senderID graph.Vertex) {
r.chIn <- &RoutingMessage{
SenderID: senderID,
ReceiverID: r.Id,
Msg: msg,
}
}

@ -1,239 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package routing
import (
"fmt"
"github.com/lightningnetwork/lnd/routing/rt/graph"
"reflect"
"testing"
"time"
)
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func vertexFromInt(x int) graph.Vertex {
s := fmt.Sprintf("%v", x)
return graph.NewVertex([]byte(s))
}
func edgeIdFromString(s string) graph.EdgeID {
e := graph.EdgeID{}
copy(e.Hash[:], []byte(s))
return e
}
var sampleEdgeId graph.EdgeID = edgeIdFromString("EdgeId")
func createLinearNetwork(n int) (*MockNetwork, []*RoutingManager) {
// Creates linear graph 0->1->2->..->n-1
nodes := make([]*RoutingManager, 0)
net := NewMockNetwork(false)
net.Start()
for i := 0; i < n; i++ {
node := NewRoutingManager(vertexFromInt(i), nil)
nodes = append(nodes, node)
node.Start()
net.Add(node)
}
for i := 0; i < n-1; i++ {
nodes[i].OpenChannel(nodes[i+1].Id, sampleEdgeId, nil)
nodes[i+1].OpenChannel(nodes[i].Id, sampleEdgeId, nil)
}
return net, nodes
}
func createCompleteNetwork(n int) (*MockNetwork, []*RoutingManager) {
nodes := make([]*RoutingManager, 0)
net := NewMockNetwork(false)
net.Start()
for i := 0; i < n; i++ {
node := NewRoutingManager(vertexFromInt(i), nil)
nodes = append(nodes, node)
node.Start()
net.Add(node)
}
for i := 0; i < n-1; i++ {
for j := i + 1; j < n; j++ {
nodes[i].OpenChannel(nodes[j].Id, sampleEdgeId, nil)
nodes[j].OpenChannel(nodes[i].Id, sampleEdgeId, nil)
}
}
return net, nodes
}
func createNetwork(desc [][2]int, idFunc func(int) graph.Vertex) (*MockNetwork, map[int]*RoutingManager, []graph.Edge) {
// Creates network of nodes from graph description
net := NewMockNetwork(false)
net.Start()
// create unique nodes
nodes := make(map[int]*RoutingManager)
for i := 0; i < len(desc); i++ {
for j := 0; j < 2; j++ {
nodeId := desc[i][j]
if _, ok := nodes[nodeId]; !ok {
var id graph.Vertex
if idFunc != nil {
id = idFunc(nodeId)
} else {
id = vertexFromInt(nodeId)
}
node := NewRoutingManager(id, nil)
nodes[nodeId] = node
node.Start()
net.Add(node)
}
}
}
edges := make([]graph.Edge, 0, len(desc))
for i := 0; i < len(desc); i++ {
edgeID := edgeIdFromString(fmt.Sprintf("edge-%v", i))
nodes[desc[i][0]].OpenChannel(nodes[desc[i][1]].Id, edgeID, &graph.ChannelInfo{1, 1})
nodes[desc[i][1]].OpenChannel(nodes[desc[i][0]].Id, edgeID, &graph.ChannelInfo{1, 1})
edges = append(edges, graph.NewEdge(
nodes[desc[i][0]].Id,
nodes[desc[i][1]].Id,
edgeID,
&graph.ChannelInfo{1, 1},
))
}
return net, nodes, edges
}
func TestNeighborsScanLinearGraph(t *testing.T) {
n := 4
net, nodes := createLinearNetwork(n)
time.Sleep(10 * time.Millisecond)
// Each node should know about all channels
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
for k := 0; k < n; k++ {
ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId)
correctAns := abs(j-k) == 1
if ans != correctAns {
t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns)
}
}
}
}
net.Stop()
}
func TestNeighborsScanCompleteGraph(t *testing.T) {
n := 4
net, nodes := createCompleteNetwork(n)
time.Sleep(10 * time.Millisecond)
// Each node should know about all channels
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
for k := 0; k < n; k++ {
ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId)
correctAns := j != k
if ans != correctAns {
t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns)
}
}
}
}
net.Stop()
}
func TestNeighborsRemoveChannel(t *testing.T) {
// Create complete graph, than delete channels to make it linear
n := 4
net, nodes := createCompleteNetwork(n)
time.Sleep(10 * time.Millisecond)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if abs(i-j) != 1 {
nodes[i].RemoveChannel(nodes[i].Id, nodes[j].Id, sampleEdgeId)
}
}
}
time.Sleep(10 * time.Millisecond)
// Each node should know about all channels
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
for k := 0; k < n; k++ {
ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId)
correctAns := abs(j-k) == 1
if ans != correctAns {
t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns)
}
}
}
}
net.Stop()
}
func TestFindPath(t *testing.T) {
// Create linear graph
n := 6
net, nodes := createLinearNetwork(n)
time.Sleep(10 * time.Millisecond) // Each node should know about all channels
path, err := nodes[0].FindPath(nodes[5].Id)
if err != nil {
t.Errorf("err = %v, want %v", err)
}
correctPath := []graph.Vertex{}
for i := 0; i < n; i++ {
correctPath = append(correctPath, nodes[i].Id)
}
if !reflect.DeepEqual(path, correctPath) {
t.Errorf("path = %v, want %v", path, correctPath)
}
// Case when path do not exist
path, err = nodes[0].FindPath(vertexFromInt(7))
if path != nil {
t.Errorf("path = %v, want %v", path, nil)
}
if err != graph.PathNotFoundError {
t.Errorf("err = %v, want %v", err, graph.PathNotFoundError)
}
net.Stop()
}
func TestKShortestPaths(t *testing.T) {
net, nodes, _ := createNetwork([][2]int{
[2]int{0, 1},
[2]int{1, 2},
[2]int{2, 3},
[2]int{1, 3},
[2]int{4, 5},
}, nil)
time.Sleep(10 * time.Millisecond) // Each node should know about all channels
// There was bug in lnd when second search of the same path leads to lncli/lnd freeze
for iter := 1; iter <= 3; iter++ {
paths, err := nodes[0].FindKShortestPaths(nodes[3].Id, 2)
if err != nil {
t.Errorf("err = %v, want %v", err, nil)
}
correctPaths := [][]graph.Vertex{
[]graph.Vertex{vertexFromInt(0), vertexFromInt(1), vertexFromInt(3)},
[]graph.Vertex{vertexFromInt(0), vertexFromInt(1), vertexFromInt(2), vertexFromInt(3)},
}
if !reflect.DeepEqual(paths, correctPaths) {
t.Errorf("on iteration: %v paths = %v, want %v", iter, paths, correctPaths)
}
}
// Case when path do not exist
paths, _ := nodes[0].FindKShortestPaths(vertexFromInt(7), 3)
if len(paths) != 0 {
t.Errorf("path = %v, want %v", paths, []graph.Vertex{})
}
net.Stop()
}

@ -1,83 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package routing
import (
"fmt"
"github.com/lightningnetwork/lnd/routing/rt/graph"
"github.com/lightningnetwork/lnd/lnwire"
)
type MockNetwork struct {
nodes map[graph.Vertex]*RoutingManager
chMsg chan *RoutingMessage
chQuit chan struct{}
printMessages bool
}
func NewMockNetwork(printMessages bool) *MockNetwork {
return &MockNetwork{
nodes: make(map[graph.Vertex]*RoutingManager),
chMsg: make(chan *RoutingMessage),
chQuit: make(chan struct{}),
printMessages: printMessages,
}
}
func (net *MockNetwork) Start() {
go func() {
for {
select {
case msg, ok := <-net.chMsg:
if !ok {
return
}
receiverId := msg.ReceiverID
// TODO: validate ReceiverID
if net.printMessages {
fmt.Println(msg.Msg)
}
if _, ok := net.nodes[receiverId]; ok {
net.nodes[receiverId].ReceiveRoutingMessage(msg.Msg, msg.SenderID)
}
case <-net.chQuit:
return
}
}
}()
}
func (net *MockNetwork) Stop() {
close(net.chQuit)
}
func (net *MockNetwork) Add(r *RoutingManager) {
net.nodes[r.Id] = r
chOut := make(chan *RoutingMessage)
if r.config == nil {
r.config = &RoutingConfig{}
}
r.config.SendMessage = func(receiver [33]byte, msg lnwire.Message) error {
chOut <- &RoutingMessage{
SenderID: r.Id,
ReceiverID: graph.NewVertex(receiver[:]),
Msg: msg,
}
return nil
}
go func() {
for {
select {
case msg, ok := <-chOut:
if !ok {
return
}
net.chMsg <- msg
case <-net.chQuit:
return
}
}
}()
}

@ -1 +1,348 @@
package routing
import (
"math"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcutil"
)
const (
// HopLimit is the maximum number hops that is permissible as a route.
// Any potential paths found that lie above this limit will be rejected
// with an error. This value is computed using the current fixed-size
// packet length of the Sphinx construction.
HopLimit = 20
// infinity is used as a starting distance in our shortest path search.
infinity = math.MaxFloat64
)
// Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the
// first hop in the path. A route is only selected as valid if all the channels
// have sufficient capacity to carry the initial payment amount after fees are
// accounted for.
type Route struct {
// TotalTimeLock is the cumulative (final) time lock across the entire
// route. This is the CLTV value that should be extended to the first
// hop in the route. All other hops will decrement the time-lock as
// advertised, leaving enough time for all hops to wait for or present
// the payment pre-image to complete the payment.
TotalTimeLock uint32
// TotalFees is the sum of the fees paid at each hop within the final
// route. In the case of a one-hop payment, this value will be zero as
// we don't need to pay a fee it ourself.
TotalFees btcutil.Amount
// TotalAmount is the total amount of funds required to complete a
// payment over this route. This value includes the cumulative fees at
// each hop. As a result, the HTLC extended to the first-hop in the
// route will need to have at least this many satoshis, otherwise the
// route will fail at an intermediate node due to an insufficient
// amount of fees.
TotalAmount btcutil.Amount
// Hops contains details concerning the specific forwarding details at
// each hop.
Hops []*Hop
}
// Hop represents the forwarding details at a particular position within the
// final route. This struct houses the values necessary to create the HTLC
// which will travel along this hop, and also encode the per-hop payload
// included within the Sphinx packet.
type Hop struct {
// Channels is the active payment channel that this hop will travel
// along.
Channel *channeldb.ChannelEdge
// TimeLockDelta is the delta that this hop will subtract from the HTLC
// before extending it to the next hop in the route.
TimeLockDelta uint16
// AmtToForward is the amount that this hop will forward to the next
// hop. This value is less than the value that the incoming HTLC
// carries as a fee will be subtracted by the hop.
AmtToForward btcutil.Amount
// Fee is the total fee that this hop will subtract from the incoming
// payment, this difference nets the hop fees for forwarding the
// payment.
Fee btcutil.Amount
}
// computeFee computes the fee to forward an HTLC of `amt` satoshis over the
// passed active payment channel. This value is currently computed as specified
// in BOLT07, but will likely change in the near future.
func computeFee(amt btcutil.Amount, edge *channeldb.ChannelEdge) btcutil.Amount {
return edge.FeeBaseMSat + (amt*edge.FeeProportionalMillionths)/1000000
}
// newRoute returns a fully valid route between the source and target that's
// capable of supporting a payment of `amtToSend` after fees are fully
// computed. IF the route is too long, or the selected path cannot support the
// fully payment including fees, then a non-nil error is returned. prevHop maps
// a vertex to the channel required to get to it.
func newRoute(amtToSend btcutil.Amount, source, target vertex,
prevHop map[vertex]edgeWithPrev) (*Route, error) {
// As an initial sanity check, the potential route is immediately
// invalidate if it spans more than 20 hops. The current Sphinx (onion
// routing) implementation can only encode up to 20 hops as the entire
// packet is fixed size. If this route is more than 20 hops, then it's
// invalid.
if len(prevHop) > HopLimit {
return nil, ErrMaxHopsExceeded
}
// If the potential route if below the max hop limit, then we'll use
// the prevHop map to unravel the path. We end up with a list of edges
// in the reverse direction which we'll use to properly calculate the
// timelock and fee values.
pathEdges := make([]*channeldb.ChannelEdge, 0, len(prevHop))
prev := target
for prev != source { // TODO(roasbeef): assumes no cycles
// Add the current hop to the limit of path edges then walk
// backwards from this hop via the prev pointer for this hop
// within the prevHop map.
pathEdges = append(pathEdges, prevHop[prev].edge)
prev = newVertex(prevHop[prev].prevNode)
}
route := &Route{
Hops: make([]*Hop, len(pathEdges)),
}
// The running amount is the total amount of satoshis required at this
// point in the route. We start this value at the amount we want to
// send to the destination. This value will then get successively
// larger as we compute the fees going backwards.
runningAmt := amtToSend
pathLength := len(pathEdges)
for i, edge := range pathEdges {
// Now we create the hop struct for this point in the route.
// The amount to forward is the running amount, and we compute
// the required fee based on this amount.
nextHop := &Hop{
Channel: edge,
AmtToForward: runningAmt,
Fee: computeFee(runningAmt, edge),
TimeLockDelta: edge.Expiry,
}
edge.Node.PubKey.Curve = nil
// As a sanity check, we ensure that the selected channel has
// enough capacity to forward the required amount which
// includes the fee dictated at each hop.
if nextHop.AmtToForward > nextHop.Channel.Capacity {
return nil, ErrInsufficientCapacity
}
// We don't pay any fees to ourselves on the first-hop channel,
// so we don't tally up the running fee and amount.
if i != len(pathEdges)-1 {
// For a node to forward an HTLC, then following
// inequality most hold true: amt_in - fee >=
// amt_to_forward. Therefore we add the fee this node
// consumes in order to calculate the amount that it
// show be forwarded by the prior node which is the
// next hop in our loop.
runningAmt += nextHop.Fee
// Next we tally the total fees (thus far) in the
// route, and also accumulate the total timelock in the
// route by adding the node's time lock delta which is
// the amount of blocks it'll subtract from the
// incoming time lock.
route.TotalFees += nextHop.Fee
} else {
nextHop.Fee = 0
}
route.TotalTimeLock += uint32(nextHop.TimeLockDelta)
// Finally, as we're currently talking the route backwards, we
// reverse the index in order to place this hop at the proper
// spot in the forward direction of the route.
route.Hops[pathLength-1-i] = nextHop
}
// The total amount required for this route will be the value the
// source extends to the first hop in the route.
route.TotalAmount = runningAmt
return route, nil
}
// vertex is a simple alias for the serialization of a compressed Bitcoin
// public key.
type vertex [33]byte
// newVertex returns a new vertex given a public key.
func newVertex(pub *btcec.PublicKey) vertex {
var v vertex
copy(v[:], pub.SerializeCompressed())
return v
}
// nodeWithDist is a helper struct that couples the distance from the current
// source to a node with a pointer to the node itself.
type nodeWithDist struct {
dist float64
node *channeldb.LightningNode
}
// edgeWithPrev is a helper struct used in path finding that couples an
// directional edge with the node's ID in the opposite direction.
type edgeWithPrev struct {
edge *channeldb.ChannelEdge
prevNode *btcec.PublicKey
}
// edgeWeight computes the weight of an edge. This value is used when searching
// for the shortest path within the channel graph between two nodes. Currently
// this is just 1 + the cltv delta value required at this hop, this value
// should be tuned with experimental and empirical data.
//
// TODO(roasbeef): compute robust weight metric
func edgeWeight(e *channeldb.ChannelEdge) float64 {
return float64(1 + e.Expiry)
}
// findRoute attempts to find a path from the source node within the
// ChannelGraph to the target node that's capable of supporting a payment of
// `amt` value. The current approach is used a multiple pass path finding
// algorithm. First we employ a modified version of Dijkstra's algorithm to
// find a potential set of shortest paths, the distance metric is related to
// the time-lock+fee along the route. Once we have a set of candidate routes,
// we calculate the required fee and time lock values running backwards along
// the route. The route that's selected is the one with the lowest total fee.
//
// TODO(roasbeef): make member, add caching
// * add k-path
func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
amt btcutil.Amount) (*Route, error) {
// First initialize empty list of all the node that we've yet to
// visited.
// TODO(roasbeef): make into incremental fibonacci heap rather than
// loading all into memory.
var unvisited []*channeldb.LightningNode
// For each node/vertex the graph we create an entry in the distance
// map for the node set with a distance of "infinity". We also mark
// add the node to our set of unvisited nodes.
distance := make(map[vertex]nodeWithDist)
if err := graph.ForEachNode(func(node *channeldb.LightningNode) error {
// TODO(roasbeef): with larger graph can just use disk seeks
// with a visited map
distance[newVertex(node.PubKey)] = nodeWithDist{
dist: infinity,
node: node,
}
unvisited = append(unvisited, node)
return nil
}); err != nil {
return nil, err
}
// Next we obtain the source node from the graph, and initialize it
// with a distance of 0. This indicates our starting point in the graph
// traversal.
sourceNode, err := graph.SourceNode()
if err != nil {
return nil, err
}
sourceVertex := newVertex(sourceNode.PubKey)
distance[sourceVertex] = nodeWithDist{
dist: 0,
node: sourceNode,
}
// We'll use this map as a series of "previous" hop pointers. So to get
// to `vertex` we'll take the edge that it's mapped to within `prev`.
prev := make(map[vertex]edgeWithPrev)
for len(unvisited) != 0 {
var bestNode *channeldb.LightningNode
smallestDist := infinity
// First we examine our list of unvisited nodes, for the most
// optimal vertex to examine next.
for i, node := range unvisited {
// The "best" node to visit next is node with the
// smallest distance from the source of all the
// unvisited nodes.
v := newVertex(node.PubKey)
if nodeInfo := distance[v]; nodeInfo.dist < smallestDist {
smallestDist = nodeInfo.dist
bestNode = nodeInfo.node
// Since we're going to visit this node, we can
// remove it from the set of unvisited nodes.
unvisited[i] = unvisited[len(unvisited)-1]
unvisited[len(unvisited)-1] = nil // Avoid GC leak.
unvisited = unvisited[:len(unvisited)-1]
break
}
}
// If we've reached our target, then we're done here and can
// exit the graph traversal early.
if bestNode.PubKey.IsEqual(target) {
break
}
// Now that we've found the next potential step to take we'll
// examine all the outgoing edge (channels) from this node to
// further our graph traversal.
pivot := newVertex(bestNode.PubKey)
err := bestNode.ForEachChannel(nil, func(edge *channeldb.ChannelEdge) error {
// Compute the tentative distance to this new
// channel/edge which is the distance to our current
// pivot node plus the weight of this edge.
tempDist := distance[pivot].dist + edgeWeight(edge)
// If this new tentative distance is better than the
// current best known distance to this node, then we
// record the new better distance, and also populate
// our "next hop" map with this edge.
// TODO(roasbeef): add capacity to relaxation criteria?
// * also add min payment?
v := newVertex(edge.Node.PubKey)
if tempDist < distance[v].dist {
// TODO(roasbeef): unconditionally add for all
// paths
distance[v] = nodeWithDist{
dist: tempDist,
node: edge.Node,
}
prev[v] = edgeWithPrev{
edge: edge,
prevNode: bestNode.PubKey,
}
}
return nil
})
if err != nil {
return nil, err
}
}
// If the target node isn't found in the prev hop map, then a path
// doesn't exist, so we terminate in an error.
if _, ok := prev[newVertex(target)]; !ok {
return nil, ErrNoPathFound
}
// Otherwise, we construct a new route which calculate the relevant
// total fees and proper time lock values for each hop.
targetVerex := newVertex(target)
return newRoute(amt, sourceVertex, targetVerex, prev)
}

395
routing/pathfind_test.go Normal file

@ -0,0 +1,395 @@
package routing
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"io/ioutil"
"net"
"os"
"strings"
"testing"
"time"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
const (
// basicGraphFilePath is the file path for a basic graph used within
// the tests. The basic graph consists of 5 nodes with 5 channels
// connecting them.
basicGraphFilePath = "testdata/basic_graph.json"
)
// testGraph is the struct which coresponds to the JSON format used to encode
// graphs within the files in the testdata directory.
//
// TODO(roasbeef): add test graph auto-generator
type testGraph struct {
Info []string `json:"info"`
Nodes []testNode `json:"nodes"`
Edges []testChan `json:"edges"`
}
// testNode represents a node within the test graph above. We skip certain
// information such as the node's IP address as that information isn't needed
// for our tests.
type testNode struct {
Source bool `json:"source"`
PubKey string `json:"pubkey"`
Alias string `json:"alias"`
}
// testChan represents the JSON version of a payment channel. This struct
// matches the Json that's encoded under the "edges" key within the test graph.
type testChan struct {
Node1 string `json:"node_1"`
Node2 string `json:"node_2"`
ChannelID uint64 `json:"channel_id"`
ChannelPoint string `json:"channel_point"`
Flags uint16 `json:"flags"`
Expiry uint16 `json:"expiry"`
MinHTLC int64 `json:"min_htlc"`
FeeBaseMsat int64 `json:"fee_base_msat"`
FeeRate float64 `json:"fee_rate"`
Capacity int64 `json:"capacity"`
}
// makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing
// purposes. A callback which cleans up the created temporary directories is
// also returned and intended to be executed after the test completes.
func makeTestGraph() (*channeldb.ChannelGraph, func(), error) {
// First, create a temporary directory to be used for the duration of
// this test.
tempDirName, err := ioutil.TempDir("", "channeldb")
if err != nil {
return nil, nil, err
}
// Next, create channeldb for the first time.
cdb, err := channeldb.Open(tempDirName)
if err != nil {
return nil, nil, err
}
cleanUp := func() {
cdb.Close()
os.RemoveAll(tempDirName)
}
return cdb.ChannelGraph(), cleanUp, nil
}
// aliasMap is a map from a node's alias to its public key. This type is
// provided in order to allow easily look up from the human rememberable alias
// to an exact node's public key.
type aliasMap map[string]*btcec.PublicKey
// parseTestGraph returns a fully populated ChannelGraph given a path to a JSON
// file which encodes a test graph.
func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, error) {
graphJson, err := ioutil.ReadFile(path)
if err != nil {
return nil, nil, nil, err
}
// First unmarshal the JSON graph into an instance of the testGraph
// struct. Using the struct tags created above in the struct, the JSON
// will be properly parsed into the struct above.
var g testGraph
if err := json.Unmarshal(graphJson, &g); err != nil {
return nil, nil, nil, err
}
// We'll use this fake address for the IP address of all the nodes in
// our tests. This value isn't needed for path finding so it doesn't
// need to be unique.
testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888")
if err != nil {
return nil, nil, nil, err
}
// Next, create a temporary graph database for usage within the test.
graph, cleanUp, err := makeTestGraph()
if err != nil {
return nil, nil, nil, err
}
aliasMap := make(map[string]*btcec.PublicKey)
var source *channeldb.LightningNode
// First we insert all the nodes within the graph as vertexes.
for _, node := range g.Nodes {
pubBytes, err := hex.DecodeString(node.PubKey)
if err != nil {
return nil, nil, nil, err
}
pub, err := btcec.ParsePubKey(pubBytes, btcec.S256())
if err != nil {
return nil, nil, nil, err
}
dbNode := &channeldb.LightningNode{
LastUpdate: time.Now(),
Address: testAddr,
PubKey: pub,
Alias: node.Alias,
}
// We require all aliases within the graph to be unique for our
// tests.
if _, ok := aliasMap[node.Alias]; ok {
return nil, nil, nil, errors.New("aliases for nodes " +
"must be unique!")
} else {
// If the alias is unique, then add the node to the
// alias map for easy lookup.
aliasMap[node.Alias] = pub
}
// If the node is tagged as the source, then we create a
// pointer to is so we can mark the source in the graph
// properly.
if node.Source {
// If we come across a node that's marked as the
// source, and we've already set the source in a prior
// iteration, then the JSON has an error as only ONE
// node can be the source in the graph.
if source != nil {
return nil, nil, nil, errors.New("JSON is invalid " +
"multiple nodes are tagged as the source")
}
source = dbNode
}
// With the node fully parsed, add it as a vertex within the
// graph.
if err := graph.AddLightningNode(dbNode); err != nil {
return nil, nil, nil, err
}
}
// Set the selected source node
if err := graph.SetSourceNode(source); err != nil {
return nil, nil, nil, err
}
// With all the vertexes inserted, we can now insert the edges into the
// test graph.
for _, edge := range g.Edges {
node1Bytes, err := hex.DecodeString(edge.Node1)
if err != nil {
return nil, nil, nil, err
}
node1Pub, err := btcec.ParsePubKey(node1Bytes, btcec.S256())
if err != nil {
return nil, nil, nil, err
}
node2Bytes, err := hex.DecodeString(edge.Node2)
if err != nil {
return nil, nil, nil, err
}
node2Pub, err := btcec.ParsePubKey(node2Bytes, btcec.S256())
if err != nil {
return nil, nil, nil, err
}
fundingTXID := strings.Split(edge.ChannelPoint, ":")[0]
txidBytes, err := wire.NewShaHashFromStr(fundingTXID)
if err != nil {
return nil, nil, nil, err
}
fundingPoint := wire.OutPoint{
Hash: *txidBytes,
Index: 0,
}
// We first insert the existence of the edge between the two
// nodes.
if err := graph.AddChannelEdge(node1Pub, node2Pub, &fundingPoint,
edge.ChannelID); err != nil {
return nil, nil, nil, err
}
edge := &channeldb.ChannelEdge{
ChannelID: edge.ChannelID,
ChannelPoint: fundingPoint,
LastUpdate: time.Now(),
Expiry: edge.Expiry,
MinHTLC: btcutil.Amount(edge.MinHTLC),
FeeBaseMSat: btcutil.Amount(edge.FeeBaseMsat),
FeeProportionalMillionths: btcutil.Amount(edge.FeeRate),
Capacity: btcutil.Amount(edge.Capacity),
}
// As the graph itself is directed, we need to insert two edges
// into the graph: one from node1->node2 and one from
// node2->node1. A flag of 0 indicates this is the routing
// policy for the first node, and a flag of 1 indicates its the
// information for the second node.
edge.Flags = 0
if err := graph.UpdateEdgeInfo(edge); err != nil {
return nil, nil, nil, err
}
edge.Flags = 1
if err := graph.UpdateEdgeInfo(edge); err != nil {
return nil, nil, nil, err
}
}
return graph, cleanUp, aliasMap, nil
}
func TestBasicGraphPathFinding(t *testing.T) {
graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
// With the test graph loaded, we'll test some basic path finding using
// the pre-generated graph. Consult the testdata/basic_graph.json file
// to follow along with the assumptions we'll use to test the path
// finding.
const paymentAmt = btcutil.Amount(100)
target := aliases["sophon"]
route, err := findRoute(graph, target, paymentAmt)
if err != nil {
t.Fatalf("unable to find route: %v", err)
}
// The length of the route selected should be of exactly length two.
if len(route.Hops) != 2 {
t.Fatalf("route is of incorrect length, expected %v got %v", 2,
len(route.Hops))
}
// As each hop only decrements a single block from the time-lock, the
// total time lock value should be two.
if route.TotalTimeLock != 2 {
t.Fatalf("expected time lock of %v, instead have %v", 2,
route.TotalTimeLock)
}
// The first hop in the path should be an edge from roasbeef to goku.
if !route.Hops[0].Channel.Node.PubKey.IsEqual(aliases["songoku"]) {
t.Fatalf("first hop should be goku, is instead: %v",
route.Hops[0].Channel.Node.Alias)
}
// WE shoul
// The second hop should be from goku to sophon.
if !route.Hops[1].Channel.Node.PubKey.IsEqual(aliases["sophon"]) {
t.Fatalf("second hop should be sophon, is instead: %v",
route.Hops[0].Channel.Node.Alias)
}
// Next, attempt to query for a path to Luo Ji for 100 satoshis, there
// exist two possible paths in the graph, but the shorter (1 hop) path
// should be selected.
target = aliases["luoji"]
route, err = findRoute(graph, target, paymentAmt)
if err != nil {
t.Fatalf("unable to find route: %v", err)
}
// The length of the path should be exactly one hop as it's the
// "shortest" known path in the graph.
if len(route.Hops) != 1 {
t.Fatalf("shortest path not selected, should be of length 1, "+
"is instead: %v", len(route.Hops))
}
// As we have a direct path, the total time lock value should be
// exactly one.
if route.TotalTimeLock != 1 {
t.Fatalf("expected time lock of %v, instead have %v", 1,
route.TotalTimeLock)
}
// Additionally, since this is a single-hop payment, we shouldn't have
// to pay any fees in total, so the total amount should be the payment
// amount.
if route.TotalAmount != paymentAmt {
t.Fatalf("incorrect total amount, expected %v got %v",
paymentAmt, route.TotalAmount)
}
}
func TestNewRoutePathTooLong(t *testing.T) {
// Ensure that potential paths which are over the maximum hop-limit are
// rejected.
var v vertex
fakePath := make(map[vertex]edgeWithPrev)
for i := 0; i < 21; i++ {
copy(v[:], bytes.Repeat([]byte{byte(2 + i)}, 33))
fakePath[v] = edgeWithPrev{}
}
_, err := newRoute(10, v, v, fakePath)
if err != ErrMaxHopsExceeded {
t.Fatalf("path should have been rejected")
}
}
func TestPathNotAvailable(t *testing.T) {
graph, cleanUp, _, err := parseTestGraph(basicGraphFilePath)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
// With the test graph loaded, we'll test that queries for target that
// are either unreachable within the graph, or unknown result in an
// error.
unknownNodeStr := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281536fd7011ec59c882"
unknownNodeBytes, err := hex.DecodeString(unknownNodeStr)
if err != nil {
t.Fatalf("unable to parse bytes: %v", err)
}
unknownNode, err := btcec.ParsePubKey(unknownNodeBytes, btcec.S256())
if err != nil {
t.Fatalf("unable to parse pubkey: %v", err)
}
if _, err := findRoute(graph, unknownNode, 100); err != ErrNoPathFound {
t.Fatalf("path shouldn't have been found: %v", err)
}
}
func TestPathInsufficientCapacity(t *testing.T) {
graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
// Next, test that attempting to find a path in which the current
// channel graph cannot support due to insufficient capacity triggers
// an error.
// To test his we'll attempt to make a payment of 1 BTC, or 100 million
// satoshis. The largest channel in the basic graph is of size 100k
// satoshis, so we shouldn't be able to find a path to sophon even
// though we have a 2-hop link.
target := aliases["sophon"]
const payAmt = btcutil.SatoshiPerBitcoin
_, err = findRoute(graph, target, payAmt)
if err != ErrInsufficientCapacity {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
}
}
func TestPathInsufficientCapacityWithFee(t *testing.T) {
// TODO(roasbeef): encode live graph to json
}

850
routing/router.go Normal file

@ -0,0 +1,850 @@
package routing
import (
"encoding/hex"
"sync"
"sync/atomic"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil"
)
// FeeSchema is the set fee configuration for a Lighting Node on the network.
// Using the coefficients described within he schema, the required fee to
// forward outgoing payments can be derived.
//
// TODO(roasbeef): should be in switch instead?
type FeeSchema struct {
// TODO(rosbeef): all these should be in msat instead
// BaseFee is the base amount that will be chained for ANY payment
// forwarded.
BaseFee btcutil.Amount
// FeeRate is the rate that will be charged for forwarding payments.
// The fee rate has a granularity of 1/1000 th of a mili-satoshi, or a
// millionth of a satoshi.
FeeRate btcutil.Amount
}
// Config defines the configuration for the ChannelRouter. ALL elements within
// the configuration MUST be non-nil for the ChannelRouter to carry out its
// duties.
type Config struct {
// Graph is the channel graph that the ChannelRouter will use to gather
// metrics from and also to carry out path finding queries.
// TODO(roasbeef): make into an interface
Graph *channeldb.ChannelGraph
// Chain is the router's source to the most up-to-date blockchain data.
// All incoming advertised channels will be checked against the chain
// to ensure that the channels advertised are still open.
// TODO(roasbeef): remove after discovery service is in
Chain lnwallet.BlockChainIO
// Notifier is an instance of the ChainNotifier that the router uses to
// received notifications of incoming blocks. With each new incoming
// block found, the router may be able to partially prune the channel
// graph as channels may have been pruned.
// TODO(roasbeef): could possibly just replace this with an epoch
// channel.
Notifier chainntnfs.ChainNotifier
// FeeSchema is the set fee schema that will be announced on to the
// network.
// TODO(roasbeef): should either be in discovery or switch
FeeSchema *FeeSchema
// Broadcast is a function that is used to broadcast a particular set
// of messages to all peers that the daemon is connected to. If
// supplied, the exclude parameter indicates that the target peer should
// be excluded from the broadcast.
Broadcast func(exclude *btcec.PublicKey, msg ...lnwire.Message) error
// SendMessages is a function which allows the ChannelRouter to send a
// set of messages to a particular peer identified by the target public
// key.
SendMessages func(target *btcec.PublicKey, msg ...lnwire.Message) error
// TODO(roasbeef): need a SendToSwitch func
// * possibly lift switch into package?
// *
}
// ChannelRouter is the layer 3 router within the Lightning stack. Below the
// ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain
// itself. The primary role of the ChannelRouter is to respond to queries for
// potential routes that can support a payment amount, and also general graph
// reachability questions. The router will prune the channel graph
// automatically as new blocks are discovered which spend certain known funding
// outpoints, thereby closing their respective channels. Additionally, it's the
// duty of the router to sync up newly connected peers with the latest state of
// the channel graph.
type ChannelRouter struct {
sync.RWMutex
cfg *Config
self *channeldb.LightningNode
// TODO(roasbeef): make LRU, invalidate upon new block connect
shortestPathCache map[[33]byte][]*Route
nodeCache map[[33]byte]*channeldb.LightningNode
edgeCache map[wire.OutPoint]*channeldb.ChannelEdge
newBlocks chan *chainntnfs.BlockEpoch
networkMsgs chan *routingMsg
syncRequests chan *syncRequest
fakeSig *btcec.Signature
started uint32
stopped uint32
quit chan struct{}
wg sync.WaitGroup
}
// New creates a new instance of the ChannelRouter with the specified
// configuration parameters. As part of initialization, if the router detects
// that the channel graph isn't fully in sync with the latest UTXO (since the
// channel graph is a subset of the UTXO set) set, then the router will proceed
// to fully sync to the latest state of the UTXO set.
func New(cfg Config) (*ChannelRouter, error) {
// TODO(roasbeef): remove this place holder after sigs are properly
// stored in the graph.
s := "30450221008ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa" +
"1bf0314f882d70220299105481d63e0f4bc2a88121167221b6700d72a0e" +
"ad154c03be696a292d24ae"
fakeSigHex, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
fakeSig, err := btcec.ParseSignature(fakeSigHex, btcec.S256())
if err != nil {
return nil, err
}
self, err := cfg.Graph.SourceNode()
if err != nil {
return nil, err
}
return &ChannelRouter{
cfg: &cfg,
self: self,
fakeSig: fakeSig,
networkMsgs: make(chan *routingMsg),
syncRequests: make(chan *syncRequest),
quit: make(chan struct{}),
}, nil
}
// Start launches all the goroutines the ChannelRouter requires to carry out
// its duties. If the router has already been started, then this method is a
// noop.
func (r *ChannelRouter) Start() error {
if !atomic.CompareAndSwapUint32(&r.started, 0, 1) {
return nil
}
log.Tracef("Channel Router starting")
// First we register for new notifications of newly discovered blocks.
// We do this immediately so we'll later be able to consume any/all
// blocks which were discovered as we prune the channel graph using a
// snapshot of the chain state.
blockEpochs, err := r.cfg.Notifier.RegisterBlockEpochNtfn()
if err != nil {
return err
}
r.newBlocks = blockEpochs.Epochs
// Before we begin normal operation of the router, we first need to
// synchronize the channel graph to the latest state of the UTXO set.
if err := r.syncGraphWithChain(); err != nil {
return err
}
r.wg.Add(1)
go r.networkHandler()
return nil
}
// Stop signals the ChannelRouter to gracefully halt all routines. This method
// will *block* until all goroutines have excited. If the channel router has
// already stopped then this method will return immediately.
func (r *ChannelRouter) Stop() error {
if !atomic.CompareAndSwapUint32(&r.stopped, 0, 1) {
return nil
}
log.Infof("Channel Router shutting down")
close(r.quit)
r.wg.Wait()
return nil
}
// syncGraphWithChain attempts to synchronize the current channel graph with
// the latest UTXO set state. This process involves pruning from the channel
// graph any channels which have been closed by spending their funding output
// since we've been down.
func (r *ChannelRouter) syncGraphWithChain() error {
// First, we'll need to check to see if we're already in sync with the
// latest state of the UTXO set.
bestHash, bestHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return err
}
pruneHash, pruneHeight, err := r.cfg.Graph.PruneTip()
if err != nil {
switch {
// If the graph has never been pruned, or hasn't fully been
// created yet, then we don't treat this as an explicit error.
case err == channeldb.ErrGraphNeverPruned:
case err == channeldb.ErrGraphNotFound:
default:
return err
}
}
log.Infof("Prune tip for Channel Graph: height=%v, hash=%v", pruneHeight,
pruneHash)
switch {
// If the graph has never been pruned, then we can exit early as this
// entails it's being created for the first time and hasn't seen any
// block or created channels.
case pruneHeight == 0 || pruneHash == nil:
return nil
// If the block hashes and heights match exactly, then we don't need to
// prune the channel graph as we're already fully in sync.
case bestHash.IsEqual(pruneHash) && uint32(bestHeight) == pruneHeight:
return nil
}
log.Infof("Syncing channel graph from height=%v (hash=%v) to height=%v "+
"(hash=%v)", pruneHeight, pruneHash, bestHeight, bestHash)
// If we're not yet caught up, then we'll walk forward in the chain in
// the chain pruning the channel graph with each new block in the chain
// that hasn't yet been consumed by the channel graph.
var numChansClosed uint32
for nextHeight := pruneHeight + 1; nextHeight <= uint32(bestHeight); nextHeight++ {
// Using the next height, fetch the next block to use in our
// incremental graph pruning routine.
nextHash, err := r.cfg.Chain.GetBlockHash(int64(nextHeight))
if err != nil {
return err
}
nextBlock, err := r.cfg.Chain.GetBlock(nextHash)
if err != nil {
return err
}
// We're only interested in all prior outputs that've been
// spent in the block, so collate all the referenced previous
// outpoints within each tx and input.
var spentOutputs []*wire.OutPoint
for _, tx := range nextBlock.Transactions {
for _, txIn := range tx.TxIn {
spentOutputs = append(spentOutputs,
&txIn.PreviousOutPoint)
}
}
// With the spent outputs gathered, attempt to prune the
// channel graph, also passing in the hash+height of the block
// being pruned so the prune tip can be updated.
numClosed, err := r.cfg.Graph.PruneGraph(spentOutputs, nextHash,
nextHeight)
if err != nil {
return err
}
log.Infof("Block %v (height=%v) closed %v channels",
nextHash, nextHeight, numClosed)
numChansClosed += numClosed
}
log.Infof("Graph pruning complete: %v channels we're closed since "+
"height %v", numChansClosed, pruneHeight)
return nil
}
// networkHandler is the primary goroutine for the ChannelRouter. The roles of
// this goroutine include answering queries related to the state of the
// network, syncing up newly connected peers, and also periodically
// broadcasting our latest state to all connected peers.
//
// NOTE: This MUST be run as a goroutine.
func (r *ChannelRouter) networkHandler() {
defer r.wg.Done()
var announcementBatch []lnwire.Message
trickleTimer := time.NewTicker(time.Millisecond * 300)
defer trickleTimer.Stop()
for {
select {
// A new fully validated network message has just arrived. As a
// result we'll modify the channel graph accordingly depending
// on the exact type of the message.
case netMsg := <-r.networkMsgs:
// TODO(roasbeef): this loop would mostly be moved to
// the discovery service
// Process the network announcement to determine if this
// is either a new announcement from our PoV or an
// update to a prior vertex/edge we previously
// accepted.
accepted := r.processNetworkAnnouncement(netMsg.msg)
// If the update was accepted, then add it to our next
// announcement batch to be broadcast once the trickle
// timer ticks gain.
if accepted {
announcementBatch = append(announcementBatch, netMsg.msg)
}
// TODO(roasbeef): remove all unconnected vertexes
// after N blocks pass with no corresponding
// announcements.
// A new block has arrived, so we can prune the channel graph
// of any channels which were closed in the block.
case newBlock, ok := <-r.newBlocks:
// If the channel has been closed, then this indicates
// the daemon is shutting down, so we exit ourselves.
if !ok {
return
}
block, err := r.cfg.Chain.GetBlock(newBlock.Hash)
if err != nil {
log.Errorf("unable to get block: %v", err)
continue
}
log.Infof("Pruning channel graph using block %v (height=%v)",
newBlock.Hash, newBlock.Height)
// We're only interested in all prior outputs that've
// been spent in the block, so collate all the
// referenced previous outpoints within each tx and
// input.
var spentOutputs []*wire.OutPoint
for _, tx := range block.Transactions {
for _, txIn := range tx.TxIn {
spentOutputs = append(spentOutputs,
&txIn.PreviousOutPoint)
}
}
// With the spent outputs gathered, attempt to prune
// the channel graph, also passing in the hash+height
// of the block being pruned so the prune tip can be
// updated.
numClosed, err := r.cfg.Graph.PruneGraph(spentOutputs,
newBlock.Hash, uint32(newBlock.Height))
if err != nil {
log.Errorf("unable to prune routing table: %v", err)
continue
}
log.Infof("Block %v (height=%v) closed %v channels",
newBlock.Hash, newBlock.Height, numClosed)
// The trickle timer has ticked, which indicates we should
// flush to the network the pending batch of new announcements
// we've received since the last trickle tick.
case <-trickleTimer.C:
// If the current announcement batch is nil, then we
// have no further work here.
if len(announcementBatch) == 0 {
continue
}
log.Infof("Broadcasting batch of %v new announcements",
len(announcementBatch))
// If we have new things to announce then broadcast
// then to all our immediately connected peers.
err := r.cfg.Broadcast(nil, announcementBatch...)
if err != nil {
log.Errorf("unable to send batch announcement: %v", err)
continue
}
// If we we're able to broadcast the current batch
// successfully, then we reset the batch for a new
// round of announcements.
announcementBatch = nil
// We've just received a new request to synchronize a peer with
// our latest graph state. This indicates that a peer has just
// connected for the first time, so for now we dump our entire
// graph and allow them to sift through the (subjectively) new
// information on their own.
case syncReq := <-r.syncRequests:
nodePub := syncReq.node.SerializeCompressed()
log.Infof("Synchronizing channel graph with %x", nodePub)
if err := r.syncChannelGraph(syncReq); err != nil {
log.Errorf("unable to sync graph state with %x: %v",
nodePub, err)
}
// The router has been signalled to exit, to we exit our main
// loop so the wait group can be decremented.
case <-r.quit:
return
}
}
}
// processNetworkAnnouncement processes a new network relate authenticated
// channel or node announcement. If the update didn't affect the internal state
// of the draft due to either being out of date, invalid, or redundant, then
// false is returned. Otherwise, true is returned indicating that the caller
// may want to batch this request to be broadcast to immediate peers during th
// next announcement epoch.
func (r *ChannelRouter) processNetworkAnnouncement(msg lnwire.Message) bool {
switch msg := msg.(type) {
// A new node announcement has arrived which either presents a new
// node, or a node updating previously advertised information.
case *lnwire.NodeAnnouncement:
// Before proceeding ensure that we aren't already away of this
// node, and if we are then this is a newer update that we
// known of.
lastUpdate, exists, err := r.cfg.Graph.HasLightningNode(msg.NodeID)
if err != nil {
log.Errorf("Unable to query for the existence of node: %v",
err)
return false
}
// If we've reached this pint then we're aware of th vertex
// being advertised. So we now check if the new message has a
// new time stamp, if not then we won't accept the new data as
// it would override newer data.
msgTimestamp := time.Unix(int64(msg.Timestamp), 0)
if exists && lastUpdate.After(msgTimestamp) ||
lastUpdate.Equal(msgTimestamp) {
log.Debugf("Ignoring outdated announcement for %x",
msg.NodeID.SerializeCompressed())
return false
}
node := &channeldb.LightningNode{
LastUpdate: msgTimestamp,
Address: msg.Address,
PubKey: msg.NodeID,
Alias: msg.Alias.String(),
}
if err = r.cfg.Graph.AddLightningNode(node); err != nil {
log.Errorf("unable to add node %v: %v", msg.NodeID, err)
return false
}
log.Infof("Updated vertex data for node=%x",
msg.NodeID.SerializeCompressed())
// A new channel announcement has arrived, this indicates the
// *creation* of a new channel within the graph. This only advertises
// the existence of a channel and not yet the routing policies in
// either direction of the channel.
case *lnwire.ChannelAnnouncement:
// Prior to processing the announcement we first check if we
// already know of this channel, if so, then we can exit early.
channelID := msg.ChannelID.ToUint64()
_, _, exists, err := r.cfg.Graph.HasChannelEdge(channelID)
if err != nil && err != channeldb.ErrGraphNoEdgesFound {
log.Errorf("unable to check for edge existence: %v", err)
return false
} else if exists {
log.Debugf("Ignoring announcement for known chan_id=%v",
channelID)
return false
}
// Before we can add the channel to the channel graph, we need
// to obtain the full funding outpoint that's encoded within
// the channel ID.
fundingPoint, err := r.fetchChanPoint(&msg.ChannelID)
if err != nil {
log.Errorf("unable to fetch chan point for chan_id=%v: %v",
channelID, err)
return false
}
// Now that we have the funding outpoint of the channel, ensure
// that it hasn't yet been spent. If so, then this channel has
// been closed so we'll ignore it.
if _, err := r.cfg.Chain.GetUtxo(&fundingPoint.Hash,
fundingPoint.Index); err != nil {
log.Errorf("unable to fetch utxo for chan_id=%v: %v",
channelID, err)
return false
}
// TODO(roasbeef): also add capacity here two instead of on the
// directed edges.
err = r.cfg.Graph.AddChannelEdge(msg.FirstNodeID,
msg.SecondNodeID, fundingPoint, channelID)
if err != nil {
log.Errorf("unable to add channel: %v", err)
return false
}
log.Infof("New channel discovered! Link "+
"connects %x and %x with ChannelPoint(%v), chan_id=%v",
msg.FirstNodeID.SerializeCompressed(),
msg.SecondNodeID.SerializeCompressed(),
fundingPoint, channelID)
// A new authenticated channel update has has arrived, this indicates
// that the directional information for an already known channel has
// been updated. All updates are signed and validated before reaching
// us, so we trust the data to be legitimate.
case *lnwire.ChannelUpdateAnnouncement:
chanID := msg.ChannelID.ToUint64()
edge1Timestamp, edge2Timestamp, _, err := r.cfg.Graph.HasChannelEdge(chanID)
if err != nil && err != channeldb.ErrGraphNoEdgesFound {
log.Errorf("unable to check for edge existence: %v", err)
return false
}
// As edges are directional edge node has a unique policy for
// the direction of th edge they control. Therefore we first
// check if we already have the most up to date information for
// that edge. If so, then we can exit early.
updateTimestamp := time.Unix(int64(msg.Timestamp), 0)
switch msg.Flags {
// A flag set of 0 indicates this is an announcement for
// the "first" node in the channel.
case 0:
if edge1Timestamp.After(updateTimestamp) ||
edge1Timestamp.Equal(updateTimestamp) {
log.Debugf("Ignoring announcement (flags=%v) "+
"for known chan_id=%v", msg.Flags,
chanID)
return false
}
// Similarly, a flag set of 1 indicates this is an
// announcement for the "second" node in the channel.
case 1:
if edge2Timestamp.After(updateTimestamp) ||
edge2Timestamp.Equal(updateTimestamp) {
log.Debugf("Ignoring announcement (flags=%v) "+
"for known chan_id=%v", msg.Flags,
chanID)
return false
}
}
// Before we can update the channel information, we need to get
// the UTXO itself so we can store the proper capacity.
chanPoint, err := r.fetchChanPoint(&msg.ChannelID)
if err != nil {
log.Errorf("unable to fetch chan point: %v", err)
return false
}
utxo, err := r.cfg.Chain.GetUtxo(&chanPoint.Hash,
chanPoint.Index)
if err != nil {
log.Errorf("unable to fetch utxo for chan_id=%v: %v",
chanID, err)
return false
}
// TODO(roasbeef): should be msat here
chanUpdate := &channeldb.ChannelEdge{
ChannelID: chanID,
ChannelPoint: *chanPoint,
LastUpdate: updateTimestamp,
Flags: msg.Flags,
Expiry: msg.Expiry,
MinHTLC: btcutil.Amount(msg.HtlcMinimumMstat),
FeeBaseMSat: btcutil.Amount(msg.FeeBaseMstat),
FeeProportionalMillionths: btcutil.Amount(msg.FeeProportionalMillionths),
// TODO(roasbeef): this is a hack, needs to be removed
// after commitment fees are dynamic.
Capacity: btcutil.Amount(utxo.Value) - 5000,
}
err = r.cfg.Graph.UpdateEdgeInfo(chanUpdate)
if err != nil {
log.Errorf("unable to add channel: %v", err)
return false
}
log.Infof("New channel update applied: %v",
spew.Sdump(chanUpdate))
}
return true
}
// syncRequest represents a request from an outside sub-system to the wallet to
// sync a new node to the latest graph state.
type syncRequest struct {
node *btcec.PublicKey
}
// SynchronizeNode sends a message to the ChannelRouter indicating it should
// synchronize routing state with the target node. This method is to be
// utilized when a node connections for the first time to provide it with the
// latest channel graph state.
func (r *ChannelRouter) SynchronizeNode(pub *btcec.PublicKey) {
select {
case r.syncRequests <- &syncRequest{
node: pub,
}:
case <-r.quit:
return
}
}
// syncChannelGraph attempts to synchronize the target node in the syncReq to
// the latest channel graph state. In order to accomplish this, (currently) the
// entire graph is read from disk, then serialized to the format defined within
// the current wire protocol. This cache of graph data is then sent directly to
// the target node.
func (r *ChannelRouter) syncChannelGraph(syncReq *syncRequest) error {
targetNode := syncReq.node
// TODO(roasbeef): need to also store sig data in db
// * will be nice when we switch to pairing sigs would only need one ^_^
// We'll collate all the gathered routing messages into a single slice
// containing all the messages to be sent to the target peer.
var announceMessages []lnwire.Message
// First run through all the vertexes in the graph, retrieving the data
// for the announcement we originally retrieved.
var numNodes uint32
if err := r.cfg.Graph.ForEachNode(func(node *channeldb.LightningNode) error {
alias, err := lnwire.NewAlias(node.Alias)
if err != nil {
return err
}
ann := &lnwire.NodeAnnouncement{
Signature: r.fakeSig,
Timestamp: uint32(node.LastUpdate.Unix()),
Address: node.Address,
NodeID: node.PubKey,
Alias: alias,
}
announceMessages = append(announceMessages, ann)
numNodes++
return nil
}); err != nil {
return err
}
// With the vertexes gathered, we'll no retrieve the initial
// announcement, as well as the latest channel update announcement for
// both of the directed edges that make up the channel.
// TODO(roasbeef): multi-sig keys should also be stored in DB
var numEdges uint32
if err := r.cfg.Graph.ForEachChannel(func(e1, e2 *channeldb.ChannelEdge) error {
chanID := lnwire.NewChanIDFromInt(e1.ChannelID)
chanAnn := &lnwire.ChannelAnnouncement{
FirstNodeSig: r.fakeSig,
SecondNodeSig: r.fakeSig,
ChannelID: chanID,
FirstBitcoinSig: r.fakeSig,
SecondBitcoinSig: r.fakeSig,
FirstNodeID: e1.Node.PubKey,
SecondNodeID: e2.Node.PubKey,
FirstBitcoinKey: e1.Node.PubKey,
SecondBitcoinKey: e2.Node.PubKey,
}
chanUpdate1 := &lnwire.ChannelUpdateAnnouncement{
Signature: r.fakeSig,
ChannelID: chanID,
Timestamp: uint32(e1.LastUpdate.Unix()),
Flags: 0,
Expiry: e1.Expiry,
HtlcMinimumMstat: uint32(e1.MinHTLC),
FeeBaseMstat: uint32(e1.FeeBaseMSat),
FeeProportionalMillionths: uint32(e1.FeeProportionalMillionths),
}
chanUpdate2 := &lnwire.ChannelUpdateAnnouncement{
Signature: r.fakeSig,
ChannelID: chanID,
Timestamp: uint32(e2.LastUpdate.Unix()),
Flags: 1,
Expiry: e2.Expiry,
HtlcMinimumMstat: uint32(e2.MinHTLC),
FeeBaseMstat: uint32(e2.FeeBaseMSat),
FeeProportionalMillionths: uint32(e2.FeeProportionalMillionths),
}
numEdges++
announceMessages = append(announceMessages, chanAnn)
announceMessages = append(announceMessages, chanUpdate1)
announceMessages = append(announceMessages, chanUpdate2)
return nil
}); err != nil && err != channeldb.ErrGraphNoEdgesFound {
log.Errorf("unable to sync edges w/ peer: %v", err)
return err
}
log.Infof("Syncing channel graph state with %x, sending %v "+
"nodes and %v edges", targetNode.SerializeCompressed(),
numNodes, numEdges)
// With all the announcement messages gathered, send them all in a
// single batch to the target peer.
return r.cfg.SendMessages(targetNode, announceMessages...)
}
// fetchChanPoint retrieves the original outpoint which is encoded within the
// channelID.
func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ChannelID) (*wire.OutPoint, error) {
// First fetch the block hash by the block number encoded, then use
// that hash to fetch the block itself.
blockNum := int64(chanID.BlockHeight)
blockHash, err := r.cfg.Chain.GetBlockHash(blockNum)
if err != nil {
return nil, err
}
fundingBlock, err := r.cfg.Chain.GetBlock(blockHash)
if err != nil {
return nil, err
}
// TODO(roasbeef): skipping validation here as
// the discovery service should handle full
// validate
// Finally once we have the block itself, we seek to the targeted
// transaction index to obtain the funding output and txid.
fundingTx := fundingBlock.Transactions[chanID.TxIndex]
return &wire.OutPoint{
Hash: fundingTx.TxSha(),
Index: uint32(chanID.TxPosition),
}, nil
}
// routingMsg couples a routing related wire message with the peer that
// originally sent it.
type routingMsg struct {
msg lnwire.Message
peer *btcec.PublicKey
}
// ProcessRoutingMessags sends a new routing message along with the peer that
// sent the routing message to the ChannelRouter. The announcement will be
// processed then added to a queue for batched tickled announcement to all
// connected peers.
//
// TODO(roasbeef): need to move to discovery package
func (r *ChannelRouter) ProcessRoutingMessage(msg lnwire.Message, src *btcec.PublicKey) {
// TODO(roasbeef): msg wrappers to add a doneChan
rMsg := &routingMsg{
msg: msg,
peer: src,
}
select {
case r.networkMsgs <- rMsg:
case <-r.quit:
return
}
}
// FindRoute attempts to query the ChannelRouter for the "best" path to a
// particular target destination which is able to send `amt` after factoring in
// channel capacities and cumulative fees along the route.
func (r *ChannelRouter) FindRoute(target *btcec.PublicKey, amt btcutil.Amount) (*Route, error) {
dest := target.SerializeCompressed()
log.Debugf("Searching for path to %x, sending %v", dest, amt)
// We can short circuit the routing by opportunistically checking to
// see if the target vertex event exists in the current graph.
if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
return nil, err
} else if !exists {
log.Debugf("Target %x is not in known graph", dest)
return nil, ErrTargetNotInNetwork
}
// TODO(roasbeef): add k-shortest paths
route, err := findRoute(r.cfg.Graph, target, amt)
if err != nil {
log.Errorf("Unable to find path: %v", err)
return nil, err
}
// TODO(roabseef): also create the Sphinx packet and add in the route
log.Debugf("Obtained path sending %v to %x: %v", amt, dest,
newLogClosure(func() string {
return spew.Sdump(route)
}),
)
return route, nil
}
// SendPayment...
//
// TODO(roasbeef): pipe through the htlcSwitch, move the payment storage info
// to the router, add interface for payment storage
// TODO(roasbeef): add version that takes a route object
func (r *ChannelRouter) SendPayment() error {
return nil
}
// TopologyClient...
// TODO(roasbeef): put in discovery package?
type TopologyClient struct {
}
// TopologyChange...
type TopologyChange struct {
NewNodes []*channeldb.LinkNode
NewChannels []*channeldb.ChannelEdge
}
// notifyTopologyChange...
func (r *ChannelRouter) notifyTopologyChange() {
}
// SubscribeTopology....
func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) {
return nil, nil
}

@ -1,67 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import "container/list"
func ShortestPathLen(g *Graph, v1, v2 Vertex) (int, error) {
dist, _, err := bfs(g, v1, v2)
if err != nil {
return 0, err
}
if _, ok := dist[v2]; !ok {
return 0, PathNotFoundError
}
return dist[v2], nil
}
func ShortestPath(g *Graph, v1, v2 Vertex) ([]Vertex, error) {
_, parent, err := bfs(g, v1, v2)
if err != nil {
return nil, err
}
if _, ok := parent[v2]; !ok {
return nil, PathNotFoundError
}
path, err := Path(v1, v2, parent)
if err != nil {
return nil, err
}
return path, nil
}
func bfs(g *Graph, v1, v2 Vertex) (map[Vertex]int, map[Vertex]Vertex, error) {
var queue list.List
queue.PushBack(v1)
dist := make(map[Vertex]int)
parent := make(map[Vertex]Vertex)
dist[v1] = 0
for queue.Len() != 0 {
err := ibfs(g, queue.Front().Value.(Vertex), &queue, dist, parent)
if err != nil {
return nil, nil, err
}
if _, ok := dist[v2]; ok {
break
}
}
return dist, parent, nil
}
func ibfs(g *Graph, v Vertex, queue *list.List, dist map[Vertex]int, parent map[Vertex]Vertex) error {
queue.Remove(queue.Front())
targets, err := g.GetNeighbors(v)
if err != nil {
return err
}
for to := range targets {
if _, ok := dist[to]; !ok {
dist[to] = dist[v] + 1
parent[to] = v
queue.PushBack(to)
}
}
return nil
}

@ -1,31 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.
package graph
import (
"reflect"
"testing"
)
func TestShortestPath(t *testing.T) {
g, v := newTestGraph()
tests := []struct{
Source, Target Vertex
ExpectedPath []Vertex
}{
{v[1], v[4], []Vertex{v[1], v[7], v[3], v[6], v[5], v[4]}},
{v[5], v[7], []Vertex{v[5], v[6], v[3], v[7]}},
}
for _, test := range tests {
path, err := ShortestPath(g, test.Source, test.Target)
if err != nil {
t.Errorf("ShortestPath(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err)
}
if !reflect.DeepEqual(path, test.ExpectedPath) {
t.Errorf("ShortestPath(g, %v, %v ) = %v, want %v", test.Source, test.Target, path, test.ExpectedPath)
}
}
}

@ -1,67 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import "math"
func DijkstraPathWeight(g *Graph, source, target Vertex) (float64, error) {
dist, _, err := dijkstra(g, source, target)
if err != nil {
return 0, err
}
if _, ok := dist[target]; !ok {
return 0, PathNotFoundError
}
return dist[target], nil
}
func DijkstraPath(g *Graph, source, target Vertex) ([]Vertex, error) {
_, parent, err := dijkstra(g, source, target)
if err != nil {
return nil, err
}
if _, ok := parent[target]; !ok {
return nil, PathNotFoundError
}
path, err := Path(source, target, parent)
if err != nil {
return nil, err
}
return path, nil
}
func dijkstra(g *Graph, source, target Vertex) (map[Vertex]float64, map[Vertex]Vertex, error) {
dist := make(map[Vertex]float64)
colored := make(map[Vertex]bool)
parent := make(map[Vertex]Vertex)
dist[source] = 0
for {
bestDist := math.MaxFloat64
var bestID Vertex
for id, val := range dist {
if val < bestDist && !colored[id] {
bestDist = val
bestID = id
}
}
if bestID == target {
break
}
colored[bestID] = true
targets, err := g.GetNeighbors(bestID)
if err != nil {
return nil, nil, err
}
for id, multiedges := range targets {
for _, edge := range multiedges {
if have, ok := dist[id]; !ok || have > dist[bestID]+edge.Wgt {
dist[id] = dist[bestID] + edge.Wgt
parent[id] = bestID
}
}
}
}
return dist, parent, nil
}

@ -1,41 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import (
"reflect"
"testing"
)
func TestDijkstraPath(t *testing.T) {
g, v := newTestGraph()
tests := []struct {
Source, Target Vertex
ExpectedPath []Vertex
ExpectedWeight float64
}{
{v[1], v[4], []Vertex{v[1], v[7], v[2], v[3], v[6], v[5], v[4]}, 13},
{v[5], v[7], []Vertex{v[5], v[6], v[3], v[2], v[7]}, 10},
}
for _, test := range tests {
// Test DijkstraPath
path, err := DijkstraPath(g, test.Source, test.Target)
if err != nil {
t.Errorf("DijkstraPath(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err)
}
if !reflect.DeepEqual(path, test.ExpectedPath) {
t.Errorf("DijkstraPath(g, %v, %v ) = %v, want %v", test.Source, test.Target, path, test.ExpectedPath)
}
// Test DijkstraPathWeight
weight, err := DijkstraPathWeight(g, test.Source, test.Target)
if err != nil {
t.Errorf("DijkstraPathWeight(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err)
}
if weight != test.ExpectedWeight {
t.Errorf("DijkstraPathWeight(g, %v, %v ) = %v, want %v", test.Source, test.Target, weight, test.ExpectedWeight)
}
}
}

@ -1,357 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import (
"errors"
"fmt"
"math"
"bytes"
"github.com/roasbeef/btcd/wire"
)
var (
// CyclicDataError represents internal error. When trying to
// obtain explicit path after BFS. Following parents resulted
// in a loop.
CyclicDataError = errors.New("Cyclic data")
// PathNotFoundError represents not existing path
// after search
PathNotFoundError = errors.New("Path not found")
// NodeNotFoundError represents that required vertex
// does not exist in the graph
NodeNotFoundError = errors.New("Node not found")
// EdgeNotFoundError represents that requested edge
// does not exist in the graph
EdgeNotFoundError = errors.New("Edge not found")
)
// Vertex represent graph vertex(node) in a
// lightning network
type Vertex struct {
PubKey [33]byte
}
// NilVertex represents not existing vertex.
// This value should be used similar to nil
var NilVertex = Vertex{PubKey: [33]byte{}}
// NewVertex creates a new vertex from a compressed public key.
func NewVertex(v []byte) Vertex {
x := Vertex{}
copy(x.PubKey[:], v)
return x
}
// String creates a string representation of a Vertex.
// Note: it does not hex encode.
func (s Vertex) String() string {
return string(s.PubKey[:])
}
// ToByte33 returns [33]byte
// 33 - is usual byte length of a compressed
// public key
func (s Vertex) ToByte33() [33]byte {
var rez [33]byte
copy(rez[:], s.PubKey[:])
return rez
}
// ToByte() returns byte representation of
// the vertex
func (s Vertex) ToByte() []byte {
return s.PubKey[:]
}
// IsNil compares vertex to nil vertex
func (s Vertex) IsNil() bool {
var z [33]byte
return bytes.Equal(s.PubKey[:], z[:])
}
// EdgeID represent edge unique identifier.
type EdgeID wire.OutPoint
// NilEdgeID represent not existing EdgeID
var NilEdgeID = EdgeID{wire.ShaHash{}, 0}
// NewEdgeID returns new EdgeID
func NewEdgeID(v wire.OutPoint) EdgeID {
return EdgeID(v)
}
// String returns string representation of EdgeID
func (e EdgeID) String() string {
return wire.OutPoint(e).String()
}
// ChannelInfo contains information about edge(channel)
// like capacity or weight
type ChannelInfo struct {
// Capacity in satoshi of the channel
Cpt int64
// Weight of the channel
Wgt float64
}
// Copy() creates a copy of a ChannelInfo struct.
// If c==nil than it returns nil.
// This method is used to safely create copy of a structure
// given by a pointer which may be nil.
func (c *ChannelInfo) Copy() *ChannelInfo {
if c == nil {
return nil
} else {
c1 := *c
return &c1
}
}
// Edge represents edge in a graph
type Edge struct {
// Source and Target
Src, Tgt Vertex
// Edge identifier
Id EdgeID
// Additional information about edge
Info *ChannelInfo
}
// NilEdge represents nil (not-existing) edge
var NilEdge = Edge{NilVertex, NilVertex, NilEdgeID, nil}
// String returns string of an Edge
func (e Edge) String() string {
return fmt.Sprintf("edge[%v %v %v %v]", e.Src, e.Tgt, e.Id, e.Info)
}
// NewEdge create a new edge
func NewEdge(src, tgt Vertex, id EdgeID, info *ChannelInfo) Edge {
return Edge{
Src: src,
Tgt: tgt,
Id: id,
Info: info,
}
}
// Graph is multigraph implementation.
type Graph struct {
adjacencyList map[Vertex]map[Vertex]map[EdgeID]*ChannelInfo
}
// NewGraph creates a new empty graph.
func NewGraph() *Graph {
g := new(Graph)
g.adjacencyList = make(map[Vertex]map[Vertex]map[EdgeID]*ChannelInfo)
return g
}
// GetVertexCount returns number of vertexes in a graph.
func (g *Graph) GetVertexCount() int {
return len(g.adjacencyList)
}
// GetVertexes returns all vertexes in a graph.
func (g *Graph) GetVertexes() []Vertex {
IDs := make([]Vertex, 0, g.GetVertexCount())
for ID := range g.adjacencyList {
IDs = append(IDs, ID)
}
return IDs
}
// GetEdges return all edges in a graph.
// For undirected graph it returns each edge twice (for each direction)
// To get all edges in undirected graph use GetUndirectedEdges
func (g *Graph) GetEdges() []Edge {
edges := make([]Edge, 0)
for v1 := range g.adjacencyList {
for v2, multiedges := range g.adjacencyList[v1] {
for id, edge := range multiedges {
edges = append(edges, NewEdge(v1, v2, id, edge))
}
}
}
return edges
}
// GetUndirectedEdges returns all edges in an undirected graph.
func (g *Graph) GetUndirectedEdges() []Edge {
edges := make([]Edge, 0)
for v1 := range g.adjacencyList {
for v2, multiedges := range g.adjacencyList[v1] {
if v1.String() <= v2.String() {
for id, edge := range multiedges {
edges = append(edges, NewEdge(v1, v2, id, edge))
}
}
}
}
return edges
}
// HasVertex check if graph contain the given vertex.
func (g *Graph) HasVertex(v Vertex) bool {
_, ok := g.adjacencyList[v]
return ok
}
// HasEdge check if there is edge with a given EdgeID
// between two given vertexes.
func (g *Graph) HasEdge(v1, v2 Vertex, edgeID EdgeID) bool {
if _, ok := g.adjacencyList[v1]; ok {
if multiedges, ok := g.adjacencyList[v1][v2]; ok {
if _, ok := multiedges[edgeID]; ok {
return true
}
}
}
return false
}
// AddVertex adds vertex to a graph.
// If graph already contains this vertex it does nothing.
// Returns true if vertex previously doesnt't exist.
func (g *Graph) AddVertex(v Vertex) bool {
if g.HasVertex(v) {
return false
}
g.adjacencyList[v] = make(map[Vertex]map[EdgeID]*ChannelInfo)
return true
}
// RemoveVertex removes vertex from a graph
// If a graph already does not contain this vertex it does nothing
// Returns true if previously existed vertex got deleted
// BUG(mkl): does it correctly deletes edges with this vertex
func (g *Graph) RemoveVertex(v Vertex) bool {
if !g.HasVertex(v) {
return false
}
delete(g.adjacencyList, v)
return true
}
// AddEdge adds directed edge to the graph
// v1, v2 must exist. If they do not exist this function do nothing
// and return false. If edge with given vertexes and id already exists it
// gets overwritten
func (g *Graph) AddEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool {
if !g.HasVertex(v1) || !g.HasVertex(v2) {
return false
}
tmap := g.adjacencyList[v1]
if tmap[v2] == nil {
tmap[v2] = make(map[EdgeID]*ChannelInfo)
}
tmap[v2][edgeID] = info
return true
}
// AddUndirectedEdge adds an undirected edge to the graph.
// Vertexes should exists.
func (g *Graph) AddUndirectedEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool {
ok1 := g.AddEdge(v1, v2, edgeID, info)
ok2 := g.AddEdge(v2, v1, edgeID, info)
return ok1 && ok2
}
// ReplaceEdge replaces directed edge in the graph
func (g *Graph) ReplaceEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool {
if tmap, ok := g.adjacencyList[v1]; ok {
if _, ok := tmap[v2]; ok {
if _, ok := tmap[v2][edgeID]; ok {
tmap[v2][edgeID] = info
return true
}
}
}
return false
}
// ReplaceUndirectedEdge replaces undirected edge in the graph.
func (g *Graph) ReplaceUndirectedEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool {
ok1 := g.ReplaceEdge(v1, v2, edgeID, info)
ok2 := g.ReplaceEdge(v2, v1, edgeID, info)
return ok1 && ok2
}
// RemoveEdge removes directed edge in a graph.
func (g *Graph) RemoveEdge(v1, v2 Vertex, edgeID EdgeID) bool {
if _, ok := g.adjacencyList[v1]; ok {
if _, ok := g.adjacencyList[v1][v2]; ok {
tmap := g.adjacencyList[v1][v2]
if _, ok := tmap[edgeID]; ok {
delete(tmap, edgeID)
if len(tmap) == 0 {
delete(g.adjacencyList[v1], v2)
}
return true
}
}
}
return false
}
// RemoveUndirectedEdge removes undirected edge in a graph.
func (g *Graph) RemoveUndirectedEdge(v1, v2 Vertex, edgeID EdgeID) bool {
ok1 := g.RemoveEdge(v1, v2, edgeID)
ok2 := g.RemoveEdge(v2, v1, edgeID)
return ok1 && ok2
}
// GetInfo returns info about edge in a graph.
func (g *Graph) GetInfo(v1, v2 Vertex, edgeID EdgeID) (*ChannelInfo, error) {
if tmap, ok := g.adjacencyList[v1]; ok {
if _, ok := tmap[v2]; ok {
if _, ok := tmap[v2][edgeID]; ok {
return tmap[v2][edgeID], nil
}
}
}
return nil, EdgeNotFoundError
}
// GetNeighbors returns neighbors of a given vertex in a graph.
// Note: output should not be modified because it will change
// original graph
func (g *Graph) GetNeighbors(v Vertex) (map[Vertex]map[EdgeID]*ChannelInfo, error) {
if !g.HasVertex(v) {
return nil, NodeNotFoundError
}
return g.adjacencyList[v], nil
}
// MinCostChannel return channel with minimal weight between two vertexes
func (g *Graph) MinCostChannel(v1, v2 Vertex) (*ChannelInfo, error) {
if !g.HasVertex(v1) || !g.HasVertex(v2) {
return nil, NodeNotFoundError
}
if _, ok := g.adjacencyList[v1][v2]; !ok {
return nil, EdgeNotFoundError
}
wgt := math.MaxFloat64
var easiest *ChannelInfo
for _, edge := range g.adjacencyList[v1][v2] {
if edge.Wgt < wgt {
wgt, easiest = edge.Wgt, edge
}
}
return easiest, nil
}
// Bfs do breadth-first search starting from a given vertex
func (g *Graph) Bfs(source Vertex) (map[Vertex]int, map[Vertex]Vertex, error) {
return bfs(g, source, NilVertex)
}

@ -1,166 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import (
"reflect"
"testing"
"fmt"
)
func vertexFromInt(x int) Vertex {
s := fmt.Sprintf("%v", x)
return NewVertex([]byte(s))
}
func edgeIdFromString(s string) EdgeID {
e := EdgeID{}
copy(e.Hash[:], []byte(s))
return e
}
// newTestGraph returns new graph for testing purposes
// Each time it returns new graph
func newTestGraph() (*Graph, []Vertex) {
g := NewGraph()
v := make([]Vertex, 8)
for i:=1; i<8; i++ {
v[i] = vertexFromInt(i)
g.AddVertex(v[i])
}
edges := []Edge{
{v[1], v[7], edgeIdFromString("2"), &ChannelInfo{Wgt:2}},
{v[7], v[2], edgeIdFromString("1"), &ChannelInfo{Wgt:1}},
{v[3], v[7], edgeIdFromString("10"), &ChannelInfo{Wgt:10}},
{v[2], v[3], edgeIdFromString("2"), &ChannelInfo{Wgt:2}},
{v[3], v[6], edgeIdFromString("4"), &ChannelInfo{Wgt:4}},
{v[5], v[6], edgeIdFromString("3"), &ChannelInfo{Wgt:3}},
{v[4], v[5], edgeIdFromString("1"), &ChannelInfo{Wgt:1}},
}
for _, e := range edges {
g.AddUndirectedEdge(e.Src, e.Tgt, e.Id, e.Info)
}
return g, v
}
func TestNodeManipulation(t *testing.T) {
g := NewGraph()
if g.HasVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if !g.AddVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if g.AddVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if !g.HasVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if !g.RemoveVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if g.RemoveVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if g.HasVertex(vertexFromInt(2)) {
t.Errorf("expected: %t, actual: %t", false, true)
}
}
func TestEdgeManipulation(t *testing.T) {
g := NewGraph()
if g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) {
t.Errorf("expected: %t, actual: %t", false, true)
}
g.AddVertex(vertexFromInt(2))
g.AddVertex(vertexFromInt(3))
if g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if g.ReplaceEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{1, 2}) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if !g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if !g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if !g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if !g.ReplaceEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{1, 2}) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if info, err := g.GetInfo(vertexFromInt(2), vertexFromInt(3), NilEdgeID); err != nil {
panic(err)
} else if !reflect.DeepEqual(*info, ChannelInfo{1, 2}) {
t.Errorf("expected: %v, actual: %v", ChannelInfo{1, 2}, *info)
}
if !g.RemoveEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) {
t.Errorf("expected: %t, actual: %t", true, false)
}
if g.RemoveEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) {
t.Errorf("expected: %t, actual: %t", false, true)
}
if g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) {
t.Errorf("expected: %t, actual: %t", false, true)
}
}
func TestAllGetMethods(t *testing.T) {
g := NewGraph()
g.AddVertex(vertexFromInt(2))
g.AddVertex(vertexFromInt(3))
if vertexCount := g.GetVertexCount(); vertexCount != 2 {
t.Errorf("expected: %d, actual: %d", 2, vertexCount)
}
if vs := g.GetVertexes(); !reflect.DeepEqual(vs, []Vertex{vertexFromInt(2), vertexFromInt(3)}) &&
!reflect.DeepEqual(vs, []Vertex{vertexFromInt(3), vertexFromInt(2)}) {
t.Errorf("expected: %v, actual: %v",
[]Vertex{vertexFromInt(2), vertexFromInt(3)},
vs,
)
}
g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil)
if edges := g.GetEdges(); !reflect.DeepEqual(edges, []Edge{NewEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil)}) {
t.Errorf("expected: %v, actual: %v",
[]Edge{NewEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil)},
edges,
)
}
if targets, err := g.GetNeighbors(vertexFromInt(2)); err != nil {
panic(err)
} else if !reflect.DeepEqual(targets, map[Vertex]map[EdgeID]*ChannelInfo{vertexFromInt(3): map[EdgeID]*ChannelInfo{NilEdgeID: nil}}) {
t.Errorf("expected: %v, actual: %v",
map[Vertex]map[EdgeID]*ChannelInfo{vertexFromInt(3): map[EdgeID]*ChannelInfo{NilEdgeID: nil}},
targets,
)
}
g2 := NewGraph()
g2.AddVertex(vertexFromInt(2))
g2.AddVertex(vertexFromInt(3))
g2.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{Wgt: 42})
if info, err := g2.GetInfo(vertexFromInt(2), vertexFromInt(3), NilEdgeID); err != nil {
panic(err)
} else if wgt := info.Wgt; wgt != 42 {
t.Errorf("expected: %v, actual: %v", wgt, 42)
}
}
func TestVertexToByte33(t *testing.T) {
var b1 [33]byte
for i := 0; i < 33; i++ {
b1[i] = byte(i)
}
v := NewVertex(b1[:])
b2 := v.ToByte33()
if b1 != b2 {
t.Errorf("Wrong result of ID.ToByte33()= %v, want %v", b2, b1)
}
}

@ -1,68 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import "math"
// KShortestPaths finds k shortest paths
// Note: this implementation finds k path not necessary shortest
// It tries to make that distinct and shortest at the same time
func KShortestPaths(g *Graph, source, target Vertex, k int) ([][]Vertex, error) {
ksp := make([][]Vertex, 0, k)
DRY := make(map[string]bool)
actualNodeWeight := make(map[Vertex]float64, g.GetVertexCount())
for _, id := range g.GetVertexes() {
actualNodeWeight[id] = 1
}
const UselessIterations = 200
for cnt := 0; len(ksp) < k && cnt < UselessIterations; cnt++ {
if err := modifyEdgeWeight(g, actualNodeWeight); err != nil {
return nil, err
}
path, err := DijkstraPath(g, source, target)
if err != nil {
return nil, err
}
for _, v := range path {
actualNodeWeight[v]++
}
key := ""
for _, v := range path {
key += v.String()
}
if !DRY[key] {
DRY[key] = true
ksp = append(ksp, path)
cnt = 0
}
}
return ksp, nil
}
func modifyEdgeWeight(g *Graph, actualNodeWeight map[Vertex]float64) error {
for _, v1 := range g.GetVertexes() {
targets, err := g.GetNeighbors(v1)
if err != nil {
return err
}
for v2, multiedges := range targets {
for ID := range multiedges {
wgt := calcEdgeWeight(actualNodeWeight, v1, v2)
g.ReplaceUndirectedEdge(v1, v2, ID, &ChannelInfo{Wgt: wgt})
}
}
}
return nil
}
// Calculate new edge weight based on vertex weights.
// It uses empirical formulae
// weight(i, j) = (weight(i) + weight(j)) ^ 6
// Number 6 was choosen because it gives best results in several simulations
func calcEdgeWeight(actualNodeWeight map[Vertex]float64, v1, v2 Vertex) float64 {
const ExperiementalNumber = 6.0
wgt := math.Pow(actualNodeWeight[v1]+actualNodeWeight[v2], ExperiementalNumber)
return wgt
}

@ -1,39 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import (
"reflect"
"testing"
)
func TestKSP(t *testing.T) {
g, v := newTestGraph()
tests := []struct {
Source, Target Vertex
K int
ExpectedPaths [][]Vertex
}{
{v[1], v[4], 10, [][]Vertex{
{v[1], v[7], v[3], v[6], v[5], v[4]},
{v[1], v[7], v[2], v[3], v[6], v[5], v[4]},
},
},
{v[5], v[7], 1, [][]Vertex{
{v[5], v[6], v[3], v[7]},
},
},
}
for _, test := range tests {
paths, err := KShortestPaths(g, test.Source, test.Target, test.K)
if err != nil {
t.Errorf("KShortestPaths(g, %v, %v, %v) returns not nil error: %v", test.Source, test.Target, test.K, err)
}
if !reflect.DeepEqual(paths, test.ExpectedPaths) {
t.Errorf("KShortestPaths(g, %v, %v, %v) = %v, want %v", test.Source, test.Target, test.K, paths, test.ExpectedPaths)
}
}
}

@ -1,39 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package graph
import "errors"
func Path(v1, v2 Vertex, parent map[Vertex]Vertex) ([]Vertex, error) {
path, err := ReversePath(v1, v2, parent)
if err != nil {
return nil, err
}
Reverse(path)
return path, nil
}
func ReversePath(v1, v2 Vertex, parent map[Vertex]Vertex) ([]Vertex, error) {
path := []Vertex{v2}
for v2 != v1 {
if v2 == parent[v2] {
return nil, CyclicDataError
}
var ok bool
v2, ok = parent[v2]
if !ok {
return nil, errors.New("Invalid key")
}
path = append(path, v2)
}
return path, nil
}
func Reverse(path []Vertex) {
length := len(path)
for i := 0; i < length/2; i++ {
path[i], path[length-1-i] = path[length-1-i], path[i]
}
}

@ -1,258 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package rt
import (
"fmt"
"github.com/lightningnetwork/lnd/routing/rt/graph"
)
type OperationType byte
const (
AddChannelOP OperationType = iota
RemoveChannelOp
)
// String returns string representation
func (t OperationType) String()string {
switch t {
case AddChannelOP:
return "<ADD_CHANNEL>"
case RemoveChannelOp:
return "<REMOVE_CHANNEL>"
default:
return "<UNKNOWN_TYPE>"
}
}
// ChannelOperation represent operation on a graph
// Such as add edge or remove edge
type ChannelOperation struct {
graph.Edge
Operation OperationType // One of ADD_CHANNEL, REMOVE_CHANNEL
}
// NewChannelOperation returns new ChannelOperation
func NewChannelOperation(src, tgt graph.Vertex, id graph.EdgeID, info *graph.ChannelInfo, opType OperationType) ChannelOperation{
return ChannelOperation{
Edge: graph.Edge{
Src: src,
Tgt: tgt,
Id: id,
Info: info.Copy(),
},
Operation: opType,
}
}
// String return string representation
func (c ChannelOperation) String () string {
return fmt.Sprintf(
"ChannelOperation[%v %v %v %v %v]",
c.Src,
c.Tgt,
c.Id,
c.Info,
c.Operation,
)
}
// Copy returns copy of ChannelOperation
func (op *ChannelOperation) Copy() ChannelOperation {
return ChannelOperation{
Edge: graph.Edge{
Src: op.Src,
Tgt: op.Tgt,
Id: op.Id,
Info: op.Info.Copy(),
},
Operation: op.Operation,
}
}
// DifferenceBuffer represent multiple changes in a graph
// such as adding or deleting edges
type DifferenceBuffer []ChannelOperation
// String returns string representation
func (d DifferenceBuffer) String() string {
s := ""
for i:=0; i<len(d); i++ {
s += fmt.Sprintf(" %v\n", d[i])
}
s1 := ""
if len(d) > 0 {
s1 = "\n"
}
return fmt.Sprintf("DifferenceBuffer[%v%v]", s1, s)
}
// NewDifferenceBuffer create new empty DifferenceBuffer
func NewDifferenceBuffer() *DifferenceBuffer {
d := make(DifferenceBuffer, 0)
return &d
}
// IsEmpty checks if buffer is empty(contains no operations in it)
func (diffBuff *DifferenceBuffer) IsEmpty() bool {
return len(*diffBuff) == 0
}
// Clear deletes all operations from DifferenceBuffer making it empty
func (diffBuff *DifferenceBuffer) Clear() {
*diffBuff = make([]ChannelOperation, 0)
}
// Copy create copy of a DifferenceBuffer
func (diffBuff *DifferenceBuffer) Copy() *DifferenceBuffer {
d := make([]ChannelOperation, 0, len(*diffBuff))
for _, op := range *diffBuff {
d = append(d, op.Copy())
}
return (*DifferenceBuffer)(&d)
}
// RoutingTable represent information about graph and neighbors
// TODO(mkl): better doc
// Methods of this struct is not thread safe
type RoutingTable struct {
// Contains node's view of lightning network
G *graph.Graph
// Contains changes to send to a different neighbors
// Changing RT modifies DifferenceBuffer
diff []*DifferenceBuffer
}
// ShortestPath find shortest path between two node in routing table
func (rt *RoutingTable) ShortestPath(src, dst graph.Vertex) ([]graph.Vertex, error) {
return graph.ShortestPath(rt.G, src, dst)
}
// NewRoutingTable creates new empty routing table
func NewRoutingTable() *RoutingTable {
return &RoutingTable{
G: graph.NewGraph(),
diff: make([]*DifferenceBuffer, 0),
}
}
// AddNodes add multiple nodes to the routing table
func (rt *RoutingTable) AddNodes(IDs ...graph.Vertex) {
for _, ID := range IDs {
rt.G.AddVertex(ID)
}
}
// AddChannel adds channel to the routing table.
// It will add nodes if they are not present in RT.
// It will update all difference buffers
func (rt *RoutingTable) AddChannel(Node1, Node2 graph.Vertex, edgeID graph.EdgeID, info *graph.ChannelInfo) {
rt.AddNodes(Node1, Node2)
if !rt.HasChannel(Node1, Node2, edgeID) {
rt.G.AddUndirectedEdge(Node1, Node2, edgeID, info)
for _, diffBuff := range rt.diff {
*diffBuff = append(*diffBuff, NewChannelOperation(Node1, Node2, edgeID, info, AddChannelOP))
}
}
}
// HasChannel check if channel between two nodes exist in routing table
func (rt *RoutingTable) HasChannel(node1, node2 graph.Vertex, edgeID graph.EdgeID) bool {
return rt.G.HasEdge(node1, node2, edgeID)
}
// RemoveChannel removes channel from the routing table.
// It will do nothing if channel does not exist in RT.
// It will update all difference buffers
func (rt *RoutingTable) RemoveChannel(Node1, Node2 graph.Vertex, edgeID graph.EdgeID) {
if rt.HasChannel(Node1, Node2, edgeID) {
rt.G.RemoveUndirectedEdge(Node1, Node2, edgeID)
for _, diffBuff := range rt.diff {
*diffBuff = append(*diffBuff, NewChannelOperation(Node1, Node2, edgeID, nil, RemoveChannelOp))
}
}
}
// NewDiffBuff create a new difference buffer in a routing table
func (rt *RoutingTable) NewDiffBuff() *DifferenceBuffer {
buff := NewDifferenceBuffer()
rt.diff = append(rt.diff, buff)
return buff
}
// ApplyDiffBuff applies difference buffer to the routing table.
// It will modify RoutingTable's difference buffers.
func (rt *RoutingTable) ApplyDiffBuff(diffBuff *DifferenceBuffer) {
for _, op := range *diffBuff {
if op.Operation == AddChannelOP {
rt.AddChannel(op.Src, op.Tgt, op.Id, op.Info)
} else if op.Operation == RemoveChannelOp {
rt.RemoveChannel(op.Src, op.Tgt, op.Id)
}
}
}
// Nodes return all nodes from routing table
func (rt *RoutingTable) Nodes() []graph.Vertex {
return rt.G.GetVertexes()
}
// NumberOfNodes returns number of nodes in routing table
func (rt *RoutingTable) NumberOfNodes() int {
return rt.G.GetVertexCount()
}
// AllChannels returns all channels from routing table
func (rt *RoutingTable) AllChannels() []graph.Edge {
edges := rt.G.GetUndirectedEdges()
return edges
}
// AddTable adds other RoutingTable.
// Resulting table contains channels, nodes from both tables.
func (rt *RoutingTable) AddTable(rt1 *RoutingTable) {
newChannels := rt1.AllChannels()
for _, channel := range newChannels {
rt.AddChannel(channel.Src, channel.Tgt, channel.Id, channel.Info.Copy())
}
}
// Copy - creates a copy of RoutingTable
// It makes deep copy of routing table.
// Note: difference buffers are not copied!
func (rt *RoutingTable) Copy() *RoutingTable {
// TODO: add tests
channels := rt.AllChannels()
newRT := NewRoutingTable()
newRT.AddNodes(rt.Nodes()...)
for _, channel := range channels {
newRT.AddChannel(channel.Src, channel.Tgt, channel.Id, channel.Info.Copy())
}
return newRT
}
// String returns string representation of routing table
func (rt *RoutingTable) String() string {
rez := ""
edges := rt.G.GetUndirectedEdges()
for _, edge := range edges {
rez += fmt.Sprintf("%v %v %v", edge.Src, edge.Tgt, edge.Info)
}
return rez
}
// Find k-shortest path between source and destination
func (rt *RoutingTable) KShortestPaths(src, dst graph.Vertex, k int) ([][]graph.Vertex, error) {
return graph.KShortestPaths(rt.G, src, dst, k)
}
// Returns copy of channel info for channel in routing table
func (rt *RoutingTable) GetChannelInfo(id1, id2 graph.Vertex, edgeId graph.EdgeID) (*graph.ChannelInfo, error) {
info, err := rt.G.GetInfo(id1, id2, edgeId)
if err != nil {
return nil, err
}
return info.Copy(), nil
}

@ -1,160 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package rt
import (
"github.com/lightningnetwork/lnd/routing/rt/graph"
"testing"
)
var (
NewID = vertexFromString
)
func vertexFromString(s string) graph.Vertex {
return graph.NewVertex([]byte(s))
}
func edgeIdFromString(s string) graph.EdgeID {
e := graph.EdgeID{}
copy(e.Hash[:], []byte(s))
return e
}
func TestRT(t *testing.T) {
r := NewRoutingTable()
r.AddNodes(vertexFromString("1"), vertexFromString("2"))
r.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil)
if !r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(1, 2) == false, want true`)
}
if !r.HasChannel(vertexFromString("2"), vertexFromString("1"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(2, 1) == false, want true`)
}
}
func TestDiff(t *testing.T) {
r := NewRoutingTable()
buff := r.NewDiffBuff()
// Add 2 channels
r.AddNodes(vertexFromString("1"), vertexFromString("2"), vertexFromString("3"))
r.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil)
r.AddChannel(vertexFromString("2"), vertexFromString("3"), edgeIdFromString("EdgeID"), nil)
r1 := NewRoutingTable()
r1.ApplyDiffBuff(buff)
if !r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(1, 2) == false, want true`)
}
if !r.HasChannel(vertexFromString("2"), vertexFromString("3"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(2, 3) == false, want true`)
}
// Remove channel
r.RemoveChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"))
r1.ApplyDiffBuff(buff)
if r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(1, 2) == true, want false`)
}
if r.HasChannel(vertexFromString("2"), vertexFromString("1"), edgeIdFromString("EdgeID")) {
t.Error(`r.HasChannel(2, 1) == true, want false`)
}
}
func TestRTChannels(t *testing.T) {
rt := NewRoutingTable()
rt.AddNodes(vertexFromString("1"), vertexFromString("2"), vertexFromString("3"))
rt.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil)
rt.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), nil)
channels := rt.AllChannels()
if len(channels) != 2 {
t.Errorf(`rt.AllChannels == %v, want %v`, len(channels), 2)
}
}
func TestRTAddTable(t *testing.T) {
rt1 := NewRoutingTable()
rt1.AddNodes(NewID("0"), NewID("1"), NewID("2"), NewID("3"), NewID("5"), NewID("6"))
rt1.AddChannel(NewID("0"), NewID("1"), edgeIdFromString("EdgeID"), nil)
rt1.AddChannel(NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), nil)
rt2 := NewRoutingTable()
rt2.AddNodes(NewID("0"), NewID("1"), NewID("2"), NewID("3"), NewID("5"), NewID("6"))
rt2.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), nil)
rt2.AddChannel(NewID("5"), NewID("6"), edgeIdFromString("EdgeID"), nil)
rt1.AddTable(rt2)
expectedChannels := [][]graph.Vertex{
[]graph.Vertex{NewID("0"), NewID("1")},
[]graph.Vertex{NewID("1"), NewID("2")},
[]graph.Vertex{NewID("2"), NewID("3")},
[]graph.Vertex{NewID("5"), NewID("6")},
}
for _, c := range expectedChannels {
if !rt1.HasChannel(c[0], c[1], edgeIdFromString("EdgeID")) {
t.Errorf("After addition, channel between %v and %v is absent", c[0], c[1])
}
}
}
func TestChannelInfoCopy(t *testing.T) {
c := &graph.ChannelInfo{Cpt: 10, Wgt: 10}
c1 := c.Copy()
if *c != *c1 {
t.Errorf("*c.Copy() != *c, *c=%v, *c.Copy()=%v", *c, *c1)
}
c2 := (*graph.ChannelInfo)(nil)
c2ans := c2.Copy()
if c2ans != nil {
t.Errorf("c.Copy()=%v, for c=nil, want nil", c2ans)
}
}
func TestRTCopy(t *testing.T) {
r := NewRoutingTable()
// Add 2 channels
r.AddNodes(NewID("1"), NewID("2"), NewID("3"))
r.AddChannel(NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{10, 11})
r.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{20, 21})
// Create copy of this rt and make sure they are equal
rCopy := r.Copy()
channelsExp := []struct {
id1, id2 graph.Vertex
edgeID graph.EdgeID
info *graph.ChannelInfo
isErrNil bool
}{
{NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{10, 11}, true},
{NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{20, 21}, true},
// Channel do not exist
{NewID("3"), NewID("4"), edgeIdFromString("EdgeID"), nil, false},
}
for _, c := range channelsExp {
info, err := rCopy.GetChannelInfo(c.id1, c.id2, c.edgeID)
if (err != nil) && (info != nil) {
t.Errorf("rCopy.GetChannelInfo give not nil result and err")
continue
}
if (err == nil) != c.isErrNil {
t.Errorf("err == nil: %v, want: %v, err: %v", err == nil, c.isErrNil, err)
continue
}
if info == nil && c.info != nil {
t.Errorf("rCopy.GetChannelInfo(%v, %v, %v)==nil, nil. want <not nil>", c.id1, c.id2, c.edgeID)
continue
}
if info != nil && c.info == nil {
t.Errorf("rCopy.GetChannelInfo(%v, %v, %v)==<not nil>, nil. want nil", c.id1, c.id2, c.edgeID)
continue
}
if (info != nil) && c.info != nil && *info != *c.info {
t.Errorf("*rCopy.GetChannelInfo(%v, %v, %v)==%v, nil. want %v", c.id1, c.id2, c.edgeID, *info, c.info)
}
}
}

@ -1,125 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package visualizer
// Configuration used for graph visualisation.
type VisualizerConfig struct {
// General options.
GlobalCfg *GlobalConfig
// Options for node visualisation.
NodeCfg *NodeConfig
// Options ofr highlighted nodes visualisation.
HighlightedNodeCfg *NodeConfig
// Options for edges visualisation.
EdgeCfg *EdgeConfig
// Options for highlighted edges visualisation.
HighlightedEdgeCfg *EdgeConfig
// Indicate whether shortcuts should be used.
// Shortcut of a string is a prefix of a string that is unique
// for a given set of strings.
EnableShortcut bool
}
var DefaultVisualizerConfig VisualizerConfig = VisualizerConfig{
GlobalCfg: &DefaultGlobalConfig,
NodeCfg: &DefaultNodeConfig,
HighlightedNodeCfg: &DefaultHighlightedNodeConfig,
EdgeCfg: &DefaultEdgeConfig,
HighlightedEdgeCfg: &DefaultHighlightedEdgeConfig,
EnableShortcut: false,
}
type GlobalConfig struct {
// Title of an image.
Name string
Dir bool
// Allow multigraph.
Strict bool
// Background color.
BgColor string
}
var DefaultGlobalConfig GlobalConfig = GlobalConfig{
Name: `"Routing Table"`,
Dir: true,
Strict: false, // Allow multigraphs
BgColor: "black",
}
type NodeConfig struct {
Shape string
Style string
FontSize string
FontColor string
Color string
FillColor string
}
var DefaultNodeConfig NodeConfig = NodeConfig{
Shape: "circle",
Style: "filled",
FontSize: "12",
FontColor: "black",
Color: "white",
FillColor: "white",
}
var DefaultHighlightedNodeConfig NodeConfig = NodeConfig{
Shape: "circle",
Style: "filled",
FontSize: "12",
FontColor: "black",
Color: "blue",
FillColor: "blue",
}
type EdgeConfig struct {
FontSize string
FontColor string
Scale string
Dir string
Style string
Color string
}
var DefaultEdgeConfig EdgeConfig = EdgeConfig{
FontSize: "12",
FontColor: "gold",
Scale: "2.5",
Dir: "none",
Style: "solid",
Color: "white",
}
var DefaultHighlightedEdgeConfig EdgeConfig = EdgeConfig{
FontSize: "12",
FontColor: "gold",
Scale: "2.5",
Dir: "none",
Style: "solid",
Color: "blue",
}
func SupportedFormatsAsMap() map[string]struct{} {
rez := make(map[string]struct{})
for _, format := range supportedFormats {
rez[format] = struct{}{}
}
return rez
}
// SupportedFormats contains list of image formats that can be
// used for output.
func SupportedFormats() []string {
return supportedFormats
}
var supportedFormats = []string{
"bmp", "canon", "cgimage", "cmap", "cmapx", "cmapx_np", "dot", "eps", "exr",
"fig", "gif", "gv", "icns", "ico", "imap", "imap_np", "ismap", "jp2", "jpe",
"jpeg", "jpg", "pct", "pdf", "pic", "pict", "plain", "plain-ext", "png",
"pov", "ps", "ps2", "psd", "sgi", "svg", "svgz", "tga", "tif", "tiff", "tk",
"vml", "vmlz", "xdot", "xdot1.2", "xdot1.4",
}

@ -1,116 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package prefix_tree
import "errors"
// Node represents the general interface for node in prefix tree
type Node interface {
Name() string
Len() int
Child(nextSymbol string) Node
Childs() map[string]Node
AddChild(symbol string, child Node) error
Parent() Node
PreviousSymbol() string
IsRoot() bool
IsLeaf() bool
IsTerminal() bool
Terminal()
Path() (string, error)
}
type node struct {
name string
childs map[string]Node
parent Node
previousSymbol string
isTerminal bool
}
// NewNode create new node
func NewNode(name string, parent Node, previousSymbol string, isTerminal bool) Node {
return &node{
name: name,
childs: make(map[string]Node),
parent: parent,
previousSymbol: previousSymbol,
isTerminal: isTerminal,
}
}
func (nd *node) Name() string {
return nd.name
}
func (nd *node) Len() int {
return len(nd.childs)
}
func (nd *node) Child(nextSymbol string) Node {
if _, ok := nd.childs[nextSymbol]; !ok {
return nil
}
return nd.childs[nextSymbol]
}
func (nd *node) Childs() map[string]Node {
return nd.childs
}
func (nd *node) AddChild(symbol string, child Node) error {
if _, ok := nd.childs[symbol]; ok {
return errors.New("Node already exists")
}
nd.childs[symbol] = child
return nil
}
func (nd *node) Parent() Node {
return nd.parent
}
func (nd *node) PreviousSymbol() string {
return nd.previousSymbol
}
func (nd *node) IsRoot() bool {
return nd.parent == nil
}
func (nd *node) IsLeaf() bool {
return len(nd.childs) == 0
}
func (nd *node) IsTerminal() bool {
return nd.isTerminal
}
func (nd *node) Terminal() {
nd.isTerminal = true
}
func (nd *node) Path() (path string, err error) {
var xNode Node = nd
for {
if xNode.IsRoot() {
break
}
path += xNode.PreviousSymbol()
if xNode == xNode.Parent() {
return "", CyclicDataError
}
xNode = xNode.Parent()
}
return Reverse(path), nil
}
func Reverse(reversePath string) (path string) {
length := len(reversePath)
for i := length - 1; i >= 0; i-- {
path += string(reversePath[i])
}
return path
}

@ -1,120 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package prefix_tree
import (
"errors"
"strconv"
)
var (
CyclicDataError = errors.New("Cyclic data")
NodeNotFoundError = errors.New("Node not found")
NotEnoughData = errors.New("Not enough data")
)
type PrefixTree interface {
Add(str string) error
Shortcut(fullname string) (string, error)
Autocomplete(prefix string) (string, error)
Len() int
}
type prefixTree struct {
root Node
nodes int
}
func NewPrefixTree() PrefixTree {
root := NewNode("0", nil, "", false)
return &prefixTree{root: root}
}
func (tree *prefixTree) Add(str string) error {
var node Node = tree.root
for _, symbolRune := range str {
symbol := string(byte(symbolRune))
child := node.Child(symbol)
if child == nil {
child = NewNode(strconv.Itoa(tree.Len()), node, symbol, false)
err := node.AddChild(symbol, child)
if err != nil {
return err
}
tree.nodes++
}
node = child
}
node.Terminal()
return nil
}
// Find shortcut for a given string. Shortcut is unique prefix for a given string.
// If there is only one string in prefix tree return first symbol
func (tree *prefixTree) Shortcut(fullname string) (string, error) {
var node Node = tree.root
for _, symbolRune := range fullname {
symbol := string(byte(symbolRune))
child := node.Child(symbol)
if child == nil {
return "", NodeNotFoundError
}
node = child
}
if !node.IsTerminal() {
return "", errors.New("Node MUST be terminal")
}
if !node.IsLeaf() {
return fullname, nil
}
highest := node
for {
if highest.Parent() == nil || highest.Parent().IsTerminal() || highest.Parent().Len() != 1 {
break
}
if highest == highest.Parent() {
return "", CyclicDataError
}
highest = highest.Parent()
}
path, err := highest.Path()
if err != nil {
return "", err
}
if path == "" {
return fullname[0:1], nil
}
return path, nil
}
func (tree *prefixTree) Autocomplete(prefix string) (string, error) {
var node Node = tree.root
for _, symbolRune := range prefix {
symbol := string(byte(symbolRune))
child := node.Child(symbol)
if child == nil {
return "", NodeNotFoundError
}
node = child
}
for !node.IsLeaf() {
if node.IsTerminal() || node.Len() != 1 {
return "", NotEnoughData
}
childs := node.Childs()
for _, value := range childs {
node = value
}
}
path, err := node.Path()
if err != nil {
return "", err
}
return path, nil
}
func (tree *prefixTree) Len() int {
return tree.nodes
}

@ -1,70 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package prefix_tree
import "testing"
func TestPrefixTree(t *testing.T) {
pt := NewPrefixTree()
pt.Add("walk")
pt.Add("hello")
pt.Add("hi")
pt.Add("hell")
pt.Add("world")
pt.Add("www")
shortcut, _ := pt.Shortcut("world")
if expected := "wo"; shortcut != expected {
t.Errorf("expected: %s, actual: %s", expected, shortcut)
}
shortcut, _ = pt.Shortcut("www")
if expected := "ww"; shortcut != expected {
t.Errorf("expected: %s, actual: %s", expected, shortcut)
}
autocompleted, _ := pt.Autocomplete("wo")
if expected := "world"; autocompleted != expected {
t.Errorf("expected: %s, actual: %s", expected, autocompleted)
}
autocompleted, _ = pt.Autocomplete("ww")
if expected := "www"; autocompleted != expected {
t.Errorf("expected: %s, actual: %s", expected, autocompleted)
}
pt.Add("123")
pt.Add("456")
pt.Add("1234")
shortcut, _ = pt.Shortcut("123")
if expected := "123"; shortcut != expected {
t.Errorf("expected: %s, actual: %s", expected, shortcut)
}
}
func TestPrefixTreeOneNode(t *testing.T) {
pt := NewPrefixTree()
pt.Add("123")
shortcut, err := pt.Shortcut("123")
if err != nil {
t.Errorf("error getting shortcut for 123: %v, want: %v", err, nil)
}
expectedShortcut := "1"
if shortcut != expectedShortcut {
t.Errorf("expected: %v, actual: %v", expectedShortcut, shortcut)
}
expectedAutocomplete := "123"
autocomplete, err := pt.Autocomplete("123")
if err != nil {
t.Errorf("error getting autocomplete for 123: %v, want: %v", err, nil)
}
if autocomplete != expectedAutocomplete {
t.Errorf("expected: %v, actual: %v", expectedAutocomplete, autocomplete)
}
}

@ -1,174 +0,0 @@
// Copyright (c) 2016 Bitfury Group Limited
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php
package visualizer
import (
"os"
"os/exec"
"path/filepath"
"github.com/lightningnetwork/lnd/routing/rt/visualizer/prefix_tree"
"github.com/lightningnetwork/lnd/routing/rt/graph"
"github.com/awalterschulze/gographviz"
"encoding/hex"
)
type visualizer struct {
// Graph used for visualisation.
G *graph.Graph
// Vertexes which should be highlighted.
HighlightedNodes []graph.Vertex
// Edges which should be highlighted.
HighlightedEdges []graph.Edge
// Configuration parameters used for visualisation.
Config *VisualizerConfig
// Function applied to node to obtain its label
ApplyToNode func(graph.Vertex) string
// Function applied to edge to obtain its label
ApplyToEdge func(*graph.ChannelInfo) string
// Prefix used for creating shortcuts.
pt prefix_tree.PrefixTree
graphviz *gographviz.Graph
}
// New creates new visualiser.
func New(g *graph.Graph, highlightedNodes []graph.Vertex,
highlightedEdges []graph.Edge, config *VisualizerConfig) *visualizer {
if config == nil {
config = &DefaultVisualizerConfig
}
return &visualizer{
G: g,
HighlightedNodes: highlightedNodes,
HighlightedEdges: highlightedEdges,
Config: config,
ApplyToNode: func(v graph.Vertex) string { return hex.EncodeToString(v.ToByte()) },
ApplyToEdge: func(info *graph.ChannelInfo) string { return "nil" },
pt: prefix_tree.NewPrefixTree(),
graphviz: gographviz.NewGraph(),
}
}
// EnableShortcut enables/disables shortcuts.
// Shortcut is a small unique string used for labeling.
func (viz *visualizer) EnableShortcut(value bool) {
viz.Config.EnableShortcut = value
}
// BuildPrefixTree builds prefix tree for nodes.
// It is needed for shortcuts.
func (viz *visualizer) BuildPrefixTree() {
for _, node := range viz.G.GetVertexes() {
id := viz.ApplyToNode(node)
viz.pt.Add(id)
}
}
// Draw creates graph representation in Graphviz dot language.
func (viz *visualizer) Draw() string {
viz.base()
viz.drawNodes()
viz.drawEdges()
return viz.graphviz.String()
}
// Base makes initialization.
func (viz *visualizer) base() {
viz.graphviz.SetName(viz.Config.GlobalCfg.Name)
viz.graphviz.SetDir(viz.Config.GlobalCfg.Dir)
viz.graphviz.SetStrict(viz.Config.GlobalCfg.Strict)
// TODO(evg): use viz.Add(...) instead viz.Attrs.Add(...)
viz.graphviz.Attrs.Add("bgcolor", viz.Config.GlobalCfg.BgColor)
}
func (viz *visualizer) drawNodes() {
for _, node := range viz.G.GetVertexes() {
if viz.isHighlightedNode(node) {
viz.drawNode(node, viz.Config.HighlightedNodeCfg)
} else {
viz.drawNode(node, viz.Config.NodeCfg)
}
}
}
func (viz *visualizer) drawNode(node graph.Vertex, cfg *NodeConfig) {
id := viz.ApplyToNode(node)
if viz.Config.EnableShortcut {
// TODO(evg): processing errors
id, _ = viz.pt.Shortcut(id)
}
attrs := gographviz.Attrs{
"shape": cfg.Shape,
"style": cfg.Style,
"fontsize": cfg.FontSize,
"fontcolor": cfg.FontColor,
"color": cfg.Color,
"fillcolor": cfg.FillColor,
}
viz.graphviz.AddNode(viz.Config.GlobalCfg.Name, id, attrs)
}
func (viz *visualizer) isHighlightedNode(node graph.Vertex) bool {
for _, value := range viz.HighlightedNodes {
if node.String() == value.String() {
return true
}
}
return false
}
func (viz *visualizer) drawEdges() {
for _, edge := range viz.G.GetUndirectedEdges() {
if viz.isHighlightedEdge(edge) {
viz.drawEdge(edge, viz.Config.HighlightedEdgeCfg)
} else {
viz.drawEdge(edge, viz.Config.EdgeCfg)
}
}
}
func (viz *visualizer) drawEdge(edge graph.Edge, cfg *EdgeConfig) {
src := viz.ApplyToNode(edge.Src)
tgt := viz.ApplyToNode(edge.Tgt)
if viz.Config.EnableShortcut {
// TODO(evg): processing errors
src, _ = viz.pt.Shortcut(src)
tgt, _ = viz.pt.Shortcut(tgt)
}
attrs := gographviz.Attrs{
"fontsize": cfg.FontSize,
"fontcolor": cfg.FontColor,
"labeldistance": cfg.Scale,
"dir": cfg.Dir,
"style": cfg.Style,
"color": cfg.Color,
"label": viz.ApplyToEdge(edge.Info),
}
viz.graphviz.AddEdge(src, tgt, true, attrs)
}
func (viz *visualizer) isHighlightedEdge(edge graph.Edge) bool {
for _, value := range viz.G.GetEdges() {
if edge == value {
return true
}
}
return false
}
// Run graphviz command line utility (such as neato).
// Used for creation image from textual graph representation.
func Run(utility string, TempFile, ImageFile *os.File) error {
extension := filepath.Ext(ImageFile.Name())[1:]
_, err := exec.Command(utility, "-T"+extension, "-o"+ImageFile.Name(), TempFile.Name()).Output()
return err
}
// Opens file in a command line open program.
// Used for displaying graphical files.
func Open(ImageFile *os.File) error {
_, err := exec.Command("open", ImageFile.Name()).Output()
return err
}

121
routing/testdata/basic_graph.json vendored Normal file

@ -0,0 +1,121 @@
{
"info": [
"This file encodes a basic graph that resembles the following ascii graph:",
"",
" 50k satoshis ┌──────┐ ",
" ┌───────────────────▶│luo ji│◀─┐ ",
" │ └──────┘ │ ",
" │ │ ",
" │ │ ",
" │ │ ",
" │ │ ",
" │ │ ",
" ▼ │ ┌──────┐ ",
" ┌────────┐ │ │sophon│◀┐ ",
" │satoshi │ │ └──────┘ │ ",
" └────────┘ │ │ ",
" ▲ │ │ 500 satoshis ",
" │ ┌───────────────────┘ │ ",
" │ │ 100k satoshis │ ",
" │ │ │ ",
" │ │ │ ┌────────┐ ",
" └──────────┤ └─▶│son goku│ ",
" 10k satoshis │ └────────┘ ",
" │ ▲ ",
" │ │ ",
" │ │ ",
" ▼ │ ",
" ┌──────────┐ │ ",
" │ roasbeef │◀─────────────────────────────────────┘ ",
" └──────────┘ 100k satoshis "
],
"nodes": [
{
"source": true,
"pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"alias": "roasbeef"
},
{
"source": false,
"pubkey": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"alias": "songoku"
},
{
"source": false,
"pubkey": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"alias": "satoshi"
},
{
"source": false,
"pubkey": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"alias": "luoji"
},
{
"source": false,
"pubkey": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"alias": "sophon"
}
],
"edges": [
{
"node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"node_2": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"channel_id": 12345,
"channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"flags": 0,
"expiry": 1,
"min_htlc": 1,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add",
"node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb",
"channel_id": 11111,
"channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0",
"flags": 0,
"expiry": 1,
"min_htlc": 1,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 500
},
{
"node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 11111,
"channel_point": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793:0",
"flags": 0,
"expiry": 1,
"min_htlc": 1,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 10000
},
{
"node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"node_2": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"channel_id": 11111,
"channel_point": "25376aa6cb81913ad30416bd22d4083241bd6d68e811d0284d3c3a17795c458a:0",
"flags": 0,
"expiry": 1,
"min_htlc": 1,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd",
"node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99",
"channel_id": 11111,
"channel_point": "704a5675c91b1c674309a6475fc51072c2913d6117ee6103c9f1b86956bcbe02:0",
"flags": 0,
"expiry": 1,
"min_htlc": 1,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 50000
}
]
}