<commit_before>package routing

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"io/ioutil"
	"math/big"
	"net"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcd/chaincfg/chainhash"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"

	prand "math/rand"
)

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"

	// 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"
)

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.
//
// 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      int64  `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.
	var testAddrs []net.Addr
	testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888")
	if err != nil {
		return nil, nil, nil, err
	}
	testAddrs = append(testAddrs, testAddr)

	// 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{
			HaveNodeAnnouncement: true,
			AuthSig:              testSig,
			LastUpdate:           time.Now(),
			Addresses:            testAddrs,
			PubKey:               pub,
			Alias:                node.Alias,
			Features:             testFeatures,
		}

		// 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!")
		}

		// 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
		}
	}

	if source != nil {
		// 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 := chainhash.NewHashFromStr(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.
		edgeInfo := channeldb.ChannelEdgeInfo{
			ChannelID:    edge.ChannelID,
			NodeKey1:     node1Pub,
			NodeKey2:     node2Pub,
			BitcoinKey1:  node1Pub,
			BitcoinKey2:  node2Pub,
			AuthProof:    &testAuthProof,
			ChannelPoint: fundingPoint,
			Capacity:     btcutil.Amount(edge.Capacity),
		}
		err = graph.AddChannelEdge(&edgeInfo)
		if err != nil && err != channeldb.ErrEdgeAlreadyExist {
			return nil, nil, nil, err
		}

		edgePolicy := &channeldb.ChannelEdgePolicy{
			Signature:                 testSig,
			Flags:                     edge.Flags,
			ChannelID:                 edge.ChannelID,
			LastUpdate:                time.Now(),
			TimeLockDelta:             edge.Expiry,
			MinHTLC:                   lnwire.MilliSatoshi(edge.MinHTLC),
			FeeBaseMSat:               lnwire.MilliSatoshi(edge.FeeBaseMsat),
			FeeProportionalMillionths: lnwire.MilliSatoshi(edge.FeeRate),
		}
		if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
			return nil, nil, nil, err
		}
	}

	return graph, cleanUp, aliasMap, nil
}

