package channeldb

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"image/color"
	"math"
	prand "math/rand"
	"net"
	"reflect"
	"runtime"
	"sync"
	"testing"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/davecgh/go-spew/spew"
	"github.com/lightningnetwork/lnd/channeldb/models"
	"github.com/lightningnetwork/lnd/kvdb"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/routing/route"
	"github.com/stretchr/testify/require"
	"golang.org/x/exp/rand"
)

var (
	testAddr = &net.TCPAddr{IP: (net.IP)([]byte{0xA, 0x0, 0x0, 0x1}),
		Port: 9000}
	anotherAddr, _ = net.ResolveTCPAddr("tcp",
		"[2001:db8:85a3:0:0:8a2e:370:7334]:80")
	testAddrs = []net.Addr{testAddr, anotherAddr}

	testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7")
	testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae")
	testRScalar   = new(btcec.ModNScalar)
	testSScalar   = new(btcec.ModNScalar)
	_             = testRScalar.SetByteSlice(testRBytes)
	_             = testSScalar.SetByteSlice(testSBytes)
	testSig       = ecdsa.NewSignature(testRScalar, testSScalar)

	testFeatures = lnwire.NewFeatureVector(
		lnwire.NewRawFeatureVector(lnwire.GossipQueriesRequired),
		lnwire.Features,
	)

	testPub = route.Vertex{2, 202, 4}
)

// MakeTestGraph creates a new instance of the ChannelGraph for testing purposes.
func MakeTestGraph(t testing.TB, modifiers ...OptionModifier) (*ChannelGraph, error) {
	opts := DefaultOptions()
	for _, modifier := range modifiers {
		modifier(&opts)
	}

	// Next, create channelgraph for the first time.
	backend, backendCleanup, err := kvdb.GetTestBackend(t.TempDir(), "cgr")
	if err != nil {
		backendCleanup()
		return nil, err
	}

	graph, err := NewChannelGraph(
		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
		true, false,
	)
	if err != nil {
		backendCleanup()
		return nil, err
	}

	t.Cleanup(func() {
		_ = backend.Close()
		backendCleanup()
	})

	return graph, nil
}

func createLightningNode(db kvdb.Backend, priv *btcec.PrivateKey) (*LightningNode, error) {
	updateTime := prand.Int63()

	pub := priv.PubKey().SerializeCompressed()
	n := &LightningNode{
		HaveNodeAnnouncement: true,
		AuthSigBytes:         testSig.Serialize(),
		LastUpdate:           time.Unix(updateTime, 0),
		Color:                color.RGBA{1, 2, 3, 0},
		Alias:                "kek" + string(pub[:]),
		Features:             testFeatures,
		Addresses:            testAddrs,
	}
	copy(n.PubKeyBytes[:], priv.PubKey().SerializeCompressed())

	return n, nil
}

func createTestVertex(db kvdb.Backend) (*LightningNode, error) {
	priv, err := btcec.NewPrivateKey()
	if err != nil {
		return nil, err
	}

	return createLightningNode(db, priv)
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test basic insertion/deletion for vertexes from the
	// graph, so we'll create a test vertex to start with.
	node := &LightningNode{
		HaveNodeAnnouncement: true,
		AuthSigBytes:         testSig.Serialize(),
		LastUpdate:           time.Unix(1232342, 0),
		Color:                color.RGBA{1, 2, 3, 0},
		Alias:                "kek",
		Features:             testFeatures,
		Addresses:            testAddrs,
		ExtraOpaqueData:      []byte("extra new data"),
		PubKeyBytes:          testPub,
	}

	// First, insert the node into the graph DB. This should succeed
	// without any errors.
	if err := graph.AddLightningNode(node); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	assertNodeInCache(t, graph, node, testFeatures)

	// Next, fetch the node from the database to ensure everything was
	// serialized properly.
	dbNode, err := graph.FetchLightningNode(testPub)
	require.NoError(t, err, "unable to locate node")

	if _, exists, err := graph.HasLightningNode(dbNode.PubKeyBytes); err != nil {
		t.Fatalf("unable to query for node: %v", err)
	} else if !exists {
		t.Fatalf("node should be found but wasn't")
	}

	// The two nodes should match exactly!
	if err := compareNodes(node, dbNode); err != nil {
		t.Fatalf("nodes don't match: %v", err)
	}

	// Next, delete the node from the graph, this should purge all data
	// related to the node.
	if err := graph.DeleteLightningNode(testPub); err != nil {
		t.Fatalf("unable to delete node; %v", err)
	}
	assertNodeNotInCache(t, graph, testPub)

	// Finally, attempt to fetch the node again. This should fail as the
	// node should have been deleted from the database.
	_, err = graph.FetchLightningNode(testPub)
	if err != ErrGraphNodeNotFound {
		t.Fatalf("fetch after delete should fail!")
	}
}

// TestPartialNode checks that we can add and retrieve a LightningNode where
// where only the pubkey is known to the database.
func TestPartialNode(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We want to be able to insert nodes into the graph that only has the
	// PubKey set.
	node := &LightningNode{
		HaveNodeAnnouncement: false,
		PubKeyBytes:          testPub,
	}

	if err := graph.AddLightningNode(node); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	assertNodeInCache(t, graph, node, nil)

	// Next, fetch the node from the database to ensure everything was
	// serialized properly.
	dbNode, err := graph.FetchLightningNode(testPub)
	require.NoError(t, err, "unable to locate node")

	if _, exists, err := graph.HasLightningNode(dbNode.PubKeyBytes); err != nil {
		t.Fatalf("unable to query for node: %v", err)
	} else if !exists {
		t.Fatalf("node should be found but wasn't")
	}

	// The two nodes should match exactly! (with default values for
	// LastUpdate and db set to satisfy compareNodes())
	node = &LightningNode{
		HaveNodeAnnouncement: false,
		LastUpdate:           time.Unix(0, 0),
		PubKeyBytes:          testPub,
	}

	if err := compareNodes(node, dbNode); err != nil {
		t.Fatalf("nodes don't match: %v", err)
	}

	// Next, delete the node from the graph, this should purge all data
	// related to the node.
	if err := graph.DeleteLightningNode(testPub); err != nil {
		t.Fatalf("unable to delete node: %v", err)
	}
	assertNodeNotInCache(t, graph, testPub)

	// Finally, attempt to fetch the node again. This should fail as the
	// node should have been deleted from the database.
	_, err = graph.FetchLightningNode(testPub)
	if err != ErrGraphNodeNotFound {
		t.Fatalf("fetch after delete should fail!")
	}
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test the alias index within the database, so first
	// create a new test node.
	testNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// Add the node to the graph's database, this should also insert an
	// entry into the alias index for this node.
	if err := graph.AddLightningNode(testNode); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// Next, attempt to lookup the alias. The alias should exactly match
	// the one which the test node was assigned.
	nodePub, err := testNode.PubKey()
	require.NoError(t, err, "unable to generate pubkey")
	dbAlias, err := graph.LookupAlias(nodePub)
	require.NoError(t, err, "unable to find alias")
	if dbAlias != testNode.Alias {
		t.Fatalf("aliases don't match, expected %v got %v",
			testNode.Alias, dbAlias)
	}

	// Ensure that looking up a non-existent alias results in an error.
	node, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	nodePub, err = node.PubKey()
	require.NoError(t, err, "unable to generate pubkey")
	_, err = graph.LookupAlias(nodePub)
	if err != ErrNodeAliasNotFound {
		t.Fatalf("alias lookup should fail for non-existent pubkey")
	}
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test the setting/getting of the source node, so we
	// first create a fake node to use within the test.
	testNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// Attempt to fetch the source node, this should return an error as the
	// source node hasn't yet been set.
	if _, err := graph.SourceNode(); err != ErrSourceNodeNotSet {
		t.Fatalf("source node shouldn't be set in new graph")
	}

	// Set the source the source node, this should insert the node into the
	// database in a special way indicating it's the source node.
	if err := graph.SetSourceNode(testNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	// Retrieve the source node from the database, it should exactly match
	// the one we set above.
	sourceNode, err := graph.SourceNode()
	require.NoError(t, err, "unable to fetch source node")
	if err := compareNodes(testNode, sourceNode); err != nil {
		t.Fatalf("nodes don't match: %v", err)
	}
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test the insertion/deletion of edges, so we create two
	// vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// In addition to the fake vertexes we create some fake channel
	// identifiers.
	chanID := uint64(prand.Int63())
	outpoint := wire.OutPoint{
		Hash:  rev,
		Index: 9,
	}

	// Add the new edge to the database, this should proceed without any
	// errors.
	node1Pub, err := node1.PubKey()
	require.NoError(t, err, "unable to generate node key")
	node2Pub, err := node2.PubKey()
	require.NoError(t, err, "unable to generate node key")
	edgeInfo := models.ChannelEdgeInfo{
		ChannelID: chanID,
		ChainHash: key,
		AuthProof: &models.ChannelAuthProof{
			NodeSig1Bytes:    testSig.Serialize(),
			NodeSig2Bytes:    testSig.Serialize(),
			BitcoinSig1Bytes: testSig.Serialize(),
			BitcoinSig2Bytes: testSig.Serialize(),
		},
		ChannelPoint: outpoint,
		Capacity:     9000,
	}
	copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed())
	copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed())
	copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed())
	copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed())

	if err := graph.AddChannelEdge(&edgeInfo); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo)

	// Ensure that both policies are returned as unknown (nil).
	_, e1, e2, err := graph.FetchChannelEdgesByID(chanID)
	if err != nil {
		t.Fatalf("unable to fetch channel edge")
	}
	if e1 != nil || e2 != nil {
		t.Fatalf("channel edges not unknown")
	}

	// Next, attempt to delete the edge from the database, again this
	// should proceed without any issues.
	if err := graph.DeleteChannelEdges(false, true, chanID); err != nil {
		t.Fatalf("unable to delete edge: %v", err)
	}
	assertNoEdge(t, graph, chanID)

	// Ensure that any query attempts to lookup the delete channel edge are
	// properly deleted.
	if _, _, _, err := graph.FetchChannelEdgesByOutpoint(&outpoint); err == nil {
		t.Fatalf("channel edge not deleted")
	}
	if _, _, _, err := graph.FetchChannelEdgesByID(chanID); err == nil {
		t.Fatalf("channel edge not deleted")
	}
	isZombie, _, _ := graph.IsZombieEdge(chanID)
	if !isZombie {
		t.Fatal("channel edge not marked as zombie")
	}

	// Finally, attempt to delete a (now) non-existent edge within the
	// database, this should result in an error.
	err = graph.DeleteChannelEdges(false, true, chanID)
	if err != ErrEdgeNotFound {
		t.Fatalf("deleting a non-existent edge should fail!")
	}
}

func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32,
	node1, node2 *LightningNode) (models.ChannelEdgeInfo,
	lnwire.ShortChannelID) {

	shortChanID := lnwire.ShortChannelID{
		BlockHeight: height,
		TxIndex:     txIndex,
		TxPosition:  txPosition,
	}
	outpoint := wire.OutPoint{
		Hash:  rev,
		Index: outPointIndex,
	}

	node1Pub, _ := node1.PubKey()
	node2Pub, _ := node2.PubKey()
	edgeInfo := models.ChannelEdgeInfo{
		ChannelID: shortChanID.ToUint64(),
		ChainHash: key,
		AuthProof: &models.ChannelAuthProof{
			NodeSig1Bytes:    testSig.Serialize(),
			NodeSig2Bytes:    testSig.Serialize(),
			BitcoinSig1Bytes: testSig.Serialize(),
			BitcoinSig2Bytes: testSig.Serialize(),
		},
		ChannelPoint: outpoint,
		Capacity:     9000,
	}

	copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed())
	copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed())
	copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed())
	copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed())

	return edgeInfo, shortChanID
}

// TestDisconnectBlockAtHeight checks that the pruned state of the channel
// database is what we expect after calling DisconnectBlockAtHeight.
func TestDisconnectBlockAtHeight(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	sourceNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create source node")
	if err := graph.SetSourceNode(sourceNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	// We'd like to test the insertion/deletion of edges, so we create two
	// vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// In addition to the fake vertexes we create some fake channel
	// identifiers.
	var spendOutputs []*wire.OutPoint
	var blockHash chainhash.Hash
	copy(blockHash[:], bytes.Repeat([]byte{1}, 32))

	// Prune the graph a few times to make sure we have entries in the
	// prune log.
	_, err = graph.PruneGraph(spendOutputs, &blockHash, 155)
	require.NoError(t, err, "unable to prune graph")
	var blockHash2 chainhash.Hash
	copy(blockHash2[:], bytes.Repeat([]byte{2}, 32))

	_, err = graph.PruneGraph(spendOutputs, &blockHash2, 156)
	require.NoError(t, err, "unable to prune graph")

	// We'll create 3 almost identical edges, so first create a helper
	// method containing all logic for doing so.

	// Create an edge which has its block height at 156.
	height := uint32(156)
	edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2)

	// Create an edge with block height 157. We give it
	// maximum values for tx index and position, to make
	// sure our database range scan get edges from the
	// entire range.
	edgeInfo2, _ := createEdge(
		height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1,
		node1, node2,
	)

	// Create a third edge, this with a block height of 155.
	edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2)

	// Now add all these new edges to the database.
	if err := graph.AddChannelEdge(&edgeInfo); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	if err := graph.AddChannelEdge(&edgeInfo2); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	if err := graph.AddChannelEdge(&edgeInfo3); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo)
	assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo2)
	assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3)

	// Call DisconnectBlockAtHeight, which should prune every channel
	// that has a funding height of 'height' or greater.
	removed, err := graph.DisconnectBlockAtHeight(uint32(height))
	if err != nil {
		t.Fatalf("unable to prune %v", err)
	}
	assertNoEdge(t, graph, edgeInfo.ChannelID)
	assertNoEdge(t, graph, edgeInfo2.ChannelID)
	assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3)

	// The two edges should have been removed.
	if len(removed) != 2 {
		t.Fatalf("expected two edges to be removed from graph, "+
			"only %d were", len(removed))
	}
	if removed[0].ChannelID != edgeInfo.ChannelID {
		t.Fatalf("expected edge to be removed from graph")
	}
	if removed[1].ChannelID != edgeInfo2.ChannelID {
		t.Fatalf("expected edge to be removed from graph")
	}

	// The two first edges should be removed from the db.
	_, _, has, isZombie, err := graph.HasChannelEdge(edgeInfo.ChannelID)
	require.NoError(t, err, "unable to query for edge")
	if has {
		t.Fatalf("edge1 was not pruned from the graph")
	}
	if isZombie {
		t.Fatal("reorged edge1 should not be marked as zombie")
	}
	_, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo2.ChannelID)
	require.NoError(t, err, "unable to query for edge")
	if has {
		t.Fatalf("edge2 was not pruned from the graph")
	}
	if isZombie {
		t.Fatal("reorged edge2 should not be marked as zombie")
	}

	// Edge 3 should not be removed.
	_, _, has, isZombie, err = graph.HasChannelEdge(edgeInfo3.ChannelID)
	require.NoError(t, err, "unable to query for edge")
	if !has {
		t.Fatalf("edge3 was pruned from the graph")
	}
	if isZombie {
		t.Fatal("edge3 was marked as zombie")
	}

	// PruneTip should be set to the blockHash we specified for the block
	// at height 155.
	hash, h, err := graph.PruneTip()
	require.NoError(t, err, "unable to get prune tip")
	if !blockHash.IsEqual(hash) {
		t.Fatalf("expected best block to be %x, was %x", blockHash, hash)
	}
	if h != height-1 {
		t.Fatalf("expected best block height to be %d, was %d", height-1, h)
	}
}

