routing: modify path finding routines use new EdgeInfo/EdgePolicy

This commit modifies the path finding routines to properly use the new
channel edge related API exposed by the database. Additionally, a new
type `ChannelHop` has been introduced which couples an edges routing
policy with the capacity and origin chain of the channel.
This commit is contained in:
Olaoluwa Osuntokun 2017-03-08 14:24:59 -08:00
parent 43f3b6bebe
commit 7bdf02bc9e
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
4 changed files with 86 additions and 29 deletions

@ -1075,7 +1075,7 @@ type ChannelAuthProof struct {
BitcoinSig1 *btcec.Signature BitcoinSig1 *btcec.Signature
// BitcoinSig2 is the signature using the public key of the second node // BitcoinSig2 is the signature using the public key of the second node
// that was used in the channel's mult-sig output. // that was used in the channel's multi-sig output.
BitcoinSig2 *btcec.Signature BitcoinSig2 *btcec.Signature
} }

@ -1081,8 +1081,8 @@ func newChanAnnouncement(localIdentity, remotePub *btcec.PublicKey,
Timestamp: uint32(time.Now().Unix()), Timestamp: uint32(time.Now().Unix()),
Flags: chanFlags, Flags: chanFlags,
TimeLockDelta: 1, TimeLockDelta: 1,
HtlcMinimumMstat: 0, HtlcMinimumMsat: 0,
FeeBaseMstat: 0, FeeBaseMsat: 0,
FeeProportionalMillionths: 0, FeeProportionalMillionths: 0,
} }

