274 lines
6.8 KiB
Go
274 lines
6.8 KiB
Go
package routing
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
|
)
|
|
|
|
// createPubkey return a new test pubkey.
|
|
func createPubkey(id byte) route.Vertex {
|
|
pubkey := route.Vertex{id}
|
|
return pubkey
|
|
}
|
|
|
|
// mockChannel holds the channel state of a channel in the mock graph.
|
|
type mockChannel struct {
|
|
id uint64
|
|
capacity btcutil.Amount
|
|
balance lnwire.MilliSatoshi
|
|
}
|
|
|
|
// mockNode holds a set of mock channels and routing policies for a node in the
|
|
// mock graph.
|
|
type mockNode struct {
|
|
channels map[route.Vertex]*mockChannel
|
|
baseFee lnwire.MilliSatoshi
|
|
pubkey route.Vertex
|
|
}
|
|
|
|
// newMockNode instantiates a new mock node with a newly generated pubkey.
|
|
func newMockNode(id byte) *mockNode {
|
|
pubkey := createPubkey(id)
|
|
return &mockNode{
|
|
channels: make(map[route.Vertex]*mockChannel),
|
|
pubkey: pubkey,
|
|
}
|
|
}
|
|
|
|
// fwd simulates an htlc forward through this node. If the from parameter is
|
|
// nil, this node is considered to be the sender of the payment. The route
|
|
// parameter describes the remaining route from this node onwards. If route.next
|
|
// is nil, this node is the final hop.
|
|
func (m *mockNode) fwd(from *mockNode, route *hop) (htlcResult, error) {
|
|
next := route.next
|
|
|
|
// Get the incoming channel, if any.
|
|
var inChan *mockChannel
|
|
if from != nil {
|
|
inChan = m.channels[from.pubkey]
|
|
}
|
|
|
|
// If there is no next node, this is the final node and we can settle the htlc.
|
|
if next == nil {
|
|
// Update the incoming balance.
|
|
inChan.balance += route.amtToFwd
|
|
|
|
return htlcResult{}, nil
|
|
}
|
|
|
|
// Check if the outgoing channel has enough balance.
|
|
outChan, ok := m.channels[next.node.pubkey]
|
|
if !ok {
|
|
return htlcResult{},
|
|
fmt.Errorf("%v: unknown next %v",
|
|
m.pubkey, next.node.pubkey)
|
|
}
|
|
if outChan.balance < route.amtToFwd {
|
|
return htlcResult{
|
|
failureSource: m.pubkey,
|
|
failure: lnwire.NewTemporaryChannelFailure(nil),
|
|
}, nil
|
|
}
|
|
|
|
// Htlc can be forwarded, update channel balances.
|
|
outChan.balance -= route.amtToFwd
|
|
if inChan != nil {
|
|
inChan.balance += route.amtToFwd
|
|
}
|
|
|
|
// Recursively forward down the given route.
|
|
result, err := next.node.fwd(m, route.next)
|
|
if err != nil {
|
|
return htlcResult{}, err
|
|
}
|
|
|
|
// Revert balances when a failure occurs.
|
|
if result.failure != nil {
|
|
outChan.balance += route.amtToFwd
|
|
if inChan != nil {
|
|
inChan.balance -= route.amtToFwd
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// mockGraph contains a set of nodes that together for a mocked graph.
|
|
type mockGraph struct {
|
|
t *testing.T
|
|
nodes map[route.Vertex]*mockNode
|
|
source *mockNode
|
|
}
|
|
|
|
// newMockGraph instantiates a new mock graph.
|
|
func newMockGraph(t *testing.T) *mockGraph {
|
|
return &mockGraph{
|
|
nodes: make(map[route.Vertex]*mockNode),
|
|
t: t,
|
|
}
|
|
}
|
|
|
|
// addNode adds the given mock node to the network.
|
|
func (m *mockGraph) addNode(node *mockNode) {
|
|
m.t.Helper()
|
|
|
|
if _, exists := m.nodes[node.pubkey]; exists {
|
|
m.t.Fatal("node already exists")
|
|
}
|
|
m.nodes[node.pubkey] = node
|
|
}
|
|
|
|
// addChannel adds a new channel between two existing nodes on the network. It
|
|
// sets the channel balance to 50/50%.
|
|
//
|
|
// Ignore linter error because addChannel isn't yet called with different
|
|
// capacities.
|
|
// nolint:unparam
|
|
func (m *mockGraph) addChannel(id uint64, node1id, node2id byte,
|
|
capacity btcutil.Amount) {
|
|
|
|
node1pubkey := createPubkey(node1id)
|
|
node2pubkey := createPubkey(node2id)
|
|
|
|
if _, exists := m.nodes[node1pubkey].channels[node2pubkey]; exists {
|
|
m.t.Fatal("channel already exists")
|
|
}
|
|
if _, exists := m.nodes[node2pubkey].channels[node1pubkey]; exists {
|
|
m.t.Fatal("channel already exists")
|
|
}
|
|
|
|
m.nodes[node1pubkey].channels[node2pubkey] = &mockChannel{
|
|
capacity: capacity,
|
|
id: id,
|
|
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
|
}
|
|
m.nodes[node2pubkey].channels[node1pubkey] = &mockChannel{
|
|
capacity: capacity,
|
|
id: id,
|
|
balance: lnwire.NewMSatFromSatoshis(capacity / 2),
|
|
}
|
|
}
|
|
|
|
// forEachNodeChannel calls the callback for every channel of the given node.
|
|
//
|
|
// NOTE: Part of the routingGraph interface.
|
|
func (m *mockGraph) forEachNodeChannel(nodePub route.Vertex,
|
|
cb func(*channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy,
|
|
*channeldb.ChannelEdgePolicy) error) error {
|
|
|
|
// Look up the mock node.
|
|
node, ok := m.nodes[nodePub]
|
|
if !ok {
|
|
return channeldb.ErrGraphNodeNotFound
|
|
}
|
|
|
|
// Iterate over all of its channels.
|
|
for peer, channel := range node.channels {
|
|
// Lexicographically sort the pubkeys.
|
|
var node1, node2 route.Vertex
|
|
if bytes.Compare(nodePub[:], peer[:]) == -1 {
|
|
node1, node2 = peer, nodePub
|
|
} else {
|
|
node1, node2 = nodePub, peer
|
|
}
|
|
|
|
peerNode := m.nodes[peer]
|
|
|
|
// Call the per channel callback.
|
|
err := cb(
|
|
&channeldb.ChannelEdgeInfo{
|
|
NodeKey1Bytes: node1,
|
|
NodeKey2Bytes: node2,
|
|
},
|
|
&channeldb.ChannelEdgePolicy{
|
|
ChannelID: channel.id,
|
|
Node: &channeldb.LightningNode{
|
|
PubKeyBytes: peer,
|
|
Features: lnwire.EmptyFeatureVector(),
|
|
},
|
|
FeeBaseMSat: node.baseFee,
|
|
},
|
|
&channeldb.ChannelEdgePolicy{
|
|
ChannelID: channel.id,
|
|
Node: &channeldb.LightningNode{
|
|
PubKeyBytes: nodePub,
|
|
Features: lnwire.EmptyFeatureVector(),
|
|
},
|
|
FeeBaseMSat: peerNode.baseFee,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// sourceNode returns the source node of the graph.
|
|
//
|
|
// NOTE: Part of the routingGraph interface.
|
|
func (m *mockGraph) sourceNode() route.Vertex {
|
|
return m.source.pubkey
|
|
}
|
|
|
|
// fetchNodeFeatures returns the features of the given node.
|
|
//
|
|
// NOTE: Part of the routingGraph interface.
|
|
func (m *mockGraph) fetchNodeFeatures(nodePub route.Vertex) (
|
|
*lnwire.FeatureVector, error) {
|
|
|
|
return lnwire.EmptyFeatureVector(), nil
|
|
}
|
|
|
|
// htlcResult describes the resolution of an htlc. If failure is nil, the htlc
|
|
// was settled.
|
|
type htlcResult struct {
|
|
failureSource route.Vertex
|
|
failure lnwire.FailureMessage
|
|
}
|
|
|
|
// hop describes one hop of a route.
|
|
type hop struct {
|
|
node *mockNode
|
|
amtToFwd lnwire.MilliSatoshi
|
|
next *hop
|
|
}
|
|
|
|
// sendHtlc sends out an htlc on the mock network and synchronously returns the
|
|
// final resolution of the htlc.
|
|
func (m *mockGraph) sendHtlc(route *route.Route) (htlcResult, error) {
|
|
var next *hop
|
|
|
|
// Convert the route into a structure that is suitable for recursive
|
|
// processing.
|
|
for i := len(route.Hops) - 1; i >= 0; i-- {
|
|
routeHop := route.Hops[i]
|
|
node := m.nodes[routeHop.PubKeyBytes]
|
|
next = &hop{
|
|
node: node,
|
|
next: next,
|
|
amtToFwd: routeHop.AmtToForward,
|
|
}
|
|
}
|
|
|
|
// Create the starting hop instance.
|
|
source := m.nodes[route.SourcePubKey]
|
|
next = &hop{
|
|
node: source,
|
|
next: next,
|
|
amtToFwd: route.TotalAmount,
|
|
}
|
|
|
|
// Recursively walk the path and obtain the htlc resolution.
|
|
return source.fwd(nil, next)
|
|
}
|
|
|
|
// Compile-time check for the routingGraph interface.
|
|
var _ routingGraph = &mockGraph{}
|