func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo,
	e2 *models.ChannelEdgeInfo) {

	if e1.ChannelID != e2.ChannelID {
		t.Fatalf("chan id's don't match: %v vs %v", e1.ChannelID,
			e2.ChannelID)
	}

	if e1.ChainHash != e2.ChainHash {
		t.Fatalf("chain hashes don't match: %v vs %v", e1.ChainHash,
			e2.ChainHash)
	}

	if !bytes.Equal(e1.NodeKey1Bytes[:], e2.NodeKey1Bytes[:]) {
		t.Fatalf("nodekey1 doesn't match")
	}
	if !bytes.Equal(e1.NodeKey2Bytes[:], e2.NodeKey2Bytes[:]) {
		t.Fatalf("nodekey2 doesn't match")
	}
	if !bytes.Equal(e1.BitcoinKey1Bytes[:], e2.BitcoinKey1Bytes[:]) {
		t.Fatalf("bitcoinkey1 doesn't match")
	}
	if !bytes.Equal(e1.BitcoinKey2Bytes[:], e2.BitcoinKey2Bytes[:]) {
		t.Fatalf("bitcoinkey2 doesn't match")
	}

	if !bytes.Equal(e1.Features, e2.Features) {
		t.Fatalf("features doesn't match: %x vs %x", e1.Features,
			e2.Features)
	}

	if !bytes.Equal(e1.AuthProof.NodeSig1Bytes, e2.AuthProof.NodeSig1Bytes) {
		t.Fatalf("nodesig1 doesn't match: %v vs %v",
			spew.Sdump(e1.AuthProof.NodeSig1Bytes),
			spew.Sdump(e2.AuthProof.NodeSig1Bytes))
	}
	if !bytes.Equal(e1.AuthProof.NodeSig2Bytes, e2.AuthProof.NodeSig2Bytes) {
		t.Fatalf("nodesig2 doesn't match")
	}
	if !bytes.Equal(e1.AuthProof.BitcoinSig1Bytes, e2.AuthProof.BitcoinSig1Bytes) {
		t.Fatalf("bitcoinsig1 doesn't match")
	}
	if !bytes.Equal(e1.AuthProof.BitcoinSig2Bytes, e2.AuthProof.BitcoinSig2Bytes) {
		t.Fatalf("bitcoinsig2 doesn't match")
	}

	if e1.ChannelPoint != e2.ChannelPoint {
		t.Fatalf("channel point match: %v vs %v", e1.ChannelPoint,
			e2.ChannelPoint)
	}

	if e1.Capacity != e2.Capacity {
		t.Fatalf("capacity doesn't match: %v vs %v", e1.Capacity,
			e2.Capacity)
	}

	if !bytes.Equal(e1.ExtraOpaqueData, e2.ExtraOpaqueData) {
		t.Fatalf("extra data doesn't match: %v vs %v",
			e2.ExtraOpaqueData, e2.ExtraOpaqueData)
	}
}

func createChannelEdge(db kvdb.Backend, node1, node2 *LightningNode) (
	*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
	*models.ChannelEdgePolicy) {

	var (
		firstNode  [33]byte
		secondNode [33]byte
	)
	if bytes.Compare(node1.PubKeyBytes[:], node2.PubKeyBytes[:]) == -1 {
		firstNode = node1.PubKeyBytes
		secondNode = node2.PubKeyBytes
	} else {
		firstNode = node2.PubKeyBytes
		secondNode = node1.PubKeyBytes
	}

	// In addition to the fake vertexes we create some fake channel
	// identifiers.
	chanID := uint64(prand.Int63())
	outpoint := wire.OutPoint{
		Hash:  rev,
		Index: 9,
	}

	// Add the new edge to the database, this should proceed without any
	// errors.
	edgeInfo := &models.ChannelEdgeInfo{
		ChannelID: chanID,
		ChainHash: key,
		AuthProof: &models.ChannelAuthProof{
			NodeSig1Bytes:    testSig.Serialize(),
			NodeSig2Bytes:    testSig.Serialize(),
			BitcoinSig1Bytes: testSig.Serialize(),
			BitcoinSig2Bytes: testSig.Serialize(),
		},
		ChannelPoint:    outpoint,
		Capacity:        1000,
		ExtraOpaqueData: []byte("new unknown feature"),
	}
	copy(edgeInfo.NodeKey1Bytes[:], firstNode[:])
	copy(edgeInfo.NodeKey2Bytes[:], secondNode[:])
	copy(edgeInfo.BitcoinKey1Bytes[:], firstNode[:])
	copy(edgeInfo.BitcoinKey2Bytes[:], secondNode[:])

	edge1 := &models.ChannelEdgePolicy{
		SigBytes:                  testSig.Serialize(),
		ChannelID:                 chanID,
		LastUpdate:                time.Unix(433453, 0),
		MessageFlags:              1,
		ChannelFlags:              0,
		TimeLockDelta:             99,
		MinHTLC:                   2342135,
		MaxHTLC:                   13928598,
		FeeBaseMSat:               4352345,
		FeeProportionalMillionths: 3452352,
		ToNode:                    secondNode,
		ExtraOpaqueData:           []byte{1, 0},
	}
	edge2 := &models.ChannelEdgePolicy{
		SigBytes:                  testSig.Serialize(),
		ChannelID:                 chanID,
		LastUpdate:                time.Unix(124234, 0),
		MessageFlags:              1,
		ChannelFlags:              1,
		TimeLockDelta:             99,
		MinHTLC:                   2342135,
		MaxHTLC:                   13928598,
		FeeBaseMSat:               4352345,
		FeeProportionalMillionths: 90392423,
		ToNode:                    firstNode,
		ExtraOpaqueData:           []byte{1, 0},
	}

	return edgeInfo, edge1, edge2
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test the update of edges inserted into the database, so
	// we create two vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	assertNodeInCache(t, graph, node1, testFeatures)
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	assertNodeInCache(t, graph, node2, testFeatures)

	// Create an edge and add it to the db.
	edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2)

	// Make sure inserting the policy at this point, before the edge info
	// is added, will fail.
	if err := graph.UpdateEdgePolicy(edge1); err != ErrEdgeNotFound {
		t.Fatalf("expected ErrEdgeNotFound, got: %v", err)
	}
	require.Len(t, graph.graphCache.nodeChannels, 0)

	// Add the edge info.
	if err := graph.AddChannelEdge(edgeInfo); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo)

	chanID := edgeInfo.ChannelID
	outpoint := edgeInfo.ChannelPoint

	// Next, insert both edge policies into the database, they should both
	// be inserted without any issues.
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}
	assertEdgeWithPolicyInCache(t, graph, edgeInfo, edge1, true)
	if err := graph.UpdateEdgePolicy(edge2); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}
	assertEdgeWithPolicyInCache(t, graph, edgeInfo, edge2, false)

	// Check for existence of the edge within the database, it should be
	// found.
	_, _, found, isZombie, err := graph.HasChannelEdge(chanID)
	require.NoError(t, err, "unable to query for edge")
	if !found {
		t.Fatalf("graph should have of inserted edge")
	}
	if isZombie {
		t.Fatal("live edge should not be marked as zombie")
	}

	// We should also be able to retrieve the channelID only knowing the
	// channel point of the channel.
	dbChanID, err := graph.ChannelID(&outpoint)
	require.NoError(t, err, "unable to retrieve channel ID")
	if dbChanID != chanID {
		t.Fatalf("chan ID's mismatch, expected %v got %v", dbChanID,
			chanID)
	}

	// With the edges inserted, perform some queries to ensure that they've
	// been inserted properly.
	dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID)
	require.NoError(t, err, "unable to fetch channel by ID")
	if err := compareEdgePolicies(dbEdge1, edge1); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)

	// Next, attempt to query the channel edges according to the outpoint
	// of the channel.
	dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByOutpoint(&outpoint)
	require.NoError(t, err, "unable to fetch channel by ID")
	if err := compareEdgePolicies(dbEdge1, edge1); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)
}

func assertNodeInCache(t *testing.T, g *ChannelGraph, n *LightningNode,
	expectedFeatures *lnwire.FeatureVector) {

	// Let's check the internal view first.
	require.Equal(
		t, expectedFeatures, g.graphCache.nodeFeatures[n.PubKeyBytes],
	)

	// The external view should reflect this as well. Except when we expect
	// the features to be nil internally, we return an empty feature vector
	// on the public interface instead.
	if expectedFeatures == nil {
		expectedFeatures = lnwire.EmptyFeatureVector()
	}
	features := g.graphCache.GetFeatures(n.PubKeyBytes)
	require.Equal(t, expectedFeatures, features)
}

func assertNodeNotInCache(t *testing.T, g *ChannelGraph, n route.Vertex) {
	_, ok := g.graphCache.nodeFeatures[n]
	require.False(t, ok)

	_, ok = g.graphCache.nodeChannels[n]
	require.False(t, ok)

	// We should get the default features for this node.
	features := g.graphCache.GetFeatures(n)
	require.Equal(t, lnwire.EmptyFeatureVector(), features)
}

func assertEdgeWithNoPoliciesInCache(t *testing.T, g *ChannelGraph,
	e *models.ChannelEdgeInfo) {

	// Let's check the internal view first.
	require.NotEmpty(t, g.graphCache.nodeChannels[e.NodeKey1Bytes])
	require.NotEmpty(t, g.graphCache.nodeChannels[e.NodeKey2Bytes])

	expectedNode1Channel := &DirectedChannel{
		ChannelID:    e.ChannelID,
		IsNode1:      true,
		OtherNode:    e.NodeKey2Bytes,
		Capacity:     e.Capacity,
		OutPolicySet: false,
		InPolicy:     nil,
	}
	require.Contains(
		t, g.graphCache.nodeChannels[e.NodeKey1Bytes], e.ChannelID,
	)
	require.Equal(
		t, expectedNode1Channel,
		g.graphCache.nodeChannels[e.NodeKey1Bytes][e.ChannelID],
	)

	expectedNode2Channel := &DirectedChannel{
		ChannelID:    e.ChannelID,
		IsNode1:      false,
		OtherNode:    e.NodeKey1Bytes,
		Capacity:     e.Capacity,
		OutPolicySet: false,
		InPolicy:     nil,
	}
	require.Contains(
		t, g.graphCache.nodeChannels[e.NodeKey2Bytes], e.ChannelID,
	)
	require.Equal(
		t, expectedNode2Channel,
		g.graphCache.nodeChannels[e.NodeKey2Bytes][e.ChannelID],
	)

	// The external view should reflect this as well.
	var foundChannel *DirectedChannel
	err := g.graphCache.ForEachChannel(
		e.NodeKey1Bytes, func(c *DirectedChannel) error {
			if c.ChannelID == e.ChannelID {
				foundChannel = c
			}

			return nil
		},
	)
	require.NoError(t, err)
	require.NotNil(t, foundChannel)
	require.Equal(t, expectedNode1Channel, foundChannel)

	err = g.graphCache.ForEachChannel(
		e.NodeKey2Bytes, func(c *DirectedChannel) error {
			if c.ChannelID == e.ChannelID {
				foundChannel = c
			}

			return nil
		},
	)
	require.NoError(t, err)
	require.NotNil(t, foundChannel)
	require.Equal(t, expectedNode2Channel, foundChannel)
}

func assertNoEdge(t *testing.T, g *ChannelGraph, chanID uint64) {
	// Make sure no channel in the cache has the given channel ID. If there
	// are no channels at all, that is fine as well.
	for _, channels := range g.graphCache.nodeChannels {
		for _, channel := range channels {
			require.NotEqual(t, channel.ChannelID, chanID)
		}
	}
}

func assertEdgeWithPolicyInCache(t *testing.T, g *ChannelGraph,
	e *models.ChannelEdgeInfo, p *models.ChannelEdgePolicy, policy1 bool) {

	// Check the internal state first.
	c1, ok := g.graphCache.nodeChannels[e.NodeKey1Bytes][e.ChannelID]
	require.True(t, ok)

	if policy1 {
		require.True(t, c1.OutPolicySet)
	} else {
		require.NotNil(t, c1.InPolicy)
		require.Equal(
			t, p.FeeProportionalMillionths,
			c1.InPolicy.FeeProportionalMillionths,
		)
	}

	c2, ok := g.graphCache.nodeChannels[e.NodeKey2Bytes][e.ChannelID]
	require.True(t, ok)

	if policy1 {
		require.NotNil(t, c2.InPolicy)
		require.Equal(
			t, p.FeeProportionalMillionths,
			c2.InPolicy.FeeProportionalMillionths,
		)
	} else {
		require.True(t, c2.OutPolicySet)
	}

	// Now for both nodes make sure that the external view is also correct.
	var (
		c1Ext *DirectedChannel
		c2Ext *DirectedChannel
	)
	require.NoError(t, g.graphCache.ForEachChannel(
		e.NodeKey1Bytes, func(c *DirectedChannel) error {
			c1Ext = c

			return nil
		},
	))
	require.NoError(t, g.graphCache.ForEachChannel(
		e.NodeKey2Bytes, func(c *DirectedChannel) error {
			c2Ext = c

			return nil
		},
	))

	// Only compare the fields that are actually copied, then compare the
	// values of the functions separately.
	require.Equal(t, c1, c1Ext.DeepCopy())
	require.Equal(t, c2, c2Ext.DeepCopy())
	if policy1 {
		require.Equal(
			t, p.FeeProportionalMillionths,
			c2Ext.InPolicy.FeeProportionalMillionths,
		)
		require.Equal(
			t, route.Vertex(e.NodeKey2Bytes),
			c2Ext.InPolicy.ToNodePubKey(),
		)
		require.Equal(t, testFeatures, c2Ext.InPolicy.ToNodeFeatures)
	} else {
		require.Equal(
			t, p.FeeProportionalMillionths,
			c1Ext.InPolicy.FeeProportionalMillionths,
		)
		require.Equal(
			t, route.Vertex(e.NodeKey1Bytes),
			c1Ext.InPolicy.ToNodePubKey(),
		)
		require.Equal(t, testFeatures, c1Ext.InPolicy.ToNodeFeatures)
	}
}