@ -5,6 +5,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
) )
@ -51,14 +52,33 @@ type Route struct {
Hops []*Hop Hops []*Hop
} }
// ChannelHop is an intermediate hop within the network with a greater
// multi-hop payment route. This struct contains the relevant routing policy of
// the particular edge, as well as the total capacity, and origin chain of the
// channel itself.
type ChannelHop struct {
// Capacity is the total capacity of the channel being traversed. This
// value is expressed for stability in satoshis.
Capacity btcutil.Amount
// Chain is a 32-byte has that denotes the base blockchain network of
// the channel. The 32-byte hash is the "genesis" block of the
// blockchain, or the very first block in the chain.
//
// TODO(roasbeef): store chain within edge info/policy in database.
Chain chainhash.Hash
*channeldb.ChannelEdgePolicy
}
// Hop represents the forwarding details at a particular position within the // Hop represents the forwarding details at a particular position within the
// final route. This struct houses the values necessary to create the HTLC // 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 // which will travel along this hop, and also encode the per-hop payload
// included within the Sphinx packet. // included within the Sphinx packet.
type Hop struct { type Hop struct {
// Channels is the active payment channel that this hop will travel // Channel is the active payment channel edge that this hop will travel
// along. // along.
Channel *channeldb.ChannelEdge Channel *ChannelHop
// TimeLockDelta is the delta that this hop will subtract from the HTLC // TimeLockDelta is the delta that this hop will subtract from the HTLC
// before extending it to the next hop in the route. // before extending it to the next hop in the route.
@ -78,7 +98,7 @@ type Hop struct {
// computeFee computes the fee to forward an HTLC of `amt` satoshis over the // computeFee computes the fee to forward an HTLC of `amt` satoshis over the
// passed active payment channel. This value is currently computed as specified // passed active payment channel. This value is currently computed as specified
// in BOLT07, but will likely change in the near future. // in BOLT07, but will likely change in the near future.
func computeFee(amt btcutil.Amount, edge *channeldb.ChannelEdge) btcutil.Amount { func computeFee(amt btcutil.Amount, edge *ChannelHop) btcutil.Amount {
return edge.FeeBaseMSat + (amt*edge.FeeProportionalMillionths)/1000000 return edge.FeeBaseMSat + (amt*edge.FeeProportionalMillionths)/1000000
} }
@ -94,7 +114,7 @@ func newRoute(amtToSend btcutil.Amount, source, target vertex,
// the prevHop map to unravel the path. We end up with a list of edges // 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 // in the reverse direction which we'll use to properly calculate the
// timelock and fee values. // timelock and fee values.
pathEdges := make([]*channeldb.ChannelEdge, 0, len(prevHop)) pathEdges := make([]*ChannelHop, 0, len(prevHop))
prev := target prev := target
for prev != source { // TODO(roasbeef): assumes no cycles for prev != source { // TODO(roasbeef): assumes no cycles
// Add the current hop to the limit of path edges then walk // Add the current hop to the limit of path edges then walk
@ -130,7 +150,7 @@ func newRoute(amtToSend btcutil.Amount, source, target vertex,
Channel: edge, Channel: edge,
AmtToForward: runningAmt, AmtToForward: runningAmt,
Fee: computeFee(runningAmt, edge), Fee: computeFee(runningAmt, edge),
TimeLockDelta: edge.Expiry, TimeLockDelta: edge.TimeLockDelta,
} }
edge.Node.PubKey.Curve = nil edge.Node.PubKey.Curve = nil
@ -198,7 +218,7 @@ type nodeWithDist struct {
// edgeWithPrev is a helper struct used in path finding that couples an // edgeWithPrev is a helper struct used in path finding that couples an
// directional edge with the node's ID in the opposite direction. // directional edge with the node's ID in the opposite direction.
type edgeWithPrev struct { type edgeWithPrev struct {
edge *channeldb.ChannelEdge edge *ChannelHop
prevNode *btcec.PublicKey prevNode *btcec.PublicKey
} }
@ -208,8 +228,8 @@ type edgeWithPrev struct {
// should be tuned with experimental and empirical data. // should be tuned with experimental and empirical data.
// //
// TODO(roasbeef): compute robust weight metric // TODO(roasbeef): compute robust weight metric
func edgeWeight(e *channeldb.ChannelEdge) float64 { func edgeWeight(e *channeldb.ChannelEdgePolicy) float64 {
return float64(1 + e.Expiry) return float64(1 + e.TimeLockDelta)
} }
// findRoute attempts to find a path from the source node within the // findRoute attempts to find a path from the source node within the
@ -292,8 +312,9 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
} }
} }
// If we've reached our target, then we're done here and can // If we've reached our target (or we don't have any outgoing
// exit the graph traversal early. // edges), then we're done here and can exit the graph
// traversal early.
if bestNode == nil || bestNode.PubKey.IsEqual(target) { if bestNode == nil || bestNode.PubKey.IsEqual(target) {
break break
} }
@ -302,7 +323,9 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
// examine all the outgoing edge (channels) from this node to // examine all the outgoing edge (channels) from this node to
// further our graph traversal. // further our graph traversal.
pivot := newVertex(bestNode.PubKey) pivot := newVertex(bestNode.PubKey)
err := bestNode.ForEachChannel(nil, func(edge *channeldb.ChannelEdge) error { err := bestNode.ForEachChannel(nil, func(edgeInfo *channeldb.ChannelEdgeInfo,
edge *channeldb.ChannelEdgePolicy) error {
// Compute the tentative distance to this new // Compute the tentative distance to this new
// channel/edge which is the distance to our current // channel/edge which is the distance to our current
// pivot node plus the weight of this edge. // pivot node plus the weight of this edge.
@ -316,14 +339,15 @@ func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey,
// * also add min payment? // * also add min payment?
v := newVertex(edge.Node.PubKey) v := newVertex(edge.Node.PubKey)
if tempDist < distance[v].dist { if tempDist < distance[v].dist {
// TODO(roasbeef): unconditionally add for all
// paths
distance[v] = nodeWithDist{ distance[v] = nodeWithDist{
dist: tempDist, dist: tempDist,
node: edge.Node, node: edge.Node,
} }
prev[v] = edgeWithPrev{ prev[v] = edgeWithPrev{
edge: edge, edge: &ChannelHop{
ChannelEdgePolicy: edge,
Capacity: edgeInfo.Capacity,
},
prevNode: bestNode.PubKey, prevNode: bestNode.PubKey,
} }
} }

@ -5,12 +5,15 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io/ioutil" "io/ioutil"
"math/big"
"net" "net"
"os" "os"
"strings" "strings"
"testing" "testing"
"time" "time"
prand "math/rand"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/chaincfg/chainhash"
@ -22,11 +25,34 @@ const (
// basicGraphFilePath is the file path for a basic graph used within // basicGraphFilePath is the file path for a basic graph used within
// the tests. The basic graph consists of 5 nodes with 5 channels // the tests. The basic graph consists of 5 nodes with 5 channels
// connecting them. // connecting them.
basicGraphFilePath = "testdata/basic_graph.json" basicGraphFilePath = "testdata/basic_graph.json"
// excessiveHopsGraphFilePath is a file path which stores the JSON dump
// of a graph which was previously triggering an erroneous excessive
// hops error. The error has since been fixed, but a test case
// exercising it is kept around to guard against regressions.
excessiveHopsGraphFilePath = "testdata/excessive_hops.json" excessiveHopsGraphFilePath = "testdata/excessive_hops.json"
) )
// testGraph is the struct which coresponds to the JSON format used to encode var (
randSource = prand.NewSource(time.Now().Unix())
randInts = prand.New(randSource)
testSig = &btcec.Signature{
R: new(big.Int),
S: new(big.Int),
}
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
testAuthProof = channeldb.ChannelAuthProof{
NodeSig1: testSig,
NodeSig2: testSig,
BitcoinSig1: testSig,
BitcoinSig2: testSig,
}
)
// testGraph is the struct which corresponds to the JSON format used to encode
// graphs within the files in the testdata directory. // graphs within the files in the testdata directory.
// //
// TODO(roasbeef): add test graph auto-generator // TODO(roasbeef): add test graph auto-generator
@ -213,20 +239,27 @@ func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, err
// We first insert the existence of the edge between the two // We first insert the existence of the edge between the two
// nodes. // nodes.
if err := graph.AddChannelEdge(node1Pub, node2Pub, &fundingPoint, edgeInfo := channeldb.ChannelEdgeInfo{
edge.ChannelID); err != nil { ChannelID: edge.ChannelID,
NodeKey1: node1Pub,
NodeKey2: node2Pub,
BitcoinKey1: node1Pub,
BitcoinKey2: node2Pub,
AuthProof: &testAuthProof,
ChannelPoint: fundingPoint,
Capacity: btcutil.Amount(edge.Capacity),
}
if err := graph.AddChannelEdge(&edgeInfo); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
edge := &channeldb.ChannelEdge{ edgePolicy := &channeldb.ChannelEdgePolicy{
ChannelID: edge.ChannelID, ChannelID: edge.ChannelID,
ChannelPoint: fundingPoint,
LastUpdate: time.Now(), LastUpdate: time.Now(),
Expiry: edge.Expiry, TimeLockDelta: edge.Expiry,
MinHTLC: btcutil.Amount(edge.MinHTLC), MinHTLC: btcutil.Amount(edge.MinHTLC),
FeeBaseMSat: btcutil.Amount(edge.FeeBaseMsat), FeeBaseMSat: btcutil.Amount(edge.FeeBaseMsat),
FeeProportionalMillionths: btcutil.Amount(edge.FeeRate), FeeProportionalMillionths: btcutil.Amount(edge.FeeRate),
Capacity: btcutil.Amount(edge.Capacity),
} }
// As the graph itself is directed, we need to insert two edges // As the graph itself is directed, we need to insert two edges
@ -234,13 +267,13 @@ func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, err
// node2->node1. A flag of 0 indicates this is the routing // node2->node1. A flag of 0 indicates this is the routing
// policy for the first node, and a flag of 1 indicates its the // policy for the first node, and a flag of 1 indicates its the
// information for the second node. // information for the second node.
edge.Flags = 0 edgePolicy.Flags = 0
if err := graph.UpdateEdgeInfo(edge); err != nil { if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
edge.Flags = 1 edgePolicy.Flags = 1
if err := graph.UpdateEdgeInfo(edge); err != nil { if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
} }