package itest

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd/chanbackup"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/lntest/wait"
	"github.com/stretchr/testify/require"
)

// testChannelBackupRestore tests that we're able to recover from, and initiate
// the DLP protocol via: the RPC restore command, restoring on unlock, and
// restoring from initial wallet creation. We'll also alternate between
// restoring form the on disk file, and restoring from the exported RPC command
// as well.
func testChannelBackupRestore(net *lntest.NetworkHarness, t *harnessTest) {
	password := []byte("El Psy Kongroo")

	ctxb := context.Background()

	var testCases = []chanRestoreTestCase{
		// Restore from backups obtained via the RPC interface. Dave
		// was the initiator, of the non-advertised channel.
		{
			name:            "restore from RPC backup",
			channelsUpdated: false,
			initiator:       true,
			private:         false,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// For this restoration method, we'll grab the
				// current multi-channel backup from the old
				// node, and use it to restore a new node
				// within the closure.
				req := &lnrpc.ChanBackupExportRequest{}
				chanBackup, err := oldNode.ExportAllChannelBackups(
					ctxb, req,
				)
				if err != nil {
					return nil, fmt.Errorf("unable to obtain "+
						"channel backup: %v", err)
				}

				multi := chanBackup.MultiChanBackup.MultiChanBackup

				// In our nodeRestorer function, we'll restore
				// the node from seed, then manually recover
				// the channel backup.
				return chanRestoreViaRPC(
					net, password, mnemonic, multi,
				)
			},
		},

		// Restore the backup from the on-disk file, using the RPC
		// interface.
		{
			name:      "restore from backup file",
			initiator: true,
			private:   false,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// Read the entire Multi backup stored within
				// this node's channels.backup file.
				multi, err := ioutil.ReadFile(backupFilePath)
				if err != nil {
					return nil, err
				}

				// Now that we have Dave's backup file, we'll
				// create a new nodeRestorer that will restore
				// using the on-disk channels.backup.
				return chanRestoreViaRPC(
					net, password, mnemonic, multi,
				)
			},
		},

		// Restore the backup as part of node initialization with the
		// prior mnemonic and new backup seed.
		{
			name:      "restore during creation",
			initiator: true,
			private:   false,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// First, fetch the current backup state as is,
				// to obtain our latest Multi.
				chanBackup, err := oldNode.ExportAllChannelBackups(
					ctxb, &lnrpc.ChanBackupExportRequest{},
				)
				if err != nil {
					return nil, fmt.Errorf("unable to obtain "+
						"channel backup: %v", err)
				}
				backupSnapshot := &lnrpc.ChanBackupSnapshot{
					MultiChanBackup: chanBackup.MultiChanBackup,
				}

				// Create a new nodeRestorer that will restore
				// the node using the Multi backup we just
				// obtained above.
				return func() (*lntest.HarnessNode, error) {
					return net.RestoreNodeWithSeed(
						"dave", nil, password,
						mnemonic, 1000, backupSnapshot,
					)
				}, nil
			},
		},

		// Restore the backup once the node has already been
		// re-created, using the Unlock call.
		{
			name:      "restore during unlock",
			initiator: true,
			private:   false,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// First, fetch the current backup state as is,
				// to obtain our latest Multi.
				chanBackup, err := oldNode.ExportAllChannelBackups(
					ctxb, &lnrpc.ChanBackupExportRequest{},
				)
				if err != nil {
					return nil, fmt.Errorf("unable to obtain "+
						"channel backup: %v", err)
				}
				backupSnapshot := &lnrpc.ChanBackupSnapshot{
					MultiChanBackup: chanBackup.MultiChanBackup,
				}

				// Create a new nodeRestorer that will restore
				// the node with its seed, but no channel
				// backup, shutdown this initialized node, then
				// restart it again using Unlock.
				return func() (*lntest.HarnessNode, error) {
					newNode, err := net.RestoreNodeWithSeed(
						"dave", nil, password,
						mnemonic, 1000, nil,
					)
					if err != nil {
						return nil, err
					}

					err = net.RestartNode(
						newNode, nil, backupSnapshot,
					)
					if err != nil {
						return nil, err
					}

					return newNode, nil
				}, nil
			},
		},

		// Restore the backup from the on-disk file a second time to
		// make sure imports can be canceled and later resumed.
		{
			name:      "restore from backup file twice",
			initiator: true,
			private:   false,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// Read the entire Multi backup stored within
				// this node's channels.backup file.
				multi, err := ioutil.ReadFile(backupFilePath)
				if err != nil {
					return nil, err
				}

				// Now that we have Dave's backup file, we'll
				// create a new nodeRestorer that will restore
				// using the on-disk channels.backup.
				backup := &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
					MultiChanBackup: multi,
				}

				ctxb := context.Background()

				return func() (*lntest.HarnessNode, error) {
					newNode, err := net.RestoreNodeWithSeed(
						"dave", nil, password, mnemonic,
						1000, nil,
					)
					if err != nil {
						return nil, fmt.Errorf("unable to "+
							"restore node: %v", err)
					}

					_, err = newNode.RestoreChannelBackups(
						ctxb,
						&lnrpc.RestoreChanBackupRequest{
							Backup: backup,
						},
					)
					if err != nil {
						return nil, fmt.Errorf("unable "+
							"to restore backups: %v",
							err)
					}

					_, err = newNode.RestoreChannelBackups(
						ctxb,
						&lnrpc.RestoreChanBackupRequest{
							Backup: backup,
						},
					)
					if err != nil {
						return nil, fmt.Errorf("unable "+
							"to restore backups the"+
							"second time: %v",
							err)
					}

					return newNode, nil
				}, nil
			},
		},

		// Use the channel backup file that contains an unconfirmed
		// channel and make sure recovery works as well.
		{
			name:            "restore unconfirmed channel file",
			channelsUpdated: false,
			initiator:       true,
			private:         false,
			unconfirmed:     true,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// Read the entire Multi backup stored within
				// this node's channels.backup file.
				multi, err := ioutil.ReadFile(backupFilePath)
				if err != nil {
					return nil, err
				}

				// Let's assume time passes, the channel
				// confirms in the meantime but for some reason
				// the backup we made while it was still
				// unconfirmed is the only backup we have. We
				// should still be able to restore it. To
				// simulate time passing, we mine some blocks
				// to get the channel confirmed _after_ we saved
				// the backup.
				mineBlocks(t, net, 6, 1)

				// In our nodeRestorer function, we'll restore
				// the node from seed, then manually recover
				// the channel backup.
				return chanRestoreViaRPC(
					net, password, mnemonic, multi,
				)
			},
		},

		// Create a backup using RPC that contains an unconfirmed
		// channel and make sure recovery works as well.
		{
			name:            "restore unconfirmed channel RPC",
			channelsUpdated: false,
			initiator:       true,
			private:         false,
			unconfirmed:     true,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// For this restoration method, we'll grab the
				// current multi-channel backup from the old
				// node. The channel should be included, even if
				// it is not confirmed yet.
				req := &lnrpc.ChanBackupExportRequest{}
				chanBackup, err := oldNode.ExportAllChannelBackups(
					ctxb, req,
				)
				if err != nil {
					return nil, fmt.Errorf("unable to obtain "+
						"channel backup: %v", err)
				}
				chanPoints := chanBackup.MultiChanBackup.ChanPoints
				if len(chanPoints) == 0 {
					return nil, fmt.Errorf("unconfirmed " +
						"channel not included in backup")
				}

				// Let's assume time passes, the channel
				// confirms in the meantime but for some reason
				// the backup we made while it was still
				// unconfirmed is the only backup we have. We
				// should still be able to restore it. To
				// simulate time passing, we mine some blocks
				// to get the channel confirmed _after_ we saved
				// the backup.
				mineBlocks(t, net, 6, 1)

				// In our nodeRestorer function, we'll restore
				// the node from seed, then manually recover
				// the channel backup.
				multi := chanBackup.MultiChanBackup.MultiChanBackup
				return chanRestoreViaRPC(
					net, password, mnemonic, multi,
				)
			},
		},

		// Restore the backup from the on-disk file, using the RPC
		// interface, for anchor commitment channels.
		{
			name:         "restore from backup file anchors",
			initiator:    true,
			private:      false,
			anchorCommit: true,
			restoreMethod: func(oldNode *lntest.HarnessNode,
				backupFilePath string,
				mnemonic []string) (nodeRestorer, error) {

				// Read the entire Multi backup stored within
				// this node's channels.backup file.
				multi, err := ioutil.ReadFile(backupFilePath)
				if err != nil {
					return nil, err
				}

				// Now that we have Dave's backup file, we'll
				// create a new nodeRestorer that will restore
				// using the on-disk channels.backup.
				return chanRestoreViaRPC(
					net, password, mnemonic, multi,
				)
			},
		},
	}

	// TODO(roasbeef): online vs offline close?

	// TODO(roasbeef): need to re-trigger the on-disk file once the node
	// ann is updated?

	for _, testCase := range testCases {
		testCase := testCase
		success := t.t.Run(testCase.name, func(t *testing.T) {
			h := newHarnessTest(t, net)

			// Start each test with the default static fee estimate.
			net.SetFeeEstimate(12500)

			testChanRestoreScenario(h, net, &testCase, password)
		})
		if !success {
			break
		}
	}
}