func randEdgePolicy(chanID uint64, db kvdb.Backend) *models.ChannelEdgePolicy {
	update := prand.Int63()

	return newEdgePolicy(chanID, db, update)
}

func newEdgePolicy(chanID uint64, db kvdb.Backend,
	updateTime int64) *models.ChannelEdgePolicy {

	return &models.ChannelEdgePolicy{
		ChannelID:                 chanID,
		LastUpdate:                time.Unix(updateTime, 0),
		MessageFlags:              1,
		ChannelFlags:              0,
		TimeLockDelta:             uint16(prand.Int63()),
		MinHTLC:                   lnwire.MilliSatoshi(prand.Int63()),
		MaxHTLC:                   lnwire.MilliSatoshi(prand.Int63()),
		FeeBaseMSat:               lnwire.MilliSatoshi(prand.Int63()),
		FeeProportionalMillionths: lnwire.MilliSatoshi(prand.Int63()),
	}
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test some of the graph traversal capabilities within
	// the DB, so we'll create a series of fake nodes to insert into the
	// graph. And we'll create 5 channels between each node pair.
	const numNodes = 20
	const numChannels = 5
	chanIndex, nodeList := fillTestGraph(t, graph, numNodes, numChannels)

	// Make an index of the node list for easy look up below.
	nodeIndex := make(map[route.Vertex]struct{})
	for _, node := range nodeList {
		nodeIndex[node.PubKeyBytes] = struct{}{}
	}

	// If we turn the channel graph cache _off_, then iterate through the
	// set of channels (to force the fall back), we should find all the
	// channel as well as the nodes included.
	graph.graphCache = nil
	err = graph.ForEachNodeCached(func(node route.Vertex,
		chans map[uint64]*DirectedChannel) error {

		if _, ok := nodeIndex[node]; !ok {
			return fmt.Errorf("node %x not found in graph", node)
		}

		for chanID := range chans {
			if _, ok := chanIndex[chanID]; !ok {
				return fmt.Errorf("chan %v not found in "+
					"graph", chanID)
			}
		}

		return nil
	})
	require.NoError(t, err)

	// Iterate through all the known channels within the graph DB, once
	// again if the map is empty that indicates that all edges have
	// properly been reached.
	err = graph.ForEachChannel(func(ei *models.ChannelEdgeInfo,
		_ *models.ChannelEdgePolicy,
		_ *models.ChannelEdgePolicy) error {

		delete(chanIndex, ei.ChannelID)
		return nil
	})
	require.NoError(t, err)
	require.Len(t, chanIndex, 0)

	// Finally, we want to test the ability to iterate over all the
	// outgoing channels for a particular node.
	numNodeChans := 0
	firstNode, secondNode := nodeList[0], nodeList[1]
	err = graph.ForEachNodeChannel(firstNode.PubKeyBytes,
		func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge,
			inEdge *models.ChannelEdgePolicy) error {

			// All channels between first and second node should
			// have fully (both sides) specified policies.
			if inEdge == nil || outEdge == nil {
				return fmt.Errorf("channel policy not present")
			}

			// Each should indicate that it's outgoing (pointed
			// towards the second node).
			if !bytes.Equal(
				outEdge.ToNode[:], secondNode.PubKeyBytes[:],
			) {

				return fmt.Errorf("wrong outgoing edge")
			}

			// The incoming edge should also indicate that it's
			// pointing to the origin node.
			if !bytes.Equal(
				inEdge.ToNode[:], firstNode.PubKeyBytes[:],
			) {

				return fmt.Errorf("wrong outgoing edge")
			}

			numNodeChans++

			return nil
		})
	require.NoError(t, err)
	require.Equal(t, numChannels, numNodeChans)
}

// TestGraphTraversalCacheable tests that the memory optimized node traversal is
// working correctly.
func TestGraphTraversalCacheable(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test some of the graph traversal capabilities within
	// the DB, so we'll create a series of fake nodes to insert into the
	// graph. And we'll create 5 channels between the first two nodes.
	const numNodes = 20
	const numChannels = 5
	chanIndex, _ := fillTestGraph(t, graph, numNodes, numChannels)

	// Create a map of all nodes with the iteration we know works (because
	// it is tested in another test).
	nodeMap := make(map[route.Vertex]struct{})
	err = graph.ForEachNode(func(tx kvdb.RTx, n *LightningNode) error {
		nodeMap[n.PubKeyBytes] = struct{}{}

		return nil
	})
	require.NoError(t, err)
	require.Len(t, nodeMap, numNodes)

	// Iterate through all the known channels within the graph DB by
	// iterating over each node, once again if the map is empty that
	// indicates that all edges have properly been reached.
	var nodes []GraphCacheNode
	err = graph.ForEachNodeCacheable(
		func(tx kvdb.RTx, node GraphCacheNode) error {
			delete(nodeMap, node.PubKey())

			nodes = append(nodes, node)

			return nil
		},
	)
	require.NoError(t, err)
	require.Len(t, nodeMap, 0)

	err = graph.db.View(func(tx kvdb.RTx) error {
		for _, node := range nodes {
			err := node.ForEachChannel(
				tx, func(tx kvdb.RTx,
					info *models.ChannelEdgeInfo,
					policy *models.ChannelEdgePolicy,
					policy2 *models.ChannelEdgePolicy) error { //nolint:lll

					delete(chanIndex, info.ChannelID)
					return nil
				},
			)
			if err != nil {
				return err
			}
		}

		return nil
	}, func() {})

	require.NoError(t, err)
	require.Len(t, chanIndex, 0)
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err)

	// We'd like to test some of the graph traversal capabilities within
	// the DB, so we'll create a series of fake nodes to insert into the
	// graph. And we'll create 5 channels between each node pair.
	const numNodes = 20
	const numChannels = 5
	chanIndex, nodeList := fillTestGraph(t, graph, numNodes, numChannels)

	// Iterate through all the known channels within the graph DB, once
	// again if the map is empty that indicates that all edges have
	// properly been reached.
	numNodeChans := 0
	for _, node := range nodeList {
		node := node

		err = graph.graphCache.ForEachChannel(
			node.PubKeyBytes, func(d *DirectedChannel) error {
				delete(chanIndex, d.ChannelID)

				if !d.OutPolicySet || d.InPolicy == nil {
					return fmt.Errorf("channel policy not " +
						"present")
				}

				// The incoming edge should also indicate that
				// it's pointing to the origin node.
				inPolicyNodeKey := d.InPolicy.ToNodePubKey()
				if !bytes.Equal(
					inPolicyNodeKey[:], node.PubKeyBytes[:],
				) {

					return fmt.Errorf("wrong outgoing edge")
				}

				numNodeChans++

				return nil
			},
		)
		require.NoError(t, err)
	}
	require.Len(t, chanIndex, 0)

	// We count the channels for both nodes, so there should be double the
	// amount now. Except for the very last node, that doesn't have any
	// channels to make the loop easier in fillTestGraph().
	require.Equal(t, numChannels*2*(numNodes-1), numNodeChans)
}

func fillTestGraph(t require.TestingT, graph *ChannelGraph, numNodes,
	numChannels int) (map[uint64]struct{}, []*LightningNode) {

	nodes := make([]*LightningNode, numNodes)
	nodeIndex := map[string]struct{}{}
	for i := 0; i < numNodes; i++ {
		node, err := createTestVertex(graph.db)
		require.NoError(t, err)

		nodes[i] = node
		nodeIndex[node.Alias] = struct{}{}
	}

	// Add each of the nodes into the graph, they should be inserted
	// without error.
	for _, node := range nodes {
		require.NoError(t, graph.AddLightningNode(node))
	}

	// Iterate over each node as returned by the graph, if all nodes are
	// reached, then the map created above should be empty.
	err := graph.ForEachNode(func(_ kvdb.RTx, node *LightningNode) error {
		delete(nodeIndex, node.Alias)
		return nil
	})
	require.NoError(t, err)
	require.Len(t, nodeIndex, 0)

	// Create a number of channels between each of the node pairs generated
	// above. This will result in numChannels*(numNodes-1) channels.
	chanIndex := map[uint64]struct{}{}
	for n := 0; n < numNodes-1; n++ {
		node1 := nodes[n]
		node2 := nodes[n+1]
		if bytes.Compare(node1.PubKeyBytes[:], node2.PubKeyBytes[:]) == -1 {
			node1, node2 = node2, node1
		}

		for i := 0; i < numChannels; i++ {
			txHash := sha256.Sum256([]byte{byte(i)})
			chanID := uint64((n << 8) + i + 1)
			op := wire.OutPoint{
				Hash:  txHash,
				Index: 0,
			}

			edgeInfo := models.ChannelEdgeInfo{
				ChannelID: chanID,
				ChainHash: key,
				AuthProof: &models.ChannelAuthProof{
					NodeSig1Bytes:    testSig.Serialize(),
					NodeSig2Bytes:    testSig.Serialize(),
					BitcoinSig1Bytes: testSig.Serialize(),
					BitcoinSig2Bytes: testSig.Serialize(),
				},
				ChannelPoint: op,
				Capacity:     1000,
			}
			copy(edgeInfo.NodeKey1Bytes[:], node1.PubKeyBytes[:])
			copy(edgeInfo.NodeKey2Bytes[:], node2.PubKeyBytes[:])
			copy(edgeInfo.BitcoinKey1Bytes[:], node1.PubKeyBytes[:])
			copy(edgeInfo.BitcoinKey2Bytes[:], node2.PubKeyBytes[:])
			err := graph.AddChannelEdge(&edgeInfo)
			require.NoError(t, err)

			// Create and add an edge with random data that points
			// from node1 -> node2.
			edge := randEdgePolicy(chanID, graph.db)
			edge.ChannelFlags = 0
			edge.ToNode = node2.PubKeyBytes
			edge.SigBytes = testSig.Serialize()
			require.NoError(t, graph.UpdateEdgePolicy(edge))

			// Create another random edge that points from
			// node2 -> node1 this time.
			edge = randEdgePolicy(chanID, graph.db)
			edge.ChannelFlags = 1
			edge.ToNode = node1.PubKeyBytes
			edge.SigBytes = testSig.Serialize()
			require.NoError(t, graph.UpdateEdgePolicy(edge))

			chanIndex[chanID] = struct{}{}
		}
	}

	return chanIndex, nodes
}

func assertPruneTip(t *testing.T, graph *ChannelGraph, blockHash *chainhash.Hash,
	blockHeight uint32) {

	pruneHash, pruneHeight, err := graph.PruneTip()
	if err != nil {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: unable to fetch prune tip: %v", line, err)
	}
	if !bytes.Equal(blockHash[:], pruneHash[:]) {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line: %v, prune tips don't match, expected %x got %x",
			line, blockHash, pruneHash)
	}
	if pruneHeight != blockHeight {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: prune heights don't match, expected %v "+
			"got %v", line, blockHeight, pruneHeight)
	}
}

func assertNumChans(t *testing.T, graph *ChannelGraph, n int) {
	numChans := 0
	if err := graph.ForEachChannel(func(*models.ChannelEdgeInfo,
		*models.ChannelEdgePolicy,
		*models.ChannelEdgePolicy) error {

		numChans++
		return nil
	}); err != nil {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: unable to scan channels: %v", line, err)
	}
	if numChans != n {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: expected %v chans instead have %v", line,
			n, numChans)
	}
}

func assertNumNodes(t *testing.T, graph *ChannelGraph, n int) {
	numNodes := 0
	err := graph.ForEachNode(func(_ kvdb.RTx, _ *LightningNode) error {
		numNodes++
		return nil
	})
	if err != nil {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: unable to scan nodes: %v", line, err)
	}

	if numNodes != n {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: expected %v nodes, got %v", line, n, numNodes)
	}
}

func assertChanViewEqual(t *testing.T, a []EdgePoint, b []EdgePoint) {
	if len(a) != len(b) {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: chan views don't match", line)
	}

	chanViewSet := make(map[wire.OutPoint]struct{})
	for _, op := range a {
		chanViewSet[op.OutPoint] = struct{}{}
	}

	for _, op := range b {
		if _, ok := chanViewSet[op.OutPoint]; !ok {
			_, _, line, _ := runtime.Caller(1)
			t.Fatalf("line %v: chanPoint(%v) not found in first "+
				"view", line, op)
		}
	}
}