func TestBasicGraphPathFinding(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}
	sourceVertex := newVertex(sourceNode.PubKey)

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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 startingHeight = 100

	paymentAmt := lnwire.NewMSatFromSatoshis(100)
	target := aliases["sophon"]
	path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find path: %v", err)
	}
	route, err := newRoute(paymentAmt, sourceVertex, path, startingHeight)
	if err != nil {
		t.Fatalf("unable to create path: %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 two more than our starting block
	// height.
	if route.TotalTimeLock != 102 {
		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)
	}

	// 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, we'll assert that the "next hop" field in each route payload
	// properly points to the channel ID that the HTLC should be forwarded
	// along.
	hopPayloads := route.ToHopPayloads()
	if len(hopPayloads) != 2 {
		t.Fatalf("incorrect number of hop payloads: expected %v, got %v",
			2, len(hopPayloads))
	}

	// The first hop should point to the second hop.
	var expectedHop [8]byte
	binary.BigEndian.PutUint64(expectedHop[:], route.Hops[1].Channel.ChannelID)
	if !bytes.Equal(hopPayloads[0].NextAddress[:], expectedHop[:]) {
		t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
			expectedHop[:], hopPayloads[0].NextAddress)
	}

	// The second hop should have a next hop value of all zeroes in order
	// to indicate it's the exit hop.
	var exitHop [8]byte
	if !bytes.Equal(hopPayloads[1].NextAddress[:], exitHop[:]) {
		t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
			exitHop[:], hopPayloads[0].NextAddress)
	}

	// We'll also assert that the outgoing CLTV value for each hop was set
	// accordingly.
	if route.Hops[0].OutgoingTimeLock != 101 {
		t.Fatalf("expected outgoing time-lock of %v, instead have %v",
			1, route.Hops[0].OutgoingTimeLock)
	}
	if route.Hops[1].OutgoingTimeLock != 101 {
		t.Fatalf("outgoing time-lock for final hop is incorrect: "+
			"expected %v, got %v", 1, route.Hops[1].OutgoingTimeLock)
	}

	// Additionally, we'll ensure that the amount to forward, and fees
	// computed for each hop are correct.
	firstHopFee := route.Hops[0].Channel.FeeBaseMSat
	if route.TotalAmount != paymentAmt+firstHopFee {
		t.Fatalf("first hop forwarding amount incorrect: expected %v, got %v",
			paymentAmt+firstHopFee, route.Hops[0].AmtToForward)
	}
	if route.Hops[0].Fee != firstHopFee {
		t.Fatalf("first hop fee incorrect: expected %v, got %v",
			firstHopFee, route.Hops[0].Fee)
	}
	if route.Hops[1].AmtToForward != paymentAmt {
		t.Fatalf("second hop forwarding amount incorrect: expected %v, got %v",
			paymentAmt+firstHopFee, route.Hops[0].AmtToForward)
	}
	if route.Hops[1].Fee != 0 {
		t.Fatalf("second hop fee incorrect: expected %v, got %v",
			0, route.Hops[1].Fee)
	}

	// 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"]
	path, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find route: %v", err)
	}
	route, err = newRoute(paymentAmt, sourceVertex, path, startingHeight)
	if err != nil {
		t.Fatalf("unable to create path: %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 the current block height plus one.
	if route.TotalTimeLock != 101 {
		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 TestKShortestPathFinding(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	// In this test we'd like to ensure that our algoirthm to find the
	// k-shortest paths from a given source node to any destination node
	// works as exepcted.

	// In our basic_graph.json, there exist two paths from roasbeef to luo
	// ji. Our algorithm should properly find both paths, and also rank
	// them in order of their total "distance".

	paymentAmt := lnwire.NewMSatFromSatoshis(100)
	target := aliases["luoji"]
	paths, err := findPaths(nil, graph, sourceNode, target, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find paths between roasbeef and "+
			"luo ji: %v", err)
	}

	// The algorithm should've found two paths from roasbeef to luo ji.
	if len(paths) != 2 {
		t.Fatalf("two path shouldn't been found, instead %v were",
			len(paths))
	}

	// Additinoally, the total hop length of the first path returned should
	// be _less_ than that of the second path returned.
	if len(paths[0]) > len(paths[1]) {
		t.Fatalf("paths found not ordered properly")
	}

	// Finally, we'll assert the exact expected ordering of both paths
	// found.
	assertExpectedPath := func(path []*ChannelHop, nodeAliases ...string) {
		for i, hop := range path {
			if hop.Node.Alias != nodeAliases[i] {
				t.Fatalf("expected %v to be pos #%v in hop, "+
					"instead %v was", nodeAliases[i], i,
					hop.Node.Alias)
			}
		}
	}

	// The first route should be a direct route to luo ji.
	assertExpectedPath(paths[0], "roasbeef", "luoji")

	// The second route should be a route to luo ji via satoshi.
	assertExpectedPath(paths[1], "roasbeef", "satoshi", "luoji")
}

func TestNewRoutePathTooLong(t *testing.T) {
	t.Parallel()

	// Ensure that potential paths which are over the maximum hop-limit are
	// rejected.
	graph, cleanUp, aliases, err := parseTestGraph(excessiveHopsGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	paymentAmt := lnwire.NewMSatFromSatoshis(100)

	// We start by confirminig that routing a payment 20 hops away is possible.
	// Alice should be able to find a valid route to ursula.
	target := aliases["ursula"]
	_, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("path should have been found")
	}

	// Vincent is 21 hops away from Alice, and thus no valid route should be
	// presented to Alice.
	target = aliases["vincent"]
	path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err == nil {
		t.Fatalf("should not have been able to find path, supposed to be "+
			"greater than 20 hops, found route with %v hops",
			len(path))
	}

}

func TestPathNotAvailable(t *testing.T) {
	t.Parallel()

	graph, cleanUp, _, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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)
	}

	_, err = findPath(nil, graph, sourceNode, unknownNode, ignoredVertexes,
		ignoredEdges, 100)
	if !IsError(err, ErrNoPathFound) {
		t.Fatalf("path shouldn't have been found: %v", err)
	}
}

func TestPathInsufficientCapacity(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}
	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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 = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, payAmt)
	if !IsError(err, ErrNoPathFound) {
		t.Fatalf("graph shouldn't be able to support payment: %v", err)
	}
}

func TestPathInsufficientCapacityWithFee(t *testing.T) {
	t.Parallel()

	// TODO(roasbeef): encode live graph to json

	// TODO(roasbeef): need to add a case, or modify the fee ratio for one
	// to ensure that has going forward, but when fees are applied doesn't
	// work
}