// testChannelBackupUpdates tests that both the streaming channel update RPC,
// and the on-disk channels.backup are updated each time a channel is
// opened/closed.
func testChannelBackupUpdates(net *lntest.NetworkHarness, t *harnessTest) {
	ctxb := context.Background()

	// First, we'll make a temp directory that we'll use to store our
	// backup file, so we can check in on it during the test easily.
	backupDir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatalf("unable to create backup dir: %v", err)
	}
	defer os.RemoveAll(backupDir)

	// First, we'll create a new node, Carol. We'll also create a temporary
	// file that Carol will use to store her channel backups.
	backupFilePath := filepath.Join(
		backupDir, chanbackup.DefaultBackupFileName,
	)
	carolArgs := fmt.Sprintf("--backupfilepath=%v", backupFilePath)
	carol, err := net.NewNode("carol", []string{carolArgs})
	if err != nil {
		t.Fatalf("unable to create new node: %v", err)
	}
	defer shutdownAndAssert(net, t, carol)

	// Next, we'll register for streaming notifications for changes to the
	// backup file.
	backupStream, err := carol.SubscribeChannelBackups(
		ctxb, &lnrpc.ChannelBackupSubscription{},
	)
	if err != nil {
		t.Fatalf("unable to create backup stream: %v", err)
	}

	// We'll use this goroutine to proxy any updates to a channel we can
	// easily use below.
	var wg sync.WaitGroup
	backupUpdates := make(chan *lnrpc.ChanBackupSnapshot)
	streamErr := make(chan error)
	streamQuit := make(chan struct{})

	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			snapshot, err := backupStream.Recv()
			if err != nil {
				select {
				case streamErr <- err:
				case <-streamQuit:
					return
				}
			}

			select {
			case backupUpdates <- snapshot:
			case <-streamQuit:
				return
			}
		}
	}()
	defer close(streamQuit)

	// With Carol up, we'll now connect her to Alice, and open a channel
	// between them.
	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
	if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil {
		t.Fatalf("unable to connect carol to alice: %v", err)
	}

	// Next, we'll open two channels between Alice and Carol back to back.
	var chanPoints []*lnrpc.ChannelPoint
	numChans := 2
	chanAmt := btcutil.Amount(1000000)
	for i := 0; i < numChans; i++ {
		ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
		chanPoint := openChannelAndAssert(
			ctxt, t, net, net.Alice, carol,
			lntest.OpenChannelParams{
				Amt: chanAmt,
			},
		)

		chanPoints = append(chanPoints, chanPoint)
	}

	// Using this helper function, we'll maintain a pointer to the latest
	// channel backup so we can compare it to the on disk state.
	var currentBackup *lnrpc.ChanBackupSnapshot
	assertBackupNtfns := func(numNtfns int) {
		for i := 0; i < numNtfns; i++ {
			select {
			case err := <-streamErr:
				t.Fatalf("error with backup stream: %v", err)

			case currentBackup = <-backupUpdates:

			case <-time.After(time.Second * 5):
				t.Fatalf("didn't receive channel backup "+
					"notification %v", i+1)
			}
		}
	}

	// assertBackupFileState is a helper function that we'll use to compare
	// the on disk back up file to our currentBackup pointer above.
	assertBackupFileState := func() {
		err := wait.NoError(func() error {
			packedBackup, err := ioutil.ReadFile(backupFilePath)
			if err != nil {
				return fmt.Errorf("unable to read backup "+
					"file: %v", err)
			}

			// As each back up file will be encrypted with a fresh
			// nonce, we can't compare them directly, so instead
			// we'll compare the length which is a proxy for the
			// number of channels that the multi-backup contains.
			rawBackup := currentBackup.MultiChanBackup.MultiChanBackup
			if len(rawBackup) != len(packedBackup) {
				return fmt.Errorf("backup files don't match: "+
					"expected %x got %x", rawBackup, packedBackup)
			}

			// Additionally, we'll assert that both backups up
			// returned are valid.
			for i, backup := range [][]byte{rawBackup, packedBackup} {
				snapshot := &lnrpc.ChanBackupSnapshot{
					MultiChanBackup: &lnrpc.MultiChanBackup{
						MultiChanBackup: backup,
					},
				}
				_, err := carol.VerifyChanBackup(ctxb, snapshot)
				if err != nil {
					return fmt.Errorf("unable to verify "+
						"backup #%d: %v", i, err)
				}
			}

			return nil
		}, defaultTimeout)
		if err != nil {
			t.Fatalf("backup state invalid: %v", err)
		}
	}

	// As these two channels were just opened, we should've got two times
	// the pending and open notifications for channel backups.
	assertBackupNtfns(2 * 2)

	// The on disk file should also exactly match the latest backup that we
	// have.
	assertBackupFileState()

	// Next, we'll close the channels one by one. After each channel
	// closure, we should get a notification, and the on-disk state should
	// match this state as well.
	for i := 0; i < numChans; i++ {
		// To ensure force closes also trigger an update, we'll force
		// close half of the channels.
		forceClose := i%2 == 0

		chanPoint := chanPoints[i]

		ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
		closeChannelAndAssert(
			ctxt, t, net, net.Alice, chanPoint, forceClose,
		)

		// We should get a single notification after closing, and the
		// on-disk state should match this latest notifications.
		assertBackupNtfns(1)
		assertBackupFileState()

		// If we force closed the channel, then we'll mine enough
		// blocks to ensure all outputs have been swept.
		if forceClose {
			cleanupForceClose(t, net, net.Alice, chanPoint)
		}
	}
}