func assertChanViewEqualChanPoints(t *testing.T, a []EdgePoint, b []*wire.OutPoint) {
	if len(a) != len(b) {
		_, _, line, _ := runtime.Caller(1)
		t.Fatalf("line %v: chan views don't match", line)
	}

	chanViewSet := make(map[wire.OutPoint]struct{})
	for _, op := range a {
		chanViewSet[op.OutPoint] = struct{}{}
	}

	for _, op := range b {
		if _, ok := chanViewSet[*op]; !ok {
			_, _, line, _ := runtime.Caller(1)
			t.Fatalf("line %v: chanPoint(%v) not found in first "+
				"view", line, op)
		}
	}
}

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

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	sourceNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create source node")
	if err := graph.SetSourceNode(sourceNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	// As initial set up for the test, we'll create a graph with 5 vertexes
	// and enough edges to create a fully connected graph. The graph will
	// be rather simple, representing a straight line.
	const numNodes = 5
	graphNodes := make([]*LightningNode, numNodes)
	for i := 0; i < numNodes; i++ {
		node, err := createTestVertex(graph.db)
		if err != nil {
			t.Fatalf("unable to create node: %v", err)
		}

		if err := graph.AddLightningNode(node); err != nil {
			t.Fatalf("unable to add node: %v", err)
		}

		graphNodes[i] = node
	}

	// With the vertexes created, we'll next create a series of channels
	// between them.
	channelPoints := make([]*wire.OutPoint, 0, numNodes-1)
	edgePoints := make([]EdgePoint, 0, numNodes-1)
	for i := 0; i < numNodes-1; i++ {
		txHash := sha256.Sum256([]byte{byte(i)})
		chanID := uint64(i + 1)
		op := wire.OutPoint{
			Hash:  txHash,
			Index: 0,
		}

		channelPoints = append(channelPoints, &op)

		edgeInfo := models.ChannelEdgeInfo{
			ChannelID: chanID,
			ChainHash: key,
			AuthProof: &models.ChannelAuthProof{
				NodeSig1Bytes:    testSig.Serialize(),
				NodeSig2Bytes:    testSig.Serialize(),
				BitcoinSig1Bytes: testSig.Serialize(),
				BitcoinSig2Bytes: testSig.Serialize(),
			},
			ChannelPoint: op,
			Capacity:     1000,
		}
		copy(edgeInfo.NodeKey1Bytes[:], graphNodes[i].PubKeyBytes[:])
		copy(edgeInfo.NodeKey2Bytes[:], graphNodes[i+1].PubKeyBytes[:])
		copy(edgeInfo.BitcoinKey1Bytes[:], graphNodes[i].PubKeyBytes[:])
		copy(edgeInfo.BitcoinKey2Bytes[:], graphNodes[i+1].PubKeyBytes[:])
		if err := graph.AddChannelEdge(&edgeInfo); err != nil {
			t.Fatalf("unable to add node: %v", err)
		}

		pkScript, err := genMultiSigP2WSH(
			edgeInfo.BitcoinKey1Bytes[:], edgeInfo.BitcoinKey2Bytes[:],
		)
		if err != nil {
			t.Fatalf("unable to gen multi-sig p2wsh: %v", err)
		}
		edgePoints = append(edgePoints, EdgePoint{
			FundingPkScript: pkScript,
			OutPoint:        op,
		})

		// Create and add an edge with random data that points from
		// node_i -> node_i+1
		edge := randEdgePolicy(chanID, graph.db)
		edge.ChannelFlags = 0
		edge.ToNode = graphNodes[i].PubKeyBytes
		edge.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}

		// Create another random edge that points from node_i+1 ->
		// node_i this time.
		edge = randEdgePolicy(chanID, graph.db)
		edge.ChannelFlags = 1
		edge.ToNode = graphNodes[i].PubKeyBytes
		edge.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}
	}

	// With all the channel points added, we'll consult the graph to ensure
	// it has the same channel view as the one we just constructed.
	channelView, err := graph.ChannelView()
	require.NoError(t, err, "unable to get graph channel view")
	assertChanViewEqual(t, channelView, edgePoints)

	// Now with our test graph created, we can test the pruning
	// capabilities of the channel graph.

	// First we create a mock block that ends up closing the first two
	// channels.
	var blockHash chainhash.Hash
	copy(blockHash[:], bytes.Repeat([]byte{1}, 32))
	blockHeight := uint32(1)
	block := channelPoints[:2]
	prunedChans, err := graph.PruneGraph(block, &blockHash, blockHeight)
	require.NoError(t, err, "unable to prune graph")
	if len(prunedChans) != 2 {
		t.Fatalf("incorrect number of channels pruned: "+
			"expected %v, got %v", 2, prunedChans)
	}

	// Now ensure that the prune tip has been updated.
	assertPruneTip(t, graph, &blockHash, blockHeight)

	// Count up the number of channels known within the graph, only 2
	// should be remaining.
	assertNumChans(t, graph, 2)

	// Those channels should also be missing from the channel view.
	channelView, err = graph.ChannelView()
	require.NoError(t, err, "unable to get graph channel view")
	assertChanViewEqualChanPoints(t, channelView, channelPoints[2:])

	// Next we'll create a block that doesn't close any channels within the
	// graph to test the negative error case.
	fakeHash := sha256.Sum256([]byte("test prune"))
	nonChannel := &wire.OutPoint{
		Hash:  fakeHash,
		Index: 9,
	}
	blockHash = sha256.Sum256(blockHash[:])
	blockHeight = 2
	prunedChans, err = graph.PruneGraph(
		[]*wire.OutPoint{nonChannel}, &blockHash, blockHeight,
	)
	require.NoError(t, err, "unable to prune graph")

	// No channels should have been detected as pruned.
	if len(prunedChans) != 0 {
		t.Fatalf("channels were pruned but shouldn't have been")
	}

	// Once again, the prune tip should have been updated. We should still
	// see both channels and their participants, along with the source node.
	assertPruneTip(t, graph, &blockHash, blockHeight)
	assertNumChans(t, graph, 2)
	assertNumNodes(t, graph, 4)

	// Finally, create a block that prunes the remainder of the channels
	// from the graph.
	blockHash = sha256.Sum256(blockHash[:])
	blockHeight = 3
	prunedChans, err = graph.PruneGraph(
		channelPoints[2:], &blockHash, blockHeight,
	)
	require.NoError(t, err, "unable to prune graph")

	// The remainder of the channels should have been pruned from the
	// graph.
	if len(prunedChans) != 2 {
		t.Fatalf("incorrect number of channels pruned: "+
			"expected %v, got %v", 2, len(prunedChans))
	}

	// The prune tip should be updated, no channels should be found, and
	// only the source node should remain within the current graph.
	assertPruneTip(t, graph, &blockHash, blockHeight)
	assertNumChans(t, graph, 0)
	assertNumNodes(t, graph, 1)

	// Finally, the channel view at this point in the graph should now be
	// completely empty.  Those channels should also be missing from the
	// channel view.
	channelView, err = graph.ChannelView()
	require.NoError(t, err, "unable to get graph channel view")
	if len(channelView) != 0 {
		t.Fatalf("channel view should be empty, instead have: %v",
			channelView)
	}
}

// TestHighestChanID tests that we're able to properly retrieve the highest
// known channel ID in the database.
func TestHighestChanID(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// If we don't yet have any channels in the database, then we should
	// get a channel ID of zero if we ask for the highest channel ID.
	bestID, err := graph.HighestChanID()
	require.NoError(t, err, "unable to get highest ID")
	if bestID != 0 {
		t.Fatalf("best ID w/ no chan should be zero, is instead: %v",
			bestID)
	}

	// Next, we'll insert two channels into the database, with each channel
	// connecting the same two nodes.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// The first channel with be at height 10, while the other will be at
	// height 100.
	edge1, _ := createEdge(10, 0, 0, 0, node1, node2)
	edge2, chanID2 := createEdge(100, 0, 0, 0, node1, node2)

	if err := graph.AddChannelEdge(&edge1); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	if err := graph.AddChannelEdge(&edge2); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	// Now that the edges has been inserted, we'll query for the highest
	// known channel ID in the database.
	bestID, err = graph.HighestChanID()
	require.NoError(t, err, "unable to get highest ID")

	if bestID != chanID2.ToUint64() {
		t.Fatalf("expected %v got %v for best chan ID: ",
			chanID2.ToUint64(), bestID)
	}

	// If we add another edge, then the current best chan ID should be
	// updated as well.
	edge3, chanID3 := createEdge(1000, 0, 0, 0, node1, node2)
	if err := graph.AddChannelEdge(&edge3); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	bestID, err = graph.HighestChanID()
	require.NoError(t, err, "unable to get highest ID")

	if bestID != chanID3.ToUint64() {
		t.Fatalf("expected %v got %v for best chan ID: ",
			chanID3.ToUint64(), bestID)
	}
}

// TestChanUpdatesInHorizon tests the we're able to properly retrieve all known
// channel updates within a specific time horizon. It also tests that upon
// insertion of a new edge, the edge update index is updated properly.
func TestChanUpdatesInHorizon(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// If we issue an arbitrary query before any channel updates are
	// inserted in the database, we should get zero results.
	chanUpdates, err := graph.ChanUpdatesInHorizon(
		time.Unix(999, 0), time.Unix(9999, 0),
	)
	require.NoError(t, err, "unable to updates for updates")
	if len(chanUpdates) != 0 {
		t.Fatalf("expected 0 chan updates, instead got %v",
			len(chanUpdates))
	}

	// We'll start by creating two nodes which will seed our test graph.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// We'll now create 10 channels between the two nodes, with update
	// times 10 seconds after each other.
	const numChans = 10
	startTime := time.Unix(1234, 0)
	endTime := startTime
	edges := make([]ChannelEdge, 0, numChans)
	for i := 0; i < numChans; i++ {
		channel, chanID := createEdge(
			uint32(i*10), 0, 0, 0, node1, node2,
		)

		if err := graph.AddChannelEdge(&channel); err != nil {
			t.Fatalf("unable to create channel edge: %v", err)
		}

		edge1UpdateTime := endTime
		edge2UpdateTime := edge1UpdateTime.Add(time.Second)
		endTime = endTime.Add(time.Second * 10)

		edge1 := newEdgePolicy(
			chanID.ToUint64(), graph.db, edge1UpdateTime.Unix(),
		)
		edge1.ChannelFlags = 0
		edge1.ToNode = node2.PubKeyBytes
		edge1.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge1); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}

		edge2 := newEdgePolicy(
			chanID.ToUint64(), graph.db, edge2UpdateTime.Unix(),
		)
		edge2.ChannelFlags = 1
		edge2.ToNode = node1.PubKeyBytes
		edge2.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge2); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}

		edges = append(edges, ChannelEdge{
			Info:    &channel,
			Policy1: edge1,
			Policy2: edge2,
		})
	}

	// With our channels loaded, we'll now start our series of queries.
	queryCases := []struct {
		start time.Time
		end   time.Time

		resp []ChannelEdge
	}{
		// If we query for a time range that's strictly below our set
		// of updates, then we'll get an empty result back.
		{
			start: time.Unix(100, 0),
			end:   time.Unix(200, 0),
		},

		// If we query for a time range that's well beyond our set of
		// updates, we should get an empty set of results back.
		{
			start: time.Unix(99999, 0),
			end:   time.Unix(999999, 0),
		},

		// If we query for the start time, and 10 seconds directly
		// after it, we should only get a single update, that first
		// one.
		{
			start: time.Unix(1234, 0),
			end:   startTime.Add(time.Second * 10),

			resp: []ChannelEdge{edges[0]},
		},

		// If we add 10 seconds past the first update, and then
		// subtract 10 from the last update, then we should only get
		// the 8 edges in the middle.
		{
			start: startTime.Add(time.Second * 10),
			end:   endTime.Add(-time.Second * 10),

			resp: edges[1:9],
		},

		// If we use the start and end time as is, we should get the
		// entire range.
		{
			start: startTime,
			end:   endTime,

			resp: edges,
		},
	}
	for _, queryCase := range queryCases {
		resp, err := graph.ChanUpdatesInHorizon(
			queryCase.start, queryCase.end,
		)
		if err != nil {
			t.Fatalf("unable to query for updates: %v", err)
		}

		if len(resp) != len(queryCase.resp) {
			t.Fatalf("expected %v chans, got %v chans",
				len(queryCase.resp), len(resp))

		}

		for i := 0; i < len(resp); i++ {
			chanExp := queryCase.resp[i]
			chanRet := resp[i]

			assertEdgeInfoEqual(t, chanExp.Info, chanRet.Info)

			err := compareEdgePolicies(chanExp.Policy1, chanRet.Policy1)
			if err != nil {
				t.Fatal(err)
			}
			compareEdgePolicies(chanExp.Policy2, chanRet.Policy2)
			if err != nil {
				t.Fatal(err)
			}
		}
	}
}

// TestNodeUpdatesInHorizon tests that we're able to properly scan and retrieve
// the most recent node updates within a particular time horizon.
func TestNodeUpdatesInHorizon(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	startTime := time.Unix(1234, 0)
	endTime := startTime

	// If we issue an arbitrary query before we insert any nodes into the
	// database, then we shouldn't get any results back.
	nodeUpdates, err := graph.NodeUpdatesInHorizon(
		time.Unix(999, 0), time.Unix(9999, 0),
	)
	require.NoError(t, err, "unable to query for node updates")
	if len(nodeUpdates) != 0 {
		t.Fatalf("expected 0 node updates, instead got %v",
			len(nodeUpdates))
	}

	// We'll create 10 node announcements, each with an update timestamp 10
	// seconds after the other.
	const numNodes = 10
	nodeAnns := make([]LightningNode, 0, numNodes)
	for i := 0; i < numNodes; i++ {
		nodeAnn, err := createTestVertex(graph.db)
		if err != nil {
			t.Fatalf("unable to create test vertex: %v", err)
		}

		// The node ann will use the current end time as its last
		// update them, then we'll add 10 seconds in order to create
		// the proper update time for the next node announcement.
		updateTime := endTime
		endTime = updateTime.Add(time.Second * 10)

		nodeAnn.LastUpdate = updateTime

		nodeAnns = append(nodeAnns, *nodeAnn)

		if err := graph.AddLightningNode(nodeAnn); err != nil {
			t.Fatalf("unable to add lightning node: %v", err)
		}
	}

	queryCases := []struct {
		start time.Time
		end   time.Time

		resp []LightningNode
	}{
		// If we query for a time range that's strictly below our set
		// of updates, then we'll get an empty result back.
		{
			start: time.Unix(100, 0),
			end:   time.Unix(200, 0),
		},

		// If we query for a time range that's well beyond our set of
		// updates, we should get an empty set of results back.
		{
			start: time.Unix(99999, 0),
			end:   time.Unix(999999, 0),
		},

		// If we skip he first time epoch with out start time, then we
		// should get back every now but the first.
		{
			start: startTime.Add(time.Second * 10),
			end:   endTime,

			resp: nodeAnns[1:],
		},

		// If we query for the range as is, we should get all 10
		// announcements back.
		{
			start: startTime,
			end:   endTime,

			resp: nodeAnns,
		},

		// If we reduce the ending time by 10 seconds, then we should
		// get all but the last node we inserted.
		{
			start: startTime,
			end:   endTime.Add(-time.Second * 10),

			resp: nodeAnns[:9],
		},
	}
	for _, queryCase := range queryCases {
		resp, err := graph.NodeUpdatesInHorizon(queryCase.start, queryCase.end)
		if err != nil {
			t.Fatalf("unable to query for nodes: %v", err)
		}

		if len(resp) != len(queryCase.resp) {
			t.Fatalf("expected %v nodes, got %v nodes",
				len(queryCase.resp), len(resp))

		}

		for i := 0; i < len(resp); i++ {
			err := compareNodes(&queryCase.resp[i], &resp[i])
			if err != nil {
				t.Fatal(err)
			}
		}
	}
}