// TODO(roasbeef): more time-lock calvulation tests
<commit_msg>routing: update path finding tests to account for change in fee calculation<commit_after>package routing

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"io/ioutil"
	"math/big"
	"net"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcd/chaincfg/chainhash"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"

	prand "math/rand"
)

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"

	// 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"
)

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.
//
// 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      int64  `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.
	var testAddrs []net.Addr
	testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888")
	if err != nil {
		return nil, nil, nil, err
	}
	testAddrs = append(testAddrs, testAddr)

	// 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{
			HaveNodeAnnouncement: true,
			AuthSig:              testSig,
			LastUpdate:           time.Now(),
			Addresses:            testAddrs,
			PubKey:               pub,
			Alias:                node.Alias,
			Features:             testFeatures,
		}

		// 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!")
		}

		// 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
		}
	}

	if source != nil {
		// 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 := chainhash.NewHashFromStr(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.
		edgeInfo := channeldb.ChannelEdgeInfo{
			ChannelID:    edge.ChannelID,
			NodeKey1:     node1Pub,
			NodeKey2:     node2Pub,
			BitcoinKey1:  node1Pub,
			BitcoinKey2:  node2Pub,
			AuthProof:    &testAuthProof,
			ChannelPoint: fundingPoint,
			Capacity:     btcutil.Amount(edge.Capacity),
		}
		err = graph.AddChannelEdge(&edgeInfo)
		if err != nil && err != channeldb.ErrEdgeAlreadyExist {
			return nil, nil, nil, err
		}

		edgePolicy := &channeldb.ChannelEdgePolicy{
			Signature:                 testSig,
			Flags:                     edge.Flags,
			ChannelID:                 edge.ChannelID,
			LastUpdate:                time.Now(),
			TimeLockDelta:             edge.Expiry,
			MinHTLC:                   lnwire.MilliSatoshi(edge.MinHTLC),
			FeeBaseMSat:               lnwire.MilliSatoshi(edge.FeeBaseMsat),
			FeeProportionalMillionths: lnwire.MilliSatoshi(edge.FeeRate),
		}
		if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
			return nil, nil, nil, err
		}
	}

	return graph, cleanUp, aliasMap, nil
}