// testExportChannelBackup tests that we're able to properly export either a
// targeted channel's backup, or export backups of all the currents open
// channels.
func testExportChannelBackup(net *lntest.NetworkHarness, t *harnessTest) {
	ctxb := context.Background()

	// First, we'll create our primary test node: Carol. We'll use Carol to
	// open channels and also export backups that we'll examine throughout
	// the test.
	carol, err := net.NewNode("carol", nil)
	if err != nil {
		t.Fatalf("unable to create new node: %v", err)
	}
	defer shutdownAndAssert(net, t, carol)

	// With Carol up, we'll now connect her to Alice, and open a channel
	// between them.
	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
	if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil {
		t.Fatalf("unable to connect carol to alice: %v", err)
	}

	// Next, we'll open two channels between Alice and Carol back to back.
	var chanPoints []*lnrpc.ChannelPoint
	numChans := 2
	chanAmt := btcutil.Amount(1000000)
	for i := 0; i < numChans; i++ {
		ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
		chanPoint := openChannelAndAssert(
			ctxt, t, net, net.Alice, carol,
			lntest.OpenChannelParams{
				Amt: chanAmt,
			},
		)

		chanPoints = append(chanPoints, chanPoint)
	}

	// Now that the channels are open, we should be able to fetch the
	// backups of each of the channels.
	for _, chanPoint := range chanPoints {
		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
		req := &lnrpc.ExportChannelBackupRequest{
			ChanPoint: chanPoint,
		}
		chanBackup, err := carol.ExportChannelBackup(ctxt, req)
		if err != nil {
			t.Fatalf("unable to fetch backup for channel %v: %v",
				chanPoint, err)
		}

		// The returned backup should be full populated. Since it's
		// encrypted, we can't assert any more than that atm.
		if len(chanBackup.ChanBackup) == 0 {
			t.Fatalf("obtained empty backup for channel: %v", chanPoint)
		}

		// The specified chanPoint in the response should match our
		// requested chanPoint.
		if chanBackup.ChanPoint.String() != chanPoint.String() {
			t.Fatalf("chanPoint mismatched: expected %v, got %v",
				chanPoint.String(),
				chanBackup.ChanPoint.String())
		}
	}

	// Before we proceed, we'll make two utility methods we'll use below
	// for our primary assertions.
	assertNumSingleBackups := func(numSingles int) {
		err := wait.NoError(func() error {
			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
			req := &lnrpc.ChanBackupExportRequest{}
			chanSnapshot, err := carol.ExportAllChannelBackups(
				ctxt, req,
			)
			if err != nil {
				return fmt.Errorf("unable to export channel "+
					"backup: %v", err)
			}

			if chanSnapshot.SingleChanBackups == nil {
				return fmt.Errorf("single chan backups not " +
					"populated")
			}

			backups := chanSnapshot.SingleChanBackups.ChanBackups
			if len(backups) != numSingles {
				return fmt.Errorf("expected %v singles, "+
					"got %v", len(backups), numSingles)
			}

			return nil
		}, defaultTimeout)
		if err != nil {
			t.Fatalf(err.Error())
		}
	}
	assertMultiBackupFound := func() func(bool, map[wire.OutPoint]struct{}) {
		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
		req := &lnrpc.ChanBackupExportRequest{}
		chanSnapshot, err := carol.ExportAllChannelBackups(ctxt, req)
		if err != nil {
			t.Fatalf("unable to export channel backup: %v", err)
		}

		return func(found bool, chanPoints map[wire.OutPoint]struct{}) {
			switch {
			case found && chanSnapshot.MultiChanBackup == nil:
				t.Fatalf("multi-backup not present")

			case !found && chanSnapshot.MultiChanBackup != nil &&
				(len(chanSnapshot.MultiChanBackup.MultiChanBackup) !=
					chanbackup.NilMultiSizePacked):

				t.Fatalf("found multi-backup when non should " +
					"be found")
			}

			if !found {
				return
			}

			backedUpChans := chanSnapshot.MultiChanBackup.ChanPoints
			if len(chanPoints) != len(backedUpChans) {
				t.Fatalf("expected %v chans got %v", len(chanPoints),
					len(backedUpChans))
			}

			for _, chanPoint := range backedUpChans {
				wirePoint := rpcPointToWirePoint(t, chanPoint)
				if _, ok := chanPoints[wirePoint]; !ok {
					t.Fatalf("unexpected backup: %v", wirePoint)
				}
			}
		}
	}

	chans := make(map[wire.OutPoint]struct{})
	for _, chanPoint := range chanPoints {
		chans[rpcPointToWirePoint(t, chanPoint)] = struct{}{}
	}

	// We should have exactly two single channel backups contained, and we
	// should also have a multi-channel backup.
	assertNumSingleBackups(2)
	assertMultiBackupFound()(true, chans)

	// We'll now close each channel on by one. After we close a channel, we
	// shouldn't be able to find that channel as a backup still. We should
	// also have one less single written to disk.
	for i, chanPoint := range chanPoints {
		ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
		closeChannelAndAssert(
			ctxt, t, net, net.Alice, chanPoint, false,
		)

		assertNumSingleBackups(len(chanPoints) - i - 1)

		delete(chans, rpcPointToWirePoint(t, chanPoint))
		assertMultiBackupFound()(true, chans)
	}

	// At this point we shouldn't have any single or multi-chan backups at
	// all.
	assertNumSingleBackups(0)
	assertMultiBackupFound()(false, nil)
}