// TestFilterKnownChanIDs tests that we're able to properly perform the set
// differences of an incoming set of channel ID's, and those that we already
// know of on disk.
func TestFilterKnownChanIDs(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	isZombieUpdate := func(updateTime1 time.Time,
		updateTime2 time.Time) bool {

		return true
	}

	var (
		scid1 = lnwire.ShortChannelID{BlockHeight: 1}
		scid2 = lnwire.ShortChannelID{BlockHeight: 2}
		scid3 = lnwire.ShortChannelID{BlockHeight: 3}
	)

	// If we try to filter out a set of channel ID's before we even know of
	// any channels, then we should get the entire set back.
	preChanIDs := []ChannelUpdateInfo{
		{ShortChannelID: scid1},
		{ShortChannelID: scid2},
		{ShortChannelID: scid3},
	}
	filteredIDs, err := graph.FilterKnownChanIDs(preChanIDs, isZombieUpdate)
	require.NoError(t, err, "unable to filter chan IDs")
	require.EqualValues(t, []uint64{
		scid1.ToUint64(),
		scid2.ToUint64(),
		scid3.ToUint64(),
	}, filteredIDs)

	// We'll start by creating two nodes which will seed our test graph.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// Next, we'll add 5 channel ID's to the graph, each of them having a
	// block height 10 blocks after the previous.
	const numChans = 5
	chanIDs := make([]ChannelUpdateInfo, 0, numChans)
	for i := 0; i < numChans; i++ {
		channel, chanID := createEdge(
			uint32(i*10), 0, 0, 0, node1, node2,
		)

		if err := graph.AddChannelEdge(&channel); err != nil {
			t.Fatalf("unable to create channel edge: %v", err)
		}

		chanIDs = append(chanIDs, NewChannelUpdateInfo(
			chanID, time.Time{}, time.Time{},
		))
	}

	const numZombies = 5
	zombieIDs := make([]ChannelUpdateInfo, 0, numZombies)
	for i := 0; i < numZombies; i++ {
		channel, chanID := createEdge(
			uint32(i*10+1), 0, 0, 0, node1, node2,
		)
		if err := graph.AddChannelEdge(&channel); err != nil {
			t.Fatalf("unable to create channel edge: %v", err)
		}
		err := graph.DeleteChannelEdges(false, true, channel.ChannelID)
		if err != nil {
			t.Fatalf("unable to mark edge zombie: %v", err)
		}

		zombieIDs = append(
			zombieIDs, ChannelUpdateInfo{ShortChannelID: chanID},
		)
	}

	queryCases := []struct {
		queryIDs []ChannelUpdateInfo

		resp []ChannelUpdateInfo
	}{
		// If we attempt to filter out all chanIDs we know of, the
		// response should be the empty set.
		{
			queryIDs: chanIDs,
		},
		// If we attempt to filter out all zombies that we know of, the
		// response should be the empty set.
		{
			queryIDs: zombieIDs,
		},

		// If we query for a set of ID's that we didn't insert, we
		// should get the same set back.
		{
			queryIDs: []ChannelUpdateInfo{
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 99,
					},
				},
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 100,
					},
				},
			},
			resp: []ChannelUpdateInfo{
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 99,
					},
				},
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 100,
					},
				},
			},
		},

		// If we query for a super-set of our the chan ID's inserted,
		// we should only get those new chanIDs back.
		{
			queryIDs: append(chanIDs, []ChannelUpdateInfo{
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 99,
					},
				},
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 101,
					},
				},
			}...),
			resp: []ChannelUpdateInfo{
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 99,
					},
				},
				{
					ShortChannelID: lnwire.ShortChannelID{
						BlockHeight: 101,
					},
				},
			},
		},
	}

	for _, queryCase := range queryCases {
		resp, err := graph.FilterKnownChanIDs(
			queryCase.queryIDs, isZombieUpdate,
		)
		require.NoError(t, err)

		expectedSCIDs := make([]uint64, len(queryCase.resp))
		for i, info := range queryCase.resp {
			expectedSCIDs[i] = info.ShortChannelID.ToUint64()
		}

		if len(expectedSCIDs) == 0 {
			expectedSCIDs = nil
		}

		require.EqualValues(t, expectedSCIDs, resp)
	}
}

// TestStressTestChannelGraphAPI is a stress test that concurrently calls some
// of the ChannelGraph methods in various orders in order to ensure that no
// deadlock can occur. This test currently focuses on stress testing all the
// methods that acquire the cache mutex along with the DB mutex.
func TestStressTestChannelGraphAPI(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err)

	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	require.NoError(t, graph.AddLightningNode(node1))

	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	require.NoError(t, graph.AddLightningNode(node2))

	err = graph.SetSourceNode(node1)
	require.NoError(t, err)

	type chanInfo struct {
		info models.ChannelEdgeInfo
		id   lnwire.ShortChannelID
	}

	var (
		chans []*chanInfo
		mu    sync.RWMutex
	)

	// newBlockHeight returns a random block height between 0 and 100.
	newBlockHeight := func() uint32 {
		return uint32(rand.Int31n(100))
	}

	// addNewChan is a will create and return a new random channel and will
	// add it to the set of channels.
	addNewChan := func() *chanInfo {
		mu.Lock()
		defer mu.Unlock()

		channel, chanID := createEdge(
			newBlockHeight(), rand.Uint32(), uint16(rand.Int()),
			rand.Uint32(), node1, node2,
		)

		newChan := &chanInfo{
			info: channel,
			id:   chanID,
		}
		chans = append(chans, newChan)

		return newChan
	}

	// getRandChan picks a random channel from the set and returns it.
	getRandChan := func() *chanInfo {
		mu.RLock()
		defer mu.RUnlock()

		if len(chans) == 0 {
			return nil
		}

		return chans[rand.Intn(len(chans))]
	}

	// getRandChanSet returns a random set of channels.
	getRandChanSet := func() []*chanInfo {
		mu.RLock()
		defer mu.RUnlock()

		if len(chans) == 0 {
			return nil
		}

		start := rand.Intn(len(chans))
		end := rand.Intn(len(chans))

		if end < start {
			start, end = end, start
		}

		var infoCopy []*chanInfo
		for i := start; i < end; i++ {
			infoCopy = append(infoCopy, &chanInfo{
				info: chans[i].info,
				id:   chans[i].id,
			})
		}

		return infoCopy
	}

	// delChan deletes the channel with the given ID from the set if it
	// exists.
	delChan := func(id lnwire.ShortChannelID) {
		mu.Lock()
		defer mu.Unlock()

		index := -1
		for i, c := range chans {
			if c.id == id {
				index = i
				break
			}
		}

		if index == -1 {
			return
		}

		chans = append(chans[:index], chans[index+1:]...)
	}

	var blockHash chainhash.Hash
	copy(blockHash[:], bytes.Repeat([]byte{2}, 32))

	var methodsMu sync.Mutex
	methods := []struct {
		name string
		fn   func() error
	}{
		{
			name: "MarkEdgeZombie",
			fn: func() error {
				channel := getRandChan()
				if channel == nil {
					return nil
				}

				return graph.MarkEdgeZombie(
					channel.id.ToUint64(),
					node1.PubKeyBytes,
					node2.PubKeyBytes,
				)
			},
		},
		{
			name: "FilterKnownChanIDs",
			fn: func() error {
				chanSet := getRandChanSet()
				var chanIDs []ChannelUpdateInfo

				for _, c := range chanSet {
					chanIDs = append(
						chanIDs,
						ChannelUpdateInfo{
							ShortChannelID: c.id,
						},
					)
				}

				_, err := graph.FilterKnownChanIDs(
					chanIDs,
					func(t time.Time, t2 time.Time) bool {
						return rand.Intn(2) == 0
					},
				)

				return err
			},
		},
		{
			name: "HasChannelEdge",
			fn: func() error {
				channel := getRandChan()
				if channel == nil {
					return nil
				}

				_, _, _, _, err := graph.HasChannelEdge(
					channel.id.ToUint64(),
				)

				return err
			},
		},
		{
			name: "PruneGraph",
			fn: func() error {
				chanSet := getRandChanSet()
				var spentOutpoints []*wire.OutPoint

				for _, c := range chanSet {
					spentOutpoints = append(
						spentOutpoints,
						&c.info.ChannelPoint,
					)
				}

				_, err := graph.PruneGraph(
					spentOutpoints, &blockHash, 100,
				)

				return err
			},
		},
		{
			name: "ChanUpdateInHorizon",
			fn: func() error {
				_, err := graph.ChanUpdatesInHorizon(
					time.Now().Add(-time.Hour), time.Now(),
				)

				return err
			},
		},
		{
			name: "DeleteChannelEdges",
			fn: func() error {
				var (
					strictPruning = rand.Intn(2) == 0
					markZombie    = rand.Intn(2) == 0
					channels      = getRandChanSet()
					chanIDs       []uint64
				)

				for _, c := range channels {
					chanIDs = append(
						chanIDs, c.id.ToUint64(),
					)
					delChan(c.id)
				}

				err := graph.DeleteChannelEdges(
					strictPruning, markZombie, chanIDs...,
				)
				if err != nil &&
					!errors.Is(err, ErrEdgeNotFound) {

					return err
				}

				return nil
			},
		},
		{
			name: "DisconnectBlockAtHeight",
			fn: func() error {
				_, err := graph.DisconnectBlockAtHeight(
					newBlockHeight(),
				)

				return err
			},
		},
		{
			name: "AddChannelEdge",
			fn: func() error {
				channel := addNewChan()

				return graph.AddChannelEdge(&channel.info)
			},
		},
	}

	const (
		// concurrencyLevel is the number of concurrent goroutines that
		// will be run simultaneously.
		concurrencyLevel = 10

		// executionCount is the number of methods that will be called
		// per goroutine.
		executionCount = 100
	)

	for i := 0; i < concurrencyLevel; i++ {
		i := i

		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			t.Parallel()

			for j := 0; j < executionCount; j++ {
				// Randomly select a method to execute.
				methodIndex := rand.Intn(len(methods))

				methodsMu.Lock()
				fn := methods[methodIndex].fn
				name := methods[methodIndex].name
				methodsMu.Unlock()

				err := fn()
				require.NoErrorf(t, err, name)
			}
		})
	}
}

// TestFilterChannelRange tests that we're able to properly retrieve the full
// set of short channel ID's for a given block range.
func TestFilterChannelRange(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err)

	// We'll first populate our graph with two nodes. All channels created
	// below will be made between these two nodes.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err)
	require.NoError(t, graph.AddLightningNode(node1))

	node2, err := createTestVertex(graph.db)
	require.NoError(t, err)
	require.NoError(t, graph.AddLightningNode(node2))

	// If we try to filter a channel range before we have any channels
	// inserted, we should get an empty slice of results.
	resp, err := graph.FilterChannelRange(10, 100, false)
	require.NoError(t, err)
	require.Empty(t, resp)

	// To start, we'll create a set of channels, two mined in a block 10
	// blocks after the prior one.
	startHeight := uint32(100)
	endHeight := startHeight
	const numChans = 10

	var (
		channelRanges = make(
			[]BlockChannelRange, 0, numChans/2,
		)
		channelRangesWithTimestamps = make(
			[]BlockChannelRange, 0, numChans/2,
		)
	)

	updateTimeSeed := time.Now().Unix()
	maybeAddPolicy := func(chanID uint64, node *LightningNode,
		node2 bool) time.Time {

		var chanFlags lnwire.ChanUpdateChanFlags
		if node2 {
			chanFlags = lnwire.ChanUpdateDirection
		}

		var updateTime = time.Unix(0, 0)
		if rand.Int31n(2) == 0 {
			updateTime = time.Unix(updateTimeSeed, 0)
			err = graph.UpdateEdgePolicy(&models.ChannelEdgePolicy{
				ToNode:       node.PubKeyBytes,
				ChannelFlags: chanFlags,
				ChannelID:    chanID,
				LastUpdate:   updateTime,
			})
			require.NoError(t, err)
		}
		updateTimeSeed++

		return updateTime
	}

	for i := 0; i < numChans/2; i++ {
		chanHeight := endHeight
		channel1, chanID1 := createEdge(
			chanHeight, uint32(i+1), 0, 0, node1, node2,
		)
		require.NoError(t, graph.AddChannelEdge(&channel1))

		channel2, chanID2 := createEdge(
			chanHeight, uint32(i+2), 0, 0, node1, node2,
		)
		require.NoError(t, graph.AddChannelEdge(&channel2))

		chanInfo1 := NewChannelUpdateInfo(
			chanID1, time.Time{}, time.Time{},
		)
		chanInfo2 := NewChannelUpdateInfo(
			chanID2, time.Time{}, time.Time{},
		)
		channelRanges = append(channelRanges, BlockChannelRange{
			Height: chanHeight,
			Channels: []ChannelUpdateInfo{
				chanInfo1, chanInfo2,
			},
		})

		var (
			time1 = maybeAddPolicy(channel1.ChannelID, node1, false)
			time2 = maybeAddPolicy(channel1.ChannelID, node2, true)
			time3 = maybeAddPolicy(channel2.ChannelID, node1, false)
			time4 = maybeAddPolicy(channel2.ChannelID, node2, true)
		)

		chanInfo1 = NewChannelUpdateInfo(
			chanID1, time1, time2,
		)
		chanInfo2 = NewChannelUpdateInfo(
			chanID2, time3, time4,
		)
		channelRangesWithTimestamps = append(
			channelRangesWithTimestamps, BlockChannelRange{
				Height: chanHeight,
				Channels: []ChannelUpdateInfo{
					chanInfo1, chanInfo2,
				},
			},
		)

		endHeight += 10
	}

	// With our channels inserted, we'll construct a series of queries that
	// we'll execute below in order to exercise the features of the
	// FilterKnownChanIDs method.
	tests := []struct {
		name string

		startHeight uint32
		endHeight   uint32

		resp          []BlockChannelRange
		expStartIndex int
		expEndIndex   int
	}{
		// If we query for the entire range, then we should get the same
		// set of short channel IDs back.
		{
			name:        "entire range",
			startHeight: startHeight,
			endHeight:   endHeight,

			resp:          channelRanges,
			expStartIndex: 0,
			expEndIndex:   len(channelRanges),
		},

		// If we query for a range of channels right before our range,
		// we shouldn't get any results back.
		{
			name:        "range before",
			startHeight: 0,
			endHeight:   10,
		},

		// If we only query for the last height (range wise), we should
		// only get that last channel.
		{
			name:        "last height",
			startHeight: endHeight - 10,
			endHeight:   endHeight - 10,

			resp:          channelRanges[4:],
			expStartIndex: 4,
			expEndIndex:   len(channelRanges),
		},

		// If we query for just the first height, we should only get a
		// single channel back (the first one).
		{
			name:        "first height",
			startHeight: startHeight,
			endHeight:   startHeight,

			resp:          channelRanges[:1],
			expStartIndex: 0,
			expEndIndex:   1,
		},

		{
			name:        "subset",
			startHeight: startHeight + 10,
			endHeight:   endHeight - 10,

			resp:          channelRanges[1:5],
			expStartIndex: 1,
			expEndIndex:   5,
		},
	}

	for _, test := range tests {
		test := test

		t.Run(test.name, func(t *testing.T) {
			t.Parallel()

			// First, do the query without requesting timestamps.
			resp, err := graph.FilterChannelRange(
				test.startHeight, test.endHeight, false,
			)
			require.NoError(t, err)

			expRes := channelRanges[test.expStartIndex:test.expEndIndex] //nolint:lll

			if len(expRes) == 0 {
				require.Nil(t, resp)
			} else {
				require.Equal(t, expRes, resp)
			}

			// Now, query the timestamps as well.
			resp, err = graph.FilterChannelRange(
				test.startHeight, test.endHeight, true,
			)
			require.NoError(t, err)

			expRes = channelRangesWithTimestamps[test.expStartIndex:test.expEndIndex] //nolint:lll

			if len(expRes) == 0 {
				require.Nil(t, resp)
			} else {
				require.Equal(t, expRes, resp)
			}
		})
	}
}

// TestFetchChanInfos tests that we're able to properly retrieve the full set
// of ChannelEdge structs for a given set of short channel ID's.
func TestFetchChanInfos(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'll first populate our graph with two nodes. All channels created
	// below will be made between these two nodes.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// We'll make 5 test channels, ensuring we keep track of which channel
	// ID corresponds to a particular ChannelEdge.
	const numChans = 5
	startTime := time.Unix(1234, 0)
	endTime := startTime
	edges := make([]ChannelEdge, 0, numChans)
	edgeQuery := make([]uint64, 0, numChans)
	for i := 0; i < numChans; i++ {
		channel, chanID := createEdge(
			uint32(i*10), 0, 0, 0, node1, node2,
		)

		if err := graph.AddChannelEdge(&channel); err != nil {
			t.Fatalf("unable to create channel edge: %v", err)
		}

		updateTime := endTime
		endTime = updateTime.Add(time.Second * 10)

		edge1 := newEdgePolicy(
			chanID.ToUint64(), graph.db, updateTime.Unix(),
		)
		edge1.ChannelFlags = 0
		edge1.ToNode = node2.PubKeyBytes
		edge1.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge1); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}

		edge2 := newEdgePolicy(
			chanID.ToUint64(), graph.db, updateTime.Unix(),
		)
		edge2.ChannelFlags = 1
		edge2.ToNode = node1.PubKeyBytes
		edge2.SigBytes = testSig.Serialize()
		if err := graph.UpdateEdgePolicy(edge2); err != nil {
			t.Fatalf("unable to update edge: %v", err)
		}

		edges = append(edges, ChannelEdge{
			Info:    &channel,
			Policy1: edge1,
			Policy2: edge2,
		})

		edgeQuery = append(edgeQuery, chanID.ToUint64())
	}

	// Add an additional edge that does not exist. The query should skip
	// this channel and return only infos for the edges that exist.
	edgeQuery = append(edgeQuery, 500)

	// Add an another edge to the query that has been marked as a zombie
	// edge. The query should also skip this channel.
	zombieChan, zombieChanID := createEdge(
		666, 0, 0, 0, node1, node2,
	)
	if err := graph.AddChannelEdge(&zombieChan); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}
	err = graph.DeleteChannelEdges(false, true, zombieChan.ChannelID)
	require.NoError(t, err, "unable to delete and mark edge zombie")
	edgeQuery = append(edgeQuery, zombieChanID.ToUint64())

	// We'll now attempt to query for the range of channel ID's we just
	// inserted into the database. We should get the exact same set of
	// edges back.
	resp, err := graph.FetchChanInfos(edgeQuery)
	require.NoError(t, err, "unable to fetch chan edges")
	if len(resp) != len(edges) {
		t.Fatalf("expected %v edges, instead got %v", len(edges),
			len(resp))
	}

	for i := 0; i < len(resp); i++ {
		err := compareEdgePolicies(resp[i].Policy1, edges[i].Policy1)
		if err != nil {
			t.Fatalf("edge doesn't match: %v", err)
		}
		err = compareEdgePolicies(resp[i].Policy2, edges[i].Policy2)
		if err != nil {
			t.Fatalf("edge doesn't match: %v", err)
		}
		assertEdgeInfoEqual(t, resp[i].Info, edges[i].Info)
	}
}

// TestIncompleteChannelPolicies tests that a channel that only has a policy
// specified on one end is properly returned in ForEachChannel calls from
// both sides.
func TestIncompleteChannelPolicies(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// Create two nodes.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	channel, chanID := createEdge(
		uint32(0), 0, 0, 0, node1, node2,
	)

	if err := graph.AddChannelEdge(&channel); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	// Ensure that channel is reported with unknown policies.
	checkPolicies := func(node *LightningNode, expectedIn, expectedOut bool) {
		calls := 0
		err := graph.ForEachNodeChannel(node.PubKeyBytes,
			func(_ kvdb.RTx, _ *models.ChannelEdgeInfo, outEdge,
				inEdge *models.ChannelEdgePolicy) error {

				if !expectedOut && outEdge != nil {
					t.Fatalf("Expected no outgoing policy")
				}

				if expectedOut && outEdge == nil {
					t.Fatalf("Expected an outgoing policy")
				}

				if !expectedIn && inEdge != nil {
					t.Fatalf("Expected no incoming policy")
				}

				if expectedIn && inEdge == nil {
					t.Fatalf("Expected an incoming policy")
				}

				calls++

				return nil
			})
		if err != nil {
			t.Fatalf("unable to scan channels: %v", err)
		}

		if calls != 1 {
			t.Fatalf("Expected only one callback call")
		}
	}

	checkPolicies(node2, false, false)

	// Only create an edge policy for node1 and leave the policy for node2
	// unknown.
	updateTime := time.Unix(1234, 0)

	edgePolicy := newEdgePolicy(
		chanID.ToUint64(), graph.db, updateTime.Unix(),
	)
	edgePolicy.ChannelFlags = 0
	edgePolicy.ToNode = node2.PubKeyBytes
	edgePolicy.SigBytes = testSig.Serialize()
	if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	checkPolicies(node1, false, true)
	checkPolicies(node2, true, false)

	// Create second policy and assert that both policies are reported
	// as present.
	edgePolicy = newEdgePolicy(
		chanID.ToUint64(), graph.db, updateTime.Unix(),
	)
	edgePolicy.ChannelFlags = 1
	edgePolicy.ToNode = node1.PubKeyBytes
	edgePolicy.SigBytes = testSig.Serialize()
	if err := graph.UpdateEdgePolicy(edgePolicy); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	checkPolicies(node1, true, true)
	checkPolicies(node2, true, true)
}

// TestChannelEdgePruningUpdateIndexDeletion tests that once edges are deleted
// from the graph, then their entries within the update index are also cleaned
// up.
func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	sourceNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create source node")
	if err := graph.SetSourceNode(sourceNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	// We'll first populate our graph with two nodes. All channels created
	// below will be made between these two nodes.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// With the two nodes created, we'll now create a random channel, as
	// well as two edges in the database with distinct update times.
	edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2)
	if err := graph.AddChannelEdge(&edgeInfo); err != nil {
		t.Fatalf("unable to add edge: %v", err)
	}

	edge1 := randEdgePolicy(chanID.ToUint64(), graph.db)
	edge1.ChannelFlags = 0
	edge1.ToNode = node1.PubKeyBytes
	edge1.SigBytes = testSig.Serialize()
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	edge2 := randEdgePolicy(chanID.ToUint64(), graph.db)
	edge2.ChannelFlags = 1
	edge2.ToNode = node2.PubKeyBytes
	edge2.SigBytes = testSig.Serialize()
	if err := graph.UpdateEdgePolicy(edge2); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	// checkIndexTimestamps is a helper function that checks the edge update
	// index only includes the given timestamps.
	checkIndexTimestamps := func(timestamps ...uint64) {
		timestampSet := make(map[uint64]struct{})
		for _, t := range timestamps {
			timestampSet[t] = struct{}{}
		}

		err := kvdb.View(graph.db, func(tx kvdb.RTx) error {
			edges := tx.ReadBucket(edgeBucket)
			if edges == nil {
				return ErrGraphNoEdgesFound
			}
			edgeUpdateIndex := edges.NestedReadBucket(
				edgeUpdateIndexBucket,
			)
			if edgeUpdateIndex == nil {
				return ErrGraphNoEdgesFound
			}

			var numEntries int
			err := edgeUpdateIndex.ForEach(func(k, v []byte) error {
				numEntries++
				return nil
			})
			if err != nil {
				return err
			}

			expectedEntries := len(timestampSet)
			if numEntries != expectedEntries {
				return fmt.Errorf("expected %v entries in the "+
					"update index, got %v", expectedEntries,
					numEntries)
			}

			return edgeUpdateIndex.ForEach(func(k, _ []byte) error {
				t := byteOrder.Uint64(k[:8])
				if _, ok := timestampSet[t]; !ok {
					return fmt.Errorf("found unexpected "+
						"timestamp "+"%d", t)
				}

				return nil
			})
		}, func() {})
		if err != nil {
			t.Fatal(err)
		}
	}

	// With both edges policies added, we'll make sure to check they exist
	// within the edge update index.
	checkIndexTimestamps(
		uint64(edge1.LastUpdate.Unix()),
		uint64(edge2.LastUpdate.Unix()),
	)

	// Now, we'll update the edge policies to ensure the old timestamps are
	// removed from the update index.
	edge1.ChannelFlags = 2
	edge1.LastUpdate = time.Now()
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}
	edge2.ChannelFlags = 3
	edge2.LastUpdate = edge1.LastUpdate.Add(time.Hour)
	if err := graph.UpdateEdgePolicy(edge2); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	// With the policies updated, we should now be able to find their
	// updated entries within the update index.
	checkIndexTimestamps(
		uint64(edge1.LastUpdate.Unix()),
		uint64(edge2.LastUpdate.Unix()),
	)

	// Now we'll prune the graph, removing the edges, and also the update
	// index entries from the database all together.
	var blockHash chainhash.Hash
	copy(blockHash[:], bytes.Repeat([]byte{2}, 32))
	_, err = graph.PruneGraph(
		[]*wire.OutPoint{&edgeInfo.ChannelPoint}, &blockHash, 101,
	)
	require.NoError(t, err, "unable to prune graph")

	// Finally, we'll check the database state one last time to conclude
	// that we should no longer be able to locate _any_ entries within the
	// edge update index.
	checkIndexTimestamps()
}

// TestPruneGraphNodes tests that unconnected vertexes are pruned via the
// PruneSyncState method.
func TestPruneGraphNodes(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'll start off by inserting our source node, to ensure that it's
	// the only node left after we prune the graph.
	sourceNode, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create source node")
	if err := graph.SetSourceNode(sourceNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	// With the source node inserted, we'll now add three nodes to the
	// channel graph, at the end of the scenario, only two of these nodes
	// should still be in the graph.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node3, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node3); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// We'll now add a new edge to the graph, but only actually advertise
	// the edge of *one* of the nodes.
	edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2)
	if err := graph.AddChannelEdge(&edgeInfo); err != nil {
		t.Fatalf("unable to add edge: %v", err)
	}

	// We'll now insert an advertised edge, but it'll only be the edge that
	// points from the first to the second node.
	edge1 := randEdgePolicy(chanID.ToUint64(), graph.db)
	edge1.ChannelFlags = 0
	edge1.ToNode = node1.PubKeyBytes
	edge1.SigBytes = testSig.Serialize()
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	// We'll now initiate a around of graph pruning.
	if err := graph.PruneGraphNodes(); err != nil {
		t.Fatalf("unable to prune graph nodes: %v", err)
	}

	// At this point, there should be 3 nodes left in the graph still: the
	// source node (which can't be pruned), and node 1+2. Nodes 1 and two
	// should still be left in the graph as there's half of an advertised
	// edge between them.
	assertNumNodes(t, graph, 3)

	// Finally, we'll ensure that node3, the only fully unconnected node as
	// properly deleted from the graph and not another node in its place.
	_, err = graph.FetchLightningNode(node3.PubKeyBytes)
	if err == nil {
		t.Fatalf("node 3 should have been deleted!")
	}
}

// TestAddChannelEdgeShellNodes tests that when we attempt to add a ChannelEdge
// to the graph, one or both of the nodes the edge involves aren't found in the
// database, then shell edges are created for each node if needed.
func TestAddChannelEdgeShellNodes(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// To start, we'll create two nodes, and only add one of them to the
	// channel graph.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	// We'll now create an edge between the two nodes, as a result, node2
	// should be inserted into the database as a shell node.
	edgeInfo, _ := createEdge(100, 0, 0, 0, node1, node2)
	if err := graph.AddChannelEdge(&edgeInfo); err != nil {
		t.Fatalf("unable to add edge: %v", err)
	}

	// Ensure that node1 was inserted as a full node, while node2 only has
	// a shell node present.
	node1, err = graph.FetchLightningNode(node1.PubKeyBytes)
	require.NoError(t, err, "unable to fetch node1")
	if !node1.HaveNodeAnnouncement {
		t.Fatalf("have shell announcement for node1, shouldn't")
	}

	node2, err = graph.FetchLightningNode(node2.PubKeyBytes)
	require.NoError(t, err, "unable to fetch node2")
	if node2.HaveNodeAnnouncement {
		t.Fatalf("should have shell announcement for node2, but is full")
	}
}

// TestNodePruningUpdateIndexDeletion tests that once a node has been removed
// from the channel graph, we also remove the entry from the update index as
// well.
func TestNodePruningUpdateIndexDeletion(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'll first populate our graph with a single node that will be
	// removed shortly.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// We'll confirm that we can retrieve the node using
	// NodeUpdatesInHorizon, using a time that's slightly beyond the last
	// update time of our test node.
	startTime := time.Unix(9, 0)
	endTime := node1.LastUpdate.Add(time.Minute)
	nodesInHorizon, err := graph.NodeUpdatesInHorizon(startTime, endTime)
	require.NoError(t, err, "unable to fetch nodes in horizon")

	// We should only have a single node, and that node should exactly
	// match the node we just inserted.
	if len(nodesInHorizon) != 1 {
		t.Fatalf("should have 1 nodes instead have: %v",
			len(nodesInHorizon))
	}
	if err := compareNodes(node1, &nodesInHorizon[0]); err != nil {
		t.Fatalf("nodes don't match: %v", err)
	}

	// We'll now delete the node from the graph, this should result in it
	// being removed from the update index as well.
	if err := graph.DeleteLightningNode(node1.PubKeyBytes); err != nil {
		t.Fatalf("unable to delete node: %v", err)
	}

	// Now that the node has been deleted, we'll again query the nodes in
	// the horizon. This time we should have no nodes at all.
	nodesInHorizon, err = graph.NodeUpdatesInHorizon(startTime, endTime)
	require.NoError(t, err, "unable to fetch nodes in horizon")

	if len(nodesInHorizon) != 0 {
		t.Fatalf("should have zero nodes instead have: %v",
			len(nodesInHorizon))
	}
}

// TestNodeIsPublic ensures that we properly detect nodes that are seen as
// public within the network graph.
func TestNodeIsPublic(t *testing.T) {
	t.Parallel()

	// We'll start off the test by creating a small network of 3
	// participants with the following graph:
	//
	//	Alice <-> Bob <-> Carol
	//
	// We'll need to create a separate database and channel graph for each
	// participant to replicate real-world scenarios (private edges being in
	// some graphs but not others, etc.).
	aliceGraph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")
	aliceNode, err := createTestVertex(aliceGraph.db)
	require.NoError(t, err, "unable to create test node")
	if err := aliceGraph.SetSourceNode(aliceNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	bobGraph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")
	bobNode, err := createTestVertex(bobGraph.db)
	require.NoError(t, err, "unable to create test node")
	if err := bobGraph.SetSourceNode(bobNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	carolGraph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")
	carolNode, err := createTestVertex(carolGraph.db)
	require.NoError(t, err, "unable to create test node")
	if err := carolGraph.SetSourceNode(carolNode); err != nil {
		t.Fatalf("unable to set source node: %v", err)
	}

	aliceBobEdge, _ := createEdge(10, 0, 0, 0, aliceNode, bobNode)
	bobCarolEdge, _ := createEdge(10, 1, 0, 1, bobNode, carolNode)

	// After creating all of our nodes and edges, we'll add them to each
	// participant's graph.
	nodes := []*LightningNode{aliceNode, bobNode, carolNode}
	edges := []*models.ChannelEdgeInfo{&aliceBobEdge, &bobCarolEdge}
	graphs := []*ChannelGraph{aliceGraph, bobGraph, carolGraph}
	for _, graph := range graphs {
		for _, node := range nodes {
			if err := graph.AddLightningNode(node); err != nil {
				t.Fatalf("unable to add node: %v", err)
			}
		}
		for _, edge := range edges {
			if err := graph.AddChannelEdge(edge); err != nil {
				t.Fatalf("unable to add edge: %v", err)
			}
		}
	}

	// checkNodes is a helper closure that will be used to assert that the
	// given nodes are seen as public/private within the given graphs.
	checkNodes := func(nodes []*LightningNode, graphs []*ChannelGraph,
		public bool) {

		t.Helper()

		for _, node := range nodes {
			for _, graph := range graphs {
				isPublic, err := graph.IsPublicNode(node.PubKeyBytes)
				if err != nil {
					t.Fatalf("unable to determine if pivot "+
						"is public: %v", err)
				}

				switch {
				case isPublic && !public:
					t.Fatalf("expected %x to be private",
						node.PubKeyBytes)
				case !isPublic && public:
					t.Fatalf("expected %x to be public",
						node.PubKeyBytes)
				}
			}
		}
	}

	// Due to the way the edges were set up above, we'll make sure each node
	// can correctly determine that every other node is public.
	checkNodes(nodes, graphs, true)

	// Now, we'll remove the edge between Alice and Bob from everyone's
	// graph. This will make Alice be seen as a private node as it no longer
	// has any advertised edges.
	for _, graph := range graphs {
		err := graph.DeleteChannelEdges(
			false, true, aliceBobEdge.ChannelID,
		)
		if err != nil {
			t.Fatalf("unable to remove edge: %v", err)
		}
	}
	checkNodes(
		[]*LightningNode{aliceNode},
		[]*ChannelGraph{bobGraph, carolGraph},
		false,
	)

	// We'll also make the edge between Bob and Carol private. Within Bob's
	// and Carol's graph, the edge will exist, but it will not have a proof
	// that allows it to be advertised. Within Alice's graph, we'll
	// completely remove the edge as it is not possible for her to know of
	// it without it being advertised.
	for _, graph := range graphs {
		err := graph.DeleteChannelEdges(
			false, true, bobCarolEdge.ChannelID,
		)
		if err != nil {
			t.Fatalf("unable to remove edge: %v", err)
		}

		if graph == aliceGraph {
			continue
		}

		bobCarolEdge.AuthProof = nil
		if err := graph.AddChannelEdge(&bobCarolEdge); err != nil {
			t.Fatalf("unable to add edge: %v", err)
		}
	}

	// With the modifications above, Bob should now be seen as a private
	// node from both Alice's and Carol's perspective.
	checkNodes(
		[]*LightningNode{bobNode},
		[]*ChannelGraph{aliceGraph, carolGraph},
		false,
	)
}

// TestDisabledChannelIDs ensures that the disabled channels within the
// disabledEdgePolicyBucket are managed properly and the list returned from
// DisabledChannelIDs is correct.
func TestDisabledChannelIDs(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// Create first node and add it to the graph.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// Create second node and add it to the graph.
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	// Adding a new channel edge to the graph.
	edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2)
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}

	if err := graph.AddChannelEdge(edgeInfo); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	// Ensure no disabled channels exist in the bucket on start.
	disabledChanIds, err := graph.DisabledChannelIDs()
	require.NoError(t, err, "unable to get disabled channel ids")
	if len(disabledChanIds) > 0 {
		t.Fatalf("expected empty disabled channels, got %v disabled channels",
			len(disabledChanIds))
	}

	// Add one disabled policy and ensure the channel is still not in the
	// disabled list.
	edge1.ChannelFlags |= lnwire.ChanUpdateDisabled
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}
	disabledChanIds, err = graph.DisabledChannelIDs()
	require.NoError(t, err, "unable to get disabled channel ids")
	if len(disabledChanIds) > 0 {
		t.Fatalf("expected empty disabled channels, got %v disabled channels",
			len(disabledChanIds))
	}

	// Add second disabled policy and ensure the channel is now in the
	// disabled list.
	edge2.ChannelFlags |= lnwire.ChanUpdateDisabled
	if err := graph.UpdateEdgePolicy(edge2); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}
	disabledChanIds, err = graph.DisabledChannelIDs()
	require.NoError(t, err, "unable to get disabled channel ids")
	if len(disabledChanIds) != 1 || disabledChanIds[0] != edgeInfo.ChannelID {
		t.Fatalf("expected disabled channel with id %v, "+
			"got %v", edgeInfo.ChannelID, disabledChanIds)
	}

	// Delete the channel edge and ensure it is removed from the disabled list.
	if err = graph.DeleteChannelEdges(
		false, true, edgeInfo.ChannelID,
	); err != nil {
		t.Fatalf("unable to delete channel edge: %v", err)
	}
	disabledChanIds, err = graph.DisabledChannelIDs()
	require.NoError(t, err, "unable to get disabled channel ids")
	if len(disabledChanIds) > 0 {
		t.Fatalf("expected empty disabled channels, got %v disabled channels",
			len(disabledChanIds))
	}
}

// TestEdgePolicyMissingMaxHtcl tests that if we find a ChannelEdgePolicy in
// the DB that indicates that it should support the htlc_maximum_value_msat
// field, but it is not part of the opaque data, then we'll handle it as it is
// unknown. It also checks that we are correctly able to overwrite it when we
// receive the proper update.
func TestEdgePolicyMissingMaxHtcl(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	// We'd like to test the update of edges inserted into the database, so
	// we create two vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")
	if err := graph.AddLightningNode(node1); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test node")

	edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2)
	if err := graph.AddLightningNode(node2); err != nil {
		t.Fatalf("unable to add node: %v", err)
	}
	if err := graph.AddChannelEdge(edgeInfo); err != nil {
		t.Fatalf("unable to create channel edge: %v", err)
	}

	chanID := edgeInfo.ChannelID
	from := edge2.ToNode[:]
	to := edge1.ToNode[:]

	// We'll remove the no max_htlc field from the first edge policy, and
	// all other opaque data, and serialize it.
	edge1.MessageFlags = 0
	edge1.ExtraOpaqueData = nil

	var b bytes.Buffer
	err = serializeChanEdgePolicy(&b, edge1, to)
	if err != nil {
		t.Fatalf("unable to serialize policy")
	}

	// Set the max_htlc field. The extra bytes added to the serialization
	// will be the opaque data containing the serialized field.
	edge1.MessageFlags = lnwire.ChanUpdateRequiredMaxHtlc
	edge1.MaxHTLC = 13928598
	var b2 bytes.Buffer
	err = serializeChanEdgePolicy(&b2, edge1, to)
	if err != nil {
		t.Fatalf("unable to serialize policy")
	}

	withMaxHtlc := b2.Bytes()

	// Remove the opaque data from the serialization.
	stripped := withMaxHtlc[:len(b.Bytes())]

	// Attempting to deserialize these bytes should return an error.
	r := bytes.NewReader(stripped)
	err = kvdb.View(graph.db, func(tx kvdb.RTx) error {
		nodes := tx.ReadBucket(nodeBucket)
		if nodes == nil {
			return ErrGraphNotFound
		}

		_, err = deserializeChanEdgePolicy(r)
		if err != ErrEdgePolicyOptionalFieldNotFound {
			t.Fatalf("expected "+
				"ErrEdgePolicyOptionalFieldNotFound, got %v",
				err)
		}

		return nil
	}, func() {})
	require.NoError(t, err, "error reading db")

	// Put the stripped bytes in the DB.
	err = kvdb.Update(graph.db, func(tx kvdb.RwTx) error {
		edges := tx.ReadWriteBucket(edgeBucket)
		if edges == nil {
			return ErrEdgeNotFound
		}

		edgeIndex := edges.NestedReadWriteBucket(edgeIndexBucket)
		if edgeIndex == nil {
			return ErrEdgeNotFound
		}

		var edgeKey [33 + 8]byte
		copy(edgeKey[:], from)
		byteOrder.PutUint64(edgeKey[33:], edge1.ChannelID)

		var scratch [8]byte
		var indexKey [8 + 8]byte
		copy(indexKey[:], scratch[:])
		byteOrder.PutUint64(indexKey[8:], edge1.ChannelID)

		updateIndex, err := edges.CreateBucketIfNotExists(edgeUpdateIndexBucket)
		if err != nil {
			return err
		}

		if err := updateIndex.Put(indexKey[:], nil); err != nil {
			return err
		}

		return edges.Put(edgeKey[:], stripped)
	}, func() {})
	require.NoError(t, err, "error writing db")

	// And add the second, unmodified edge.
	if err := graph.UpdateEdgePolicy(edge2); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	// Attempt to fetch the edge and policies from the DB. Since the policy
	// we added is invalid according to the new format, it should be as we
	// are not aware of the policy (indicated by the policy returned being
	// nil)
	dbEdgeInfo, dbEdge1, dbEdge2, err := graph.FetchChannelEdgesByID(chanID)
	require.NoError(t, err, "unable to fetch channel by ID")

	// The first edge should have a nil-policy returned
	if dbEdge1 != nil {
		t.Fatalf("expected db edge to be nil")
	}
	if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)

	// Now add the original, unmodified edge policy, and make sure the edge
	// policies then become fully populated.
	if err := graph.UpdateEdgePolicy(edge1); err != nil {
		t.Fatalf("unable to update edge: %v", err)
	}

	dbEdgeInfo, dbEdge1, dbEdge2, err = graph.FetchChannelEdgesByID(chanID)
	require.NoError(t, err, "unable to fetch channel by ID")
	if err := compareEdgePolicies(dbEdge1, edge1); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	if err := compareEdgePolicies(dbEdge2, edge2); err != nil {
		t.Fatalf("edge doesn't match: %v", err)
	}
	assertEdgeInfoEqual(t, dbEdgeInfo, edgeInfo)
}

// assertNumZombies queries the provided ChannelGraph for NumZombies, and
// asserts that the returned number is equal to expZombies.
func assertNumZombies(t *testing.T, graph *ChannelGraph, expZombies uint64) {
	t.Helper()

	numZombies, err := graph.NumZombies()
	require.NoError(t, err, "unable to query number of zombies")

	if numZombies != expZombies {
		t.Fatalf("expected %d zombies, found %d",
			expZombies, numZombies)
	}
}

// TestGraphZombieIndex ensures that we can mark edges correctly as zombie/live.
func TestGraphZombieIndex(t *testing.T) {
	t.Parallel()

	// We'll start by creating our test graph along with a test edge.
	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to create test database")

	node1, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test vertex")
	node2, err := createTestVertex(graph.db)
	require.NoError(t, err, "unable to create test vertex")

	// Swap the nodes if the second's pubkey is smaller than the first.
	// Without this, the comparisons at the end will fail probabilistically.
	if bytes.Compare(node2.PubKeyBytes[:], node1.PubKeyBytes[:]) < 0 {
		node1, node2 = node2, node1
	}

	edge, _, _ := createChannelEdge(graph.db, node1, node2)
	require.NoError(t, graph.AddChannelEdge(edge))

	// Since the edge is known the graph and it isn't a zombie, IsZombieEdge
	// should not report the channel as a zombie.
	isZombie, _, _ := graph.IsZombieEdge(edge.ChannelID)
	require.False(t, isZombie)
	assertNumZombies(t, graph, 0)

	// If we delete the edge and mark it as a zombie, then we should expect
	// to see it within the index.
	err = graph.DeleteChannelEdges(false, true, edge.ChannelID)
	require.NoError(t, err, "unable to mark edge as zombie")
	isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID)
	require.True(t, isZombie)
	require.Equal(t, node1.PubKeyBytes, pubKey1)
	require.Equal(t, node2.PubKeyBytes, pubKey2)
	assertNumZombies(t, graph, 1)

	// Similarly, if we mark the same edge as live, we should no longer see
	// it within the index.
	require.NoError(t, graph.MarkEdgeLive(edge.ChannelID))

	// Attempting to mark the edge as live again now that it is no longer
	// in the zombie index should fail.
	require.ErrorIs(
		t, graph.MarkEdgeLive(edge.ChannelID), ErrZombieEdgeNotFound,
	)

	isZombie, _, _ = graph.IsZombieEdge(edge.ChannelID)
	require.False(t, isZombie)

	assertNumZombies(t, graph, 0)

	// If we mark the edge as a zombie manually, then it should show up as
	// being a zombie once again.
	err = graph.MarkEdgeZombie(
		edge.ChannelID, node1.PubKeyBytes, node2.PubKeyBytes,
	)
	require.NoError(t, err, "unable to mark edge as zombie")

	isZombie, _, _ = graph.IsZombieEdge(edge.ChannelID)
	require.True(t, isZombie)
	assertNumZombies(t, graph, 1)
}

// compareNodes is used to compare two LightningNodes while excluding the
// Features struct, which cannot be compared as the semantics for reserializing
// the featuresMap have not been defined.
func compareNodes(a, b *LightningNode) error {
	if a.LastUpdate != b.LastUpdate {
		return fmt.Errorf("node LastUpdate doesn't match: expected %v, \n"+
			"got %v", a.LastUpdate, b.LastUpdate)
	}
	if !reflect.DeepEqual(a.Addresses, b.Addresses) {
		return fmt.Errorf("Addresses doesn't match: expected %#v, \n "+
			"got %#v", a.Addresses, b.Addresses)
	}
	if !reflect.DeepEqual(a.PubKeyBytes, b.PubKeyBytes) {
		return fmt.Errorf("PubKey doesn't match: expected %#v, \n "+
			"got %#v", a.PubKeyBytes, b.PubKeyBytes)
	}
	if !reflect.DeepEqual(a.Color, b.Color) {
		return fmt.Errorf("Color doesn't match: expected %#v, \n "+
			"got %#v", a.Color, b.Color)
	}
	if !reflect.DeepEqual(a.Alias, b.Alias) {
		return fmt.Errorf("Alias doesn't match: expected %#v, \n "+
			"got %#v", a.Alias, b.Alias)
	}
	if !reflect.DeepEqual(a.HaveNodeAnnouncement, b.HaveNodeAnnouncement) {
		return fmt.Errorf("HaveNodeAnnouncement doesn't match: expected %#v, \n "+
			"got %#v", a.HaveNodeAnnouncement, b.HaveNodeAnnouncement)
	}
	if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) {
		return fmt.Errorf("extra data doesn't match: %v vs %v",
			a.ExtraOpaqueData, b.ExtraOpaqueData)
	}

	return nil
}

// compareEdgePolicies is used to compare two ChannelEdgePolices using
// compareNodes, so as to exclude comparisons of the Nodes' Features struct.
func compareEdgePolicies(a, b *models.ChannelEdgePolicy) error {
	if a.ChannelID != b.ChannelID {
		return fmt.Errorf("ChannelID doesn't match: expected %v, "+
			"got %v", a.ChannelID, b.ChannelID)
	}
	if !reflect.DeepEqual(a.LastUpdate, b.LastUpdate) {
		return fmt.Errorf("edge LastUpdate doesn't match: expected %#v, \n "+
			"got %#v", a.LastUpdate, b.LastUpdate)
	}
	if a.MessageFlags != b.MessageFlags {
		return fmt.Errorf("MessageFlags doesn't match: expected %v, "+
			"got %v", a.MessageFlags, b.MessageFlags)
	}
	if a.ChannelFlags != b.ChannelFlags {
		return fmt.Errorf("ChannelFlags doesn't match: expected %v, "+
			"got %v", a.ChannelFlags, b.ChannelFlags)
	}
	if a.TimeLockDelta != b.TimeLockDelta {
		return fmt.Errorf("TimeLockDelta doesn't match: expected %v, "+
			"got %v", a.TimeLockDelta, b.TimeLockDelta)
	}
	if a.MinHTLC != b.MinHTLC {
		return fmt.Errorf("MinHTLC doesn't match: expected %v, "+
			"got %v", a.MinHTLC, b.MinHTLC)
	}
	if a.MaxHTLC != b.MaxHTLC {
		return fmt.Errorf("MaxHTLC doesn't match: expected %v, "+
			"got %v", a.MaxHTLC, b.MaxHTLC)
	}
	if a.FeeBaseMSat != b.FeeBaseMSat {
		return fmt.Errorf("FeeBaseMSat doesn't match: expected %v, "+
			"got %v", a.FeeBaseMSat, b.FeeBaseMSat)
	}
	if a.FeeProportionalMillionths != b.FeeProportionalMillionths {
		return fmt.Errorf("FeeProportionalMillionths doesn't match: "+
			"expected %v, got %v", a.FeeProportionalMillionths,
			b.FeeProportionalMillionths)
	}
	if !bytes.Equal(a.ExtraOpaqueData, b.ExtraOpaqueData) {
		return fmt.Errorf("extra data doesn't match: %v vs %v",
			a.ExtraOpaqueData, b.ExtraOpaqueData)
	}
	if !bytes.Equal(a.ToNode[:], b.ToNode[:]) {
		return fmt.Errorf("ToNode doesn't match: expected %x, got %x",
			a.ToNode, b.ToNode)
	}

	return nil
}

// TestLightningNodeSigVerification checks that we can use the LightningNode's
// pubkey to verify signatures.
func TestLightningNodeSigVerification(t *testing.T) {
	t.Parallel()

	// Create some dummy data to sign.
	var data [32]byte
	if _, err := prand.Read(data[:]); err != nil {
		t.Fatalf("unable to read prand: %v", err)
	}

	// Create private key and sign the data with it.
	priv, err := btcec.NewPrivateKey()
	require.NoError(t, err, "unable to crete priv key")

	sign := ecdsa.Sign(priv, data[:])

	// Sanity check that the signature checks out.
	if !sign.Verify(data[:], priv.PubKey()) {
		t.Fatalf("signature doesn't check out")
	}

	// Create a LightningNode from the same private key.
	graph, err := MakeTestGraph(t)
	require.NoError(t, err, "unable to make test database")

	node, err := createLightningNode(graph.db, priv)
	require.NoError(t, err, "unable to create node")

	// And finally check that we can verify the same signature from the
	// pubkey returned from the lightning node.
	nodePub, err := node.PubKey()
	require.NoError(t, err, "unable to get pubkey")

	if !sign.Verify(data[:], nodePub) {
		t.Fatalf("unable to verify sig")
	}
}

// TestComputeFee tests fee calculation based on the outgoing amt.
func TestComputeFee(t *testing.T) {
	var (
		policy = models.ChannelEdgePolicy{
			FeeBaseMSat:               10000,
			FeeProportionalMillionths: 30000,
		}
		outgoingAmt = lnwire.MilliSatoshi(1000000)
		expectedFee = lnwire.MilliSatoshi(40000)
	)

	fee := policy.ComputeFee(outgoingAmt)
	if fee != expectedFee {
		t.Fatalf("expected fee %v, got %v", expectedFee, fee)
	}
}

// TestBatchedAddChannelEdge asserts that BatchedAddChannelEdge properly
// executes multiple AddChannelEdge requests in a single txn.
func TestBatchedAddChannelEdge(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.Nil(t, err)

	sourceNode, err := createTestVertex(graph.db)
	require.Nil(t, err)
	err = graph.SetSourceNode(sourceNode)
	require.Nil(t, err)

	// We'd like to test the insertion/deletion of edges, so we create two
	// vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.Nil(t, err)
	node2, err := createTestVertex(graph.db)
	require.Nil(t, err)

	// In addition to the fake vertexes we create some fake channel
	// identifiers.
	var spendOutputs []*wire.OutPoint
	var blockHash chainhash.Hash
	copy(blockHash[:], bytes.Repeat([]byte{1}, 32))

	// Prune the graph a few times to make sure we have entries in the
	// prune log.
	_, err = graph.PruneGraph(spendOutputs, &blockHash, 155)
	require.Nil(t, err)
	var blockHash2 chainhash.Hash
	copy(blockHash2[:], bytes.Repeat([]byte{2}, 32))

	_, err = graph.PruneGraph(spendOutputs, &blockHash2, 156)
	require.Nil(t, err)

	// We'll create 3 almost identical edges, so first create a helper
	// method containing all logic for doing so.

	// Create an edge which has its block height at 156.
	height := uint32(156)
	edgeInfo, _ := createEdge(height, 0, 0, 0, node1, node2)

	// Create an edge with block height 157. We give it
	// maximum values for tx index and position, to make
	// sure our database range scan get edges from the
	// entire range.
	edgeInfo2, _ := createEdge(
		height+1, math.MaxUint32&0x00ffffff, math.MaxUint16, 1,
		node1, node2,
	)

	// Create a third edge, this with a block height of 155.
	edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2)

	edges := []models.ChannelEdgeInfo{edgeInfo, edgeInfo2, edgeInfo3}
	errChan := make(chan error, len(edges))
	errTimeout := errors.New("timeout adding batched channel")

	// Now add all these new edges to the database.
	var wg sync.WaitGroup
	for _, edge := range edges {
		wg.Add(1)
		go func(edge models.ChannelEdgeInfo) {
			defer wg.Done()

			select {
			case errChan <- graph.AddChannelEdge(&edge):
			case <-time.After(2 * time.Second):
				errChan <- errTimeout
			}
		}(edge)
	}
	wg.Wait()

	for i := 0; i < len(edges); i++ {
		err := <-errChan
		require.Nil(t, err)
	}
}

// TestBatchedUpdateEdgePolicy asserts that BatchedUpdateEdgePolicy properly
// executes multiple UpdateEdgePolicy requests in a single txn.
func TestBatchedUpdateEdgePolicy(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.Nil(t, err)

	// We'd like to test the update of edges inserted into the database, so
	// we create two vertexes to connect.
	node1, err := createTestVertex(graph.db)
	require.Nil(t, err)
	err = graph.AddLightningNode(node1)
	require.Nil(t, err)
	node2, err := createTestVertex(graph.db)
	require.Nil(t, err)
	err = graph.AddLightningNode(node2)
	require.Nil(t, err)

	// Create an edge and add it to the db.
	edgeInfo, edge1, edge2 := createChannelEdge(graph.db, node1, node2)

	// Make sure inserting the policy at this point, before the edge info
	// is added, will fail.
	err = graph.UpdateEdgePolicy(edge1)
	require.Error(t, ErrEdgeNotFound, err)

	// Add the edge info.
	err = graph.AddChannelEdge(edgeInfo)
	require.Nil(t, err)

	errTimeout := errors.New("timeout adding batched channel")

	updates := []*models.ChannelEdgePolicy{edge1, edge2}

	errChan := make(chan error, len(updates))

	// Now add all these new edges to the database.
	var wg sync.WaitGroup
	for _, update := range updates {
		wg.Add(1)
		go func(update *models.ChannelEdgePolicy) {
			defer wg.Done()

			select {
			case errChan <- graph.UpdateEdgePolicy(update):
			case <-time.After(2 * time.Second):
				errChan <- errTimeout
			}
		}(update)
	}
	wg.Wait()

	for i := 0; i < len(updates); i++ {
		err := <-errChan
		require.Nil(t, err)
	}
}

// BenchmarkForEachChannel is a benchmark test that measures the number of
// allocations and the total memory consumed by the full graph traversal.
func BenchmarkForEachChannel(b *testing.B) {
	graph, err := MakeTestGraph(b)
	require.Nil(b, err)

	const numNodes = 100
	const numChannels = 4
	_, _ = fillTestGraph(b, graph, numNodes, numChannels)

	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		var (
			totalCapacity btcutil.Amount
			maxHTLCs      lnwire.MilliSatoshi
		)

		var nodes []GraphCacheNode
		err = graph.ForEachNodeCacheable(
			func(tx kvdb.RTx, node GraphCacheNode) error {
				nodes = append(nodes, node)

				return nil
			},
		)
		require.NoError(b, err)

		err = graph.db.View(func(tx kvdb.RTx) error {
			for _, n := range nodes {
				cb := func(tx kvdb.RTx,
					info *models.ChannelEdgeInfo,
					policy *models.ChannelEdgePolicy,
					policy2 *models.ChannelEdgePolicy) error { //nolint:lll

					// We need to do something with
					// the data here, otherwise the
					// compiler is going to optimize
					// this away, and we get bogus
					// results.
					totalCapacity += info.Capacity
					maxHTLCs += policy.MaxHTLC
					maxHTLCs += policy2.MaxHTLC

					return nil
				}

				err := n.ForEachChannel(tx, cb)
				if err != nil {
					return err
				}
			}

			return nil
		}, func() {})
		require.NoError(b, err)
	}
}

// TestGraphCacheForEachNodeChannel tests that the ForEachNodeDirectedChannel
// method works as expected, and is able to handle nil self edges.
func TestGraphCacheForEachNodeChannel(t *testing.T) {
	graph, err := MakeTestGraph(t)
	require.NoError(t, err)

	// Unset the channel graph cache to simulate the user running with the
	// option turned off.
	graph.graphCache = nil

	node1, err := createTestVertex(graph.db)
	require.Nil(t, err)
	err = graph.AddLightningNode(node1)
	require.Nil(t, err)
	node2, err := createTestVertex(graph.db)
	require.Nil(t, err)
	err = graph.AddLightningNode(node2)
	require.Nil(t, err)

	// Create an edge and add it to the db.
	edgeInfo, e1, e2 := createChannelEdge(graph.db, node1, node2)

	// Because of lexigraphical sorting and the usage of random node keys in
	// this test, we need to determine which edge belongs to node 1 at
	// runtime.
	var edge1 *models.ChannelEdgePolicy
	if e1.ToNode == node2.PubKeyBytes {
		edge1 = e1
	} else {
		edge1 = e2
	}

	// Add the channel, but only insert a single edge into the graph.
	require.NoError(t, graph.AddChannelEdge(edgeInfo))

	getSingleChannel := func() *DirectedChannel {
		var ch *DirectedChannel
		err = graph.ForEachNodeDirectedChannel(nil, node1.PubKeyBytes,
			func(c *DirectedChannel) error {
				require.Nil(t, ch)
				ch = c

				return nil
			},
		)
		require.NoError(t, err)

		return ch
	}

	// We should be able to accumulate the single channel added, even
	// though we have a nil edge policy here.
	require.NotNil(t, getSingleChannel())

	// Set an inbound fee and check that it is properly returned.
	edge1.ExtraOpaqueData = []byte{
		253, 217, 3, 8, 0, 0, 0, 10, 0, 0, 0, 20,
	}
	require.NoError(t, graph.UpdateEdgePolicy(edge1))

	directedChan := getSingleChannel()
	require.NotNil(t, directedChan)
	require.Equal(t, directedChan.InboundFee, lnwire.Fee{
		BaseFee: 10,
		FeeRate: 20,
	})

	// Set an invalid inbound fee and check that the edge is no longer
	// returned.
	edge1.ExtraOpaqueData = []byte{
		253, 217, 3, 8, 0,
	}
	require.NoError(t, graph.UpdateEdgePolicy(edge1))

	require.Nil(t, getSingleChannel())
}

// TestGraphLoading asserts that the cache is properly reconstructed after a
// restart.
func TestGraphLoading(t *testing.T) {
	// First, create a temporary directory to be used for the duration of
	// this test.
	tempDirName := t.TempDir()

	// Next, create the graph for the first time.
	backend, backendCleanup, err := kvdb.GetTestBackend(tempDirName, "cgr")
	require.NoError(t, err)
	defer backend.Close()
	defer backendCleanup()

	opts := DefaultOptions()
	graph, err := NewChannelGraph(
		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
		true, false,
	)
	require.NoError(t, err)

	// Populate the graph with test data.
	const numNodes = 100
	const numChannels = 4
	_, _ = fillTestGraph(t, graph, numNodes, numChannels)

	// Recreate the graph. This should cause the graph cache to be
	// populated.
	graphReloaded, err := NewChannelGraph(
		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
		true, false,
	)
	require.NoError(t, err)

	// Assert that the cache content is identical.
	require.Equal(
		t, graph.graphCache.nodeChannels,
		graphReloaded.graphCache.nodeChannels,
	)

	require.Equal(
		t, graph.graphCache.nodeFeatures,
		graphReloaded.graphCache.nodeFeatures,
	)
}

// TestClosedScid tests that we can correctly insert a SCID into the index of
// closed short channel ids.
func TestClosedScid(t *testing.T) {
	t.Parallel()

	graph, err := MakeTestGraph(t)
	require.Nil(t, err)

	scid := lnwire.ShortChannelID{}

	// The scid should not exist in the closedScidBucket.
	exists, err := graph.IsClosedScid(scid)
	require.Nil(t, err)
	require.False(t, exists)

	// After we call PutClosedScid, the call to IsClosedScid should return
	// true.
	err = graph.PutClosedScid(scid)
	require.Nil(t, err)

	exists, err = graph.IsClosedScid(scid)
	require.Nil(t, err)
	require.True(t, exists)
}