func TestBasicGraphPathFinding(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}
	sourceVertex := newVertex(sourceNode.PubKey)

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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 (
		startingHeight = 100
		finalHopCLTV   = 1
	)

	paymentAmt := lnwire.NewMSatFromSatoshis(100)
	target := aliases["sophon"]
	path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find path: %v", err)
	}
	route, err := newRoute(paymentAmt, sourceVertex, path, startingHeight,
		finalHopCLTV)
	if err != nil {
		t.Fatalf("unable to create path: %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 two more than our starting block
	// height.
	if route.TotalTimeLock != 102 {
		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)
	}

	// 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, we'll assert that the "next hop" field in each route payload
	// properly points to the channel ID that the HTLC should be forwarded
	// along.
	hopPayloads := route.ToHopPayloads()
	if len(hopPayloads) != 2 {
		t.Fatalf("incorrect number of hop payloads: expected %v, got %v",
			2, len(hopPayloads))
	}

	// The first hop should point to the second hop.
	var expectedHop [8]byte
	binary.BigEndian.PutUint64(expectedHop[:], route.Hops[1].Channel.ChannelID)
	if !bytes.Equal(hopPayloads[0].NextAddress[:], expectedHop[:]) {
		t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
			expectedHop[:], hopPayloads[0].NextAddress)
	}

	// The second hop should have a next hop value of all zeroes in order
	// to indicate it's the exit hop.
	var exitHop [8]byte
	if !bytes.Equal(hopPayloads[1].NextAddress[:], exitHop[:]) {
		t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
			exitHop[:], hopPayloads[0].NextAddress)
	}

	// We'll also assert that the outgoing CLTV value for each hop was set
	// accordingly.
	if route.Hops[0].OutgoingTimeLock != 101 {
		t.Fatalf("expected outgoing time-lock of %v, instead have %v",
			1, route.Hops[0].OutgoingTimeLock)
	}
	if route.Hops[1].OutgoingTimeLock != 101 {
		t.Fatalf("outgoing time-lock for final hop is incorrect: "+
			"expected %v, got %v", 1, route.Hops[1].OutgoingTimeLock)
	}

	// Additionally, we'll ensure that the amount to forward, and fees
	// computed for each hop are correct.
	if route.Hops[0].Fee != 0 {
		t.Fatalf("first hop fee incorrect: expected %v, got %v",
			0, route.Hops[1].Fee)
	}

	firstHopFee := computeFee(paymentAmt, route.Hops[1].Channel)
	if route.TotalAmount != paymentAmt+firstHopFee {
		t.Fatalf("first hop forwarding amount incorrect: expected %v, got %v",
			paymentAmt+firstHopFee, route.TotalAmount)
	}
	if route.Hops[1].Fee != firstHopFee {
		t.Fatalf("first hop fee incorrect: expected %v, got %v",
			firstHopFee, route.Hops[1].Fee)
	}

	if route.Hops[1].AmtToForward != paymentAmt {
		t.Fatalf("second hop forwarding amount incorrect: expected %v, got %v",
			paymentAmt+firstHopFee, route.Hops[1].AmtToForward)
	}

	// 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"]
	path, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find route: %v", err)
	}
	route, err = newRoute(paymentAmt, sourceVertex, path, startingHeight,
		finalHopCLTV)
	if err != nil {
		t.Fatalf("unable to create path: %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 the current block height plus one.
	if route.TotalTimeLock != 101 {
		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 TestKShortestPathFinding(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	// In this test we'd like to ensure that our algoirthm to find the
	// k-shortest paths from a given source node to any destination node
	// works as exepcted.

	// In our basic_graph.json, there exist two paths from roasbeef to luo
	// ji. Our algorithm should properly find both paths, and also rank
	// them in order of their total "distance".

	paymentAmt := lnwire.NewMSatFromSatoshis(100)
	target := aliases["luoji"]
	paths, err := findPaths(nil, graph, sourceNode, target, paymentAmt)
	if err != nil {
		t.Fatalf("unable to find paths between roasbeef and "+
			"luo ji: %v", err)
	}

	// The algorithm should've found two paths from roasbeef to luo ji.
	if len(paths) != 2 {
		t.Fatalf("two path shouldn't been found, instead %v were",
			len(paths))
	}

	// Additinoally, the total hop length of the first path returned should
	// be _less_ than that of the second path returned.
	if len(paths[0]) > len(paths[1]) {
		t.Fatalf("paths found not ordered properly")
	}

	// Finally, we'll assert the exact expected ordering of both paths
	// found.
	assertExpectedPath := func(path []*ChannelHop, nodeAliases ...string) {
		for i, hop := range path {
			if hop.Node.Alias != nodeAliases[i] {
				t.Fatalf("expected %v to be pos #%v in hop, "+
					"instead %v was", nodeAliases[i], i,
					hop.Node.Alias)
			}
		}
	}

	// The first route should be a direct route to luo ji.
	assertExpectedPath(paths[0], "roasbeef", "luoji")

	// The second route should be a route to luo ji via satoshi.
	assertExpectedPath(paths[1], "roasbeef", "satoshi", "luoji")
}

func TestNewRoutePathTooLong(t *testing.T) {

	// Ensure that potential paths which are over the maximum hop-limit are
	// rejected.
	graph, cleanUp, aliases, err := parseTestGraph(excessiveHopsGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	paymentAmt := lnwire.NewMSatFromSatoshis(100)

	// We start by confirminig that routing a payment 20 hops away is possible.
	// Alice should be able to find a valid route to ursula.
	target := aliases["ursula"]
	_, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err != nil {
		t.Fatalf("path should have been found")
	}

	// Vincent is 21 hops away from Alice, and thus no valid route should be
	// presented to Alice.
	target = aliases["vincent"]
	path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, paymentAmt)
	if err == nil {
		t.Fatalf("should not have been able to find path, supposed to be "+
			"greater than 20 hops, found route with %v hops",
			len(path))
	}

}

func TestPathNotAvailable(t *testing.T) {
	t.Parallel()

	graph, cleanUp, _, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}

	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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)
	}

	_, err = findPath(nil, graph, sourceNode, unknownNode, ignoredVertexes,
		ignoredEdges, 100)
	if !IsError(err, ErrNoPathFound) {
		t.Fatalf("path shouldn't have been found: %v", err)
	}
}

func TestPathInsufficientCapacity(t *testing.T) {
	t.Parallel()

	graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
	defer cleanUp()
	if err != nil {
		t.Fatalf("unable to create graph: %v", err)
	}

	sourceNode, err := graph.SourceNode()
	if err != nil {
		t.Fatalf("unable to fetch source node: %v", err)
	}
	ignoredEdges := make(map[uint64]struct{})
	ignoredVertexes := make(map[vertex]struct{})

	// 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 = findPath(nil, graph, sourceNode, target, ignoredVertexes,
		ignoredEdges, payAmt)
	if !IsError(err, ErrNoPathFound) {
		t.Fatalf("graph shouldn't be able to support payment: %v", err)
	}
}

func TestPathInsufficientCapacityWithFee(t *testing.T) {
	t.Parallel()

	// TODO(roasbeef): encode live graph to json

	// TODO(roasbeef): need to add a case, or modify the fee ratio for one
	// to ensure that has going forward, but when fees are applied doesn't
	// work
}

// TODO(roasbeef): more time-lock calvulation tests

{'fraction_non_alphanumeric': 0.11491142988411863, 'fraction_numerical': 0.018479759048445555, 'mean_word_length': 3.5388090824837812, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '779821', 'n_tokens_mistral': 14245, 'n_tokens_neox': 13069, 'n_words': 4387}