// nodeRestorer is a function closure that allows each chanRestoreTestCase to
// control exactly *how* the prior node is restored. This might be using an
// backup obtained over RPC, or the file system, etc.
type nodeRestorer func() (*lntest.HarnessNode, error)

// chanRestoreTestCase describes a test case for an end to end SCB restoration
// work flow. One node will start from scratch using an existing SCB. At the
// end of the est, both nodes should be made whole via the DLP protocol.
type chanRestoreTestCase struct {
	// name is the name of the target test case.
	name string

	// channelsUpdated is false then this means that no updates
	// have taken place within the channel before restore.
	// Otherwise, HTLCs will be settled between the two parties
	// before restoration modifying the balance beyond the initial
	// allocation.
	channelsUpdated bool

	// initiator signals if Dave should be the one that opens the
	// channel to Alice, or if it should be the other way around.
	initiator bool

	// private signals if the channel from Dave to Carol should be
	// private or not.
	private bool

	// unconfirmed signals if the channel from Dave to Carol should be
	// confirmed or not.
	unconfirmed bool

	// anchorCommit is true, then the new anchor commitment type will be
	// used for the channels created in the test.
	anchorCommit bool

	// restoreMethod takes an old node, then returns a function
	// closure that'll return the same node, but with its state
	// restored via a custom method. We use this to abstract away
	// _how_ a node is restored from our assertions once the node
	// has been fully restored itself.
	restoreMethod func(oldNode *lntest.HarnessNode,
		backupFilePath string,
		mnemonic []string) (nodeRestorer, error)
}

// testChanRestoreScenario executes a chanRestoreTestCase from end to end,
// ensuring that after Dave restores his channel state according to the
// testCase, the DLP protocol is executed properly and both nodes are made
// whole.
func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
	testCase *chanRestoreTestCase, password []byte) {

	const (
		chanAmt = btcutil.Amount(10000000)
		pushAmt = btcutil.Amount(5000000)
	)

	ctxb := context.Background()

	var nodeArgs []string
	if testCase.anchorCommit {
		nodeArgs = commitTypeAnchors.Args()
	}

	// First, we'll create a brand new node we'll use within the test. If
	// we have a custom backup file specified, then we'll also create that
	// for use.
	dave, mnemonic, _, err := net.NewNodeWithSeed(
		"dave", nodeArgs, password, false,
	)
	if err != nil {
		t.Fatalf("unable to create new node: %v", err)
	}
	// Defer to a closure instead of to shutdownAndAssert due to the value
	// of 'dave' changing throughout the test.
	defer func() {
		shutdownAndAssert(net, t, dave)
	}()
	carol, err := net.NewNode("carol", nodeArgs)
	if err != nil {
		t.Fatalf("unable to make new node: %v", err)
	}
	defer shutdownAndAssert(net, t, carol)

	// Now that our new nodes are created, we'll give them some coins for
	// channel opening and anchor sweeping.
	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
	err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
	if err != nil {
		t.Fatalf("unable to send coins to dave: %v", err)
	}
	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
	err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave)
	if err != nil {
		t.Fatalf("unable to send coins to dave: %v", err)
	}

	var from, to *lntest.HarnessNode
	if testCase.initiator {
		from, to = dave, carol
	} else {
		from, to = carol, dave
	}

	// Next, we'll connect Dave to Carol, and open a new channel to her
	// with a portion pushed.
	if err := net.ConnectNodes(ctxt, dave, carol); err != nil {
		t.Fatalf("unable to connect dave to carol: %v", err)
	}

	// We will either open a confirmed or unconfirmed channel, depending on
	// the requirements of the test case.
	switch {
	case testCase.unconfirmed:
		ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
		_, err := net.OpenPendingChannel(
			ctxt, from, to, chanAmt, pushAmt,
		)
		if err != nil {
			t.Fatalf("couldn't open pending channel: %v", err)
		}

		// Give the pubsub some time to update the channel backup.
		err = wait.NoError(func() error {
			fi, err := os.Stat(dave.ChanBackupPath())
			if err != nil {
				return err
			}
			if fi.Size() <= chanbackup.NilMultiSizePacked {
				return fmt.Errorf("backup file empty")
			}
			return nil
		}, defaultTimeout)
		if err != nil {
			t.Fatalf("channel backup not updated in time: %v", err)
		}

	default:
		ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
		chanPoint := openChannelAndAssert(
			ctxt, t, net, from, to,
			lntest.OpenChannelParams{
				Amt:     chanAmt,
				PushAmt: pushAmt,
				Private: testCase.private,
			},
		)

		// Wait for both sides to see the opened channel.
		ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
		err = dave.WaitForNetworkChannelOpen(ctxt, chanPoint)
		if err != nil {
			t.Fatalf("dave didn't report channel: %v", err)
		}
		err = carol.WaitForNetworkChannelOpen(ctxt, chanPoint)
		if err != nil {
			t.Fatalf("carol didn't report channel: %v", err)
		}
	}

	// If both parties should start with existing channel updates, then
	// we'll send+settle an HTLC between 'from' and 'to' now.
	if testCase.channelsUpdated {
		invoice := &lnrpc.Invoice{
			Memo:  "testing",
			Value: 100000,
		}
		invoiceResp, err := to.AddInvoice(ctxt, invoice)
		if err != nil {
			t.Fatalf("unable to add invoice: %v", err)
		}

		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
		err = completePaymentRequests(
			ctxt, from, from.RouterClient,
			[]string{invoiceResp.PaymentRequest}, true,
		)
		if err != nil {
			t.Fatalf("unable to complete payments: %v", err)
		}
	}

	// Before we start the recovery, we'll record the balances of both
	// Carol and Dave to ensure they both sweep their coins at the end.
	balReq := &lnrpc.WalletBalanceRequest{}
	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
	carolBalResp, err := carol.WalletBalance(ctxt, balReq)
	if err != nil {
		t.Fatalf("unable to get carol's balance: %v", err)
	}
	carolStartingBalance := carolBalResp.ConfirmedBalance

	daveBalance, err := dave.WalletBalance(ctxt, balReq)
	if err != nil {
		t.Fatalf("unable to get carol's balance: %v", err)
	}
	daveStartingBalance := daveBalance.ConfirmedBalance

	// At this point, we'll now execute the restore method to give us the
	// new node we should attempt our assertions against.
	backupFilePath := dave.ChanBackupPath()
	restoredNodeFunc, err := testCase.restoreMethod(
		dave, backupFilePath, mnemonic,
	)
	if err != nil {
		t.Fatalf("unable to prep node restoration: %v", err)
	}

	// Now that we're able to make our restored now, we'll shutdown the old
	// Dave node as we'll be storing it shortly below.
	shutdownAndAssert(net, t, dave)

	// To make sure the channel state is advanced correctly if the channel
	// peer is not online at first, we also shutdown Carol.
	restartCarol, err := net.SuspendNode(carol)
	require.NoError(t.t, err)

	// Next, we'll make a new Dave and start the bulk of our recovery
	// workflow.
	dave, err = restoredNodeFunc()
	if err != nil {
		t.Fatalf("unable to restore node: %v", err)
	}

	// First ensure that the on-chain balance is restored.
	err = wait.NoError(func() error {
		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
		balReq := &lnrpc.WalletBalanceRequest{}
		daveBalResp, err := dave.WalletBalance(ctxt, balReq)
		if err != nil {
			return err
		}

		daveBal := daveBalResp.ConfirmedBalance
		if daveBal <= 0 {
			return fmt.Errorf("expected positive balance, had %v",
				daveBal)
		}

		return nil
	}, defaultTimeout)
	if err != nil {
		t.Fatalf("On-chain balance not restored: %v", err)
	}

	// We now check that the restored channel is in the proper state. It
	// should not yet be force closing as no connection with the remote
	// peer was established yet. We should also not be able to close the
	// channel.
	assertNumPendingChannels(t, dave, 1, 0)
	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
	defer cancel()
	pendingChanResp, err := dave.PendingChannels(
		ctxt, &lnrpc.PendingChannelsRequest{},
	)
	require.NoError(t.t, err)

	// We now need to make sure the server is fully started before we can
	// actually close the channel. This is the first check in CloseChannel
	// so we can try with a nil channel point until we get the correct error
	// to find out if Dave is fully started.
	err = wait.Predicate(func() bool {
		const expectedErr = "must specify channel point"
		ctxc, cancel := context.WithCancel(ctxt)
		defer cancel()

		resp, err := dave.CloseChannel(
			ctxc, &lnrpc.CloseChannelRequest{},
		)
		if err != nil {
			return false
		}

		defer func() { _ = resp.CloseSend() }()

		_, err = resp.Recv()
		if err != nil && strings.Contains(err.Error(), expectedErr) {
			return true
		}

		return false
	}, defaultTimeout)
	require.NoError(t.t, err)

	// We also want to make sure we cannot force close in this state. That
	// would get the state machine in a weird state.
	chanPointParts := strings.Split(
		pendingChanResp.WaitingCloseChannels[0].Channel.ChannelPoint,
		":",
	)
	chanPointIndex, _ := strconv.ParseUint(chanPointParts[1], 10, 32)
	resp, err := dave.CloseChannel(ctxt, &lnrpc.CloseChannelRequest{
		ChannelPoint: &lnrpc.ChannelPoint{
			FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
				FundingTxidStr: chanPointParts[0],
			},
			OutputIndex: uint32(chanPointIndex),
		},
		Force: true,
	})

	// We don't get an error directly but only when reading the first
	// message of the stream.
	require.NoError(t.t, err)
	_, err = resp.Recv()
	require.Error(t.t, err)
	require.Contains(t.t, err.Error(), "cannot close channel with state: ")
	require.Contains(t.t, err.Error(), "ChanStatusRestored")

	// Increase the fee estimate so that the following force close tx will
	// be cpfp'ed in case of anchor commitments.
	net.SetFeeEstimate(30000)

	// Now that we have ensured that the channels restored by the backup are
	// in the correct state even without the remote peer telling us so,
	// let's start up Carol again.
	err = restartCarol()
	require.NoError(t.t, err)

	// Now that we have our new node up, we expect that it'll re-connect to
	// Carol automatically based on the restored backup.
	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
	err = net.EnsureConnected(ctxt, dave, carol)
	if err != nil {
		t.Fatalf("node didn't connect after recovery: %v", err)
	}

	// TODO(roasbeef): move dave restarts?

	// Now we'll assert that both sides properly execute the DLP protocol.
	// We grab their balances now to ensure that they're made whole at the
	// end of the protocol.
	assertDLPExecuted(
		net, t, carol, carolStartingBalance, dave, daveStartingBalance,
		testCase.anchorCommit,
	)
}

// chanRestoreViaRPC is a helper test method that returns a nodeRestorer
// instance which will restore the target node from a password+seed, then
// trigger a SCB restore using the RPC interface.
func chanRestoreViaRPC(net *lntest.NetworkHarness,
	password []byte, mnemonic []string,
	multi []byte) (nodeRestorer, error) {

	backup := &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
		MultiChanBackup: multi,
	}

	ctxb := context.Background()

	return func() (*lntest.HarnessNode, error) {
		newNode, err := net.RestoreNodeWithSeed(
			"dave", nil, password, mnemonic, 1000, nil,
		)
		if err != nil {
			return nil, fmt.Errorf("unable to "+
				"restore node: %v", err)
		}

		_, err = newNode.RestoreChannelBackups(
			ctxb, &lnrpc.RestoreChanBackupRequest{
				Backup: backup,
			},
		)
		if err != nil {
			return nil, fmt.Errorf("unable "+
				"to restore backups: %v", err)
		}

		return newNode, nil
	}, nil
}