package lnwallet_test

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/boltdb/bolt"
	"github.com/davecgh/go-spew/spew"

	"github.com/roasbeef/btcwallet/chain"

	"github.com/lightningnetwork/lnd/chainntnfs"
	"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcd/chaincfg/chainhash"
	"github.com/roasbeef/btcd/rpcclient"
	_ "github.com/roasbeef/btcwallet/walletdb/bdb"

	"github.com/roasbeef/btcd/btcec"
	"github.com/roasbeef/btcd/integration/rpctest"
	"github.com/roasbeef/btcd/txscript"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"
)

var (
	privPass = []byte("private-test")

	// For simplicity a single priv key controls all of our test outputs.
	testWalletPrivKey = []byte{
		0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf,
		0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9,
		0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f,
		0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90,
	}

	bobsPrivKey = []byte{
		0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
		0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
		0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
		0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
	}

	// Use a hard-coded HD seed.
	testHdSeed = chainhash.Hash{
		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
		0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
		0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
		0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
	}

	aliceHDSeed = chainhash.Hash{
		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
		0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
		0x4f, 0x2f, 0x6f, 0x25, 0x18, 0xa3, 0xef, 0xb9,
		0x64, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
	}
	bobHDSeed = chainhash.Hash{
		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
		0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
		0x4f, 0x2f, 0x6f, 0x25, 0x98, 0xa3, 0xef, 0xb9,
		0x69, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
	}

	netParams = &chaincfg.SimNetParams
	chainHash = netParams.GenesisHash

	_, alicePub = btcec.PrivKeyFromBytes(btcec.S256(), testHdSeed[:])
	_, bobPub   = btcec.PrivKeyFromBytes(btcec.S256(), bobsPrivKey)

	// The number of confirmations required to consider any created channel
	// open.
	numReqConfs uint16 = 1

	csvDelay uint16 = 4

	bobAddr, _   = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
	aliceAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.3:9000")
)

// assertProperBalance asserts than the total value of the unspent outputs
// within the wallet are *exactly* amount. If unable to retrieve the current
// balance, or the assertion fails, the test will halt with a fatal error.
func assertProperBalance(t *testing.T, lw *lnwallet.LightningWallet, numConfirms int32, amount int64) {
	balance, err := lw.ConfirmedBalance(numConfirms, false)
	if err != nil {
		t.Fatalf("unable to query for balance: %v", err)
	}
	if balance != btcutil.Amount(amount*1e8) {
		t.Fatalf("wallet credits not properly loaded, should have 40BTC, "+
			"instead have %v", balance)
	}
}

func assertChannelOpen(t *testing.T, miner *rpctest.Harness, numConfs uint32,
	c <-chan *lnwallet.LightningChannel) *lnwallet.LightningChannel {
	// Mine a single block. After this block is mined, the channel should
	// be considered fully open.
	if _, err := miner.Node.Generate(1); err != nil {
		t.Fatalf("unable to generate block: %v", err)
	}
	select {
	case lnc := <-c:
		return lnc
	case <-time.After(time.Second * 5):
		t.Fatalf("channel never opened")
		return nil
	}
}

func assertReservationDeleted(res *lnwallet.ChannelReservation, t *testing.T) {
	if err := res.Cancel(); err == nil {
		t.Fatalf("reservation wasn't deleted from wallet")
	}
}

// calcStaticFee calculates appropriate fees for commitment transactions.  This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
// TODO(bvu): Refactor when dynamic fee estimation is added.
func calcStaticFee(numHTLCs int) btcutil.Amount {
	const (
		commitWeight = btcutil.Amount(724)
		htlcWeight   = 172
		feePerKw     = btcutil.Amount(250/4) * 1000
	)
	return feePerKw * (commitWeight +
		btcutil.Amount(htlcWeight*numHTLCs)) / 1000
}

func loadTestCredits(miner *rpctest.Harness, w *lnwallet.LightningWallet,
	numOutputs, btcPerOutput int) error {

	// Using the mining node, spend from a coinbase output numOutputs to
	// give us btcPerOutput with each output.
	satoshiPerOutput := int64(btcPerOutput * 1e8)
	expectedBalance, err := w.ConfirmedBalance(1, false)
	if err != nil {
		return err
	}
	expectedBalance += btcutil.Amount(satoshiPerOutput * int64(numOutputs))
	addrs := make([]btcutil.Address, 0, numOutputs)
	for i := 0; i < numOutputs; i++ {
		// Grab a fresh address from the wallet to house this output.
		walletAddr, err := w.NewAddress(lnwallet.WitnessPubKey, false)
		if err != nil {
			return err
		}

		script, err := txscript.PayToAddrScript(walletAddr)
		if err != nil {
			return err
		}

		addrs = append(addrs, walletAddr)

		output := &wire.TxOut{
			Value:    satoshiPerOutput,
			PkScript: script,
		}
		if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
			return err
		}
	}

	// TODO(roasbeef): shouldn't hardcode 10, use config param that dictates
	// how many confs we wait before opening a channel.
	// Generate 10 blocks with the mining node, this should mine all
	// numOutputs transactions created above. We generate 10 blocks here
	// in order to give all the outputs a "sufficient" number of confirmations.
	if _, err := miner.Node.Generate(10); err != nil {
		return err
	}

	// Wait until the wallet has finished syncing up to the main chain.
	ticker := time.NewTicker(100 * time.Millisecond)

	for range ticker.C {
		balance, err := w.ConfirmedBalance(1, false)
		if err != nil {
			return err
		}
		if balance == expectedBalance {
			break
		}
	}
	ticker.Stop()

	return nil
}

// createTestWallet creates a test LightningWallet will a total of 20BTC
// available for funding channels.
func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
	netParams *chaincfg.Params, notifier chainntnfs.ChainNotifier,
	wc lnwallet.WalletController, signer lnwallet.Signer,
	bio lnwallet.BlockChainIO) (*lnwallet.LightningWallet, error) {

	dbDir := filepath.Join(tempTestDir, "cdb")
	cdb, err := channeldb.Open(dbDir)
	if err != nil {
		return nil, err
	}

	cfg := lnwallet.Config{
		Database:         cdb,
		Notifier:         notifier,
		WalletController: wc,
		Signer:           signer,
		ChainIO:          bio,
		FeeEstimator:     lnwallet.StaticFeeEstimator{FeeRate: 250},
		DefaultConstraints: channeldb.ChannelConstraints{
			DustLimit:        500,
			MaxPendingAmount: lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) * 100,
			ChanReserve:      100,
			MinHTLC:          400,
			MaxAcceptedHtlcs: 900,
		},
		NetParams: *netParams,
	}

	wallet, err := lnwallet.NewLightningWallet(cfg)
	if err != nil {
		return nil, err
	}

	if err := wallet.Startup(); err != nil {
		return nil, err
	}

	// Load our test wallet with 20 outputs each holding 4BTC.
	if err := loadTestCredits(miningNode, wallet, 20, 4); err != nil {
		return nil, err
	}

	return wallet, nil
}

func testDualFundingReservationWorkflow(miner *rpctest.Harness,
	alice, bob *lnwallet.LightningWallet, t *testing.T) {

	const fundingAmount = btcutil.Amount(5 * 1e8)

	// In this scenario, we'll test a dual funder reservation, with each
	// side putting in 10 BTC.

	// Alice initiates a channel funded with 5 BTC for each side, so 10 BTC
	// total. She also generates 2 BTC in change.
	feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1)
	if err != nil {
		t.Fatalf("unable to query fee estimator: %v", err)
	}
	feePerKw := feePerWeight * 1000
	aliceChanReservation, err := alice.InitChannelReservation(
		fundingAmount*2, fundingAmount, 0, feePerKw, feePerKw,
		bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("unable to initialize funding reservation: %v", err)
	}
	aliceChanReservation.SetNumConfsRequired(numReqConfs)
	aliceChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
		lnwire.NewMSatFromSatoshis(fundingAmount), 10)

	// The channel reservation should now be populated with a multi-sig key
	// from our HD chain, a change output with 3 BTC, and 2 outputs
	// selected of 4 BTC each. Additionally, the rest of the items needed
	// to fulfill a funding contribution should also have been filled in.
	aliceContribution := aliceChanReservation.OurContribution()
	if len(aliceContribution.Inputs) != 2 {
		t.Fatalf("outputs for funding tx not properly selected, have %v "+
			"outputs should have 2", len(aliceContribution.Inputs))
	}
	assertContributionInitPopulated(t, aliceContribution)

	// Bob does the same, generating his own contribution. He then also
	// receives' Alice's contribution, and consumes that so we can continue
	// the funding process.
	bobChanReservation, err := bob.InitChannelReservation(fundingAmount*2,
		fundingAmount, 0, feePerKw, feePerKw, alicePub, aliceAddr,
		chainHash, lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("bob unable to init channel reservation: %v", err)
	}
	bobChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
		lnwire.NewMSatFromSatoshis(fundingAmount), 10)
	bobChanReservation.SetNumConfsRequired(numReqConfs)

	assertContributionInitPopulated(t, bobChanReservation.OurContribution())

	err = bobChanReservation.ProcessContribution(aliceContribution)
	if err != nil {
		t.Fatalf("bob unable to process alice's contribution: %v", err)
	}
	assertContributionInitPopulated(t, bobChanReservation.TheirContribution())

	bobContribution := bobChanReservation.OurContribution()

	// Bob then sends over his contribution, which will be consumed by
	// Alice. After this phase, Alice should have all the necessary
	// material required to craft the funding transaction and commitment
	// transactions.
	err = aliceChanReservation.ProcessContribution(bobContribution)
	if err != nil {
		t.Fatalf("alice unable to process bob's contribution: %v", err)
	}
	assertContributionInitPopulated(t, aliceChanReservation.TheirContribution())

	// At this point, all Alice's signatures should be fully populated.
	aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
	if aliceFundingSigs == nil {
		t.Fatalf("alice's funding signatures not populated")
	}
	if aliceCommitSig == nil {
		t.Fatalf("alice's commit signatures not populated")
	}

	// Additionally, Bob's signatures should also be fully populated.
	bobFundingSigs, bobCommitSig := bobChanReservation.OurSignatures()
	if bobFundingSigs == nil {
		t.Fatalf("bob's funding signatures not populated")
	}
	if bobCommitSig == nil {
		t.Fatalf("bob's commit signatures not populated")
	}

	// To concludes, we'll consume first Alice's signatures with Bob, and
	// then the other way around.
	_, err = aliceChanReservation.CompleteReservation(
		bobFundingSigs, bobCommitSig,
	)
	if err != nil {
		t.Fatalf("unable to consume alice's sigs: %v", err)
	}
	_, err = bobChanReservation.CompleteReservation(
		aliceFundingSigs, aliceCommitSig,
	)
	if err != nil {
		t.Fatalf("unable to consume bob's sigs: %v", err)
	}

	// At this point, the funding tx should have been populated.
	fundingTx := aliceChanReservation.FinalFundingTx()
	if fundingTx == nil {
		t.Fatalf("funding transaction never created!")
	}

	// The resulting active channel state should have been persisted to the
	// DB.
	fundingSha := fundingTx.TxHash()
	aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
	if err != nil {
		t.Fatalf("unable to retrieve channel from DB: %v", err)
	}
	if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
		t.Fatalf("channel state not properly saved")
	}
	if aliceChannels[0].ChanType != channeldb.DualFunder {
		t.Fatalf("channel not detected as dual funder")
	}
	bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
	if err != nil {
		t.Fatalf("unable to retrieve channel from DB: %v", err)
	}
	if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
		t.Fatalf("channel state not properly saved")
	}
	if bobChannels[0].ChanType != channeldb.DualFunder {
		t.Fatalf("channel not detected as dual funder")
	}

	// Mine a single block, the funding transaction should be included
	// within this block.
	blockHashes, err := miner.Node.Generate(1)
	if err != nil {
		t.Fatalf("unable to generate block: %v", err)
	}
	block, err := miner.Node.GetBlock(blockHashes[0])
	if err != nil {
		t.Fatalf("unable to find block: %v", err)
	}
	if len(block.Transactions) != 2 {
		t.Fatalf("funding transaction wasn't mined: %v", err)
	}
	blockTx := block.Transactions[1]
	if blockTx.TxHash() != fundingSha {
		t.Fatalf("incorrect transaction was mined")
	}

	assertReservationDeleted(aliceChanReservation, t)
	assertReservationDeleted(bobChanReservation, t)
}

func testFundingTransactionLockedOutputs(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	// Create a single channel asking for 16 BTC total.
	fundingAmount := btcutil.Amount(8 * 1e8)
	feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1)
	if err != nil {
		t.Fatalf("unable to query fee estimator: %v", err)
	}
	feePerKw := feePerWeight * 1000
	_, err = alice.InitChannelReservation(fundingAmount,
		fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
		lnwire.FFAnnounceChannel,
	)
	if err != nil {
		t.Fatalf("unable to initialize funding reservation 1: %v", err)
	}

	// Now attempt to reserve funds for another channel, this time
	// requesting 900 BTC. We only have around 64BTC worth of outpoints
	// that aren't locked, so this should fail.
	amt := btcutil.Amount(900 * 1e8)
	failedReservation, err := alice.InitChannelReservation(amt, amt, 0,
		feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel)
	if err == nil {
		t.Fatalf("not error returned, should fail on coin selection")
	}
	if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok {
		t.Fatalf("error not coinselect error: %v", err)
	}
	if failedReservation != nil {
		t.Fatalf("reservation should be nil")
	}
}

func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1)
	if err != nil {
		t.Fatalf("unable to query fee estimator: %v", err)
	}
	feePerKw := feePerWeight * 1000

	// Create a reservation for 44 BTC.
	fundingAmount := btcutil.Amount(44 * 1e8)
	chanReservation, err := alice.InitChannelReservation(fundingAmount,
		fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
		lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("unable to initialize funding reservation: %v", err)
	}

	// Attempt to create another channel with 44 BTC, this should fail.
	_, err = alice.InitChannelReservation(fundingAmount,
		fundingAmount, 0, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
		lnwire.FFAnnounceChannel,
	)
	if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok {
		t.Fatalf("coin selection succeded should have insufficient funds: %v",
			err)
	}

	// Now cancel that old reservation.
	if err := chanReservation.Cancel(); err != nil {
		t.Fatalf("unable to cancel reservation: %v", err)
	}

	// Those outpoints should no longer be locked.
	lockedOutPoints := alice.LockedOutpoints()
	if len(lockedOutPoints) != 0 {
		t.Fatalf("outpoints still locked")
	}

	// Reservation ID should no longer be tracked.
	numReservations := alice.ActiveReservations()
	if len(alice.ActiveReservations()) != 0 {
		t.Fatalf("should have 0 reservations, instead have %v",
			numReservations)
	}

	// TODO(roasbeef): create method like Balance that ignores locked
	// outpoints, will let us fail early/fast instead of querying and
	// attempting coin selection.

	// Request to fund a new channel should now succeed.
	_, err = alice.InitChannelReservation(fundingAmount, fundingAmount, 0,
		feePerKw, feePerKw, bobPub, bobAddr, chainHash, lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("unable to initialize funding reservation: %v", err)
	}
}

func testCancelNonExistantReservation(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	feeRate, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1)
	if err != nil {
		t.Fatalf("unable to query fee estimator: %v", err)
	}

	// Create our own reservation, give it some ID.
	res, err := lnwallet.NewChannelReservation(
		1000, 1000, feeRate, alice, 22, 10, &testHdSeed, lnwire.FFAnnounceChannel,
	)
	if err != nil {
		t.Fatalf("unable to create res: %v", err)
	}

	// Attempt to cancel this reservation. This should fail, we know
	// nothing of it.
	if err := res.Cancel(); err == nil {
		t.Fatalf("cancelled non-existent reservation")
	}
}

func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	// We'll attempt to create a new reservation with an extremely high fee
	// rate. This should push our balance into the negative and result in a
	// failure to create the reservation.
	fundingAmount := btcutil.Amount(4 * 1e8)
	feePerKw := btcutil.Amount(btcutil.SatoshiPerBitcoin * 10)
	_, err := alice.InitChannelReservation(
		fundingAmount, fundingAmount, 0, feePerKw, feePerKw, bobPub,
		bobAddr, chainHash, lnwire.FFAnnounceChannel,
	)
	switch {
	case err == nil:
		t.Fatalf("initialization should've failed due to " +
			"insufficient local amount")

	case !strings.Contains(err.Error(), "local output is too small"):
		t.Fatalf("incorrect error: %v", err)
	}
}

func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContribution) {
	_, _, line, _ := runtime.Caller(1)

	if c.FirstCommitmentPoint == nil {
		t.Fatalf("line #%v: commitment point not fond", line)
	}

	if c.CsvDelay == 0 {
		t.Fatalf("line #%v: csv delay not set", line)
	}

	if c.MultiSigKey == nil {
		t.Fatalf("line #%v: multi-sig key not set", line)
	}
	if c.RevocationBasePoint == nil {
		t.Fatalf("line #%v: revocation key not set", line)
	}
	if c.PaymentBasePoint == nil {
		t.Fatalf("line #%v: payment key not set", line)
	}
	if c.DelayBasePoint == nil {
		t.Fatalf("line #%v: delay key not set", line)
	}

	if c.DustLimit == 0 {
		t.Fatalf("line #%v: dust limit not set", line)
	}
	if c.MaxPendingAmount == 0 {
		t.Fatalf("line #%v: max pending amt not set", line)
	}
	if c.ChanReserve == 0 {
		// TODO(roasbeef): need to follow up and ensure reserve set to
		// fraction
		t.Fatalf("line #%v: chan reserve not set", line)
	}
	if c.MinHTLC == 0 {
		t.Fatalf("line #%v: min htlc not set", line)
	}
	if c.MaxAcceptedHtlcs == 0 {
		t.Fatalf("line #%v: max accepted htlc's not set", line)
	}
}

func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
	alice, bob *lnwallet.LightningWallet, t *testing.T) {

	// For this scenario, Alice will be the channel initiator while bob
	// will act as the responder to the workflow.

	// First, Alice will Initialize a reservation for a channel with 4 BTC
	// funded solely by us. We'll also initially push 1 BTC of the channel
	// towards Bob's side.
	fundingAmt := btcutil.Amount(4 * 1e8)
	pushAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
	feePerWeight, err := alice.Cfg.FeeEstimator.EstimateFeePerWeight(1)
	if err != nil {
		t.Fatalf("unable to query fee estimator: %v", err)
	}
	feePerKw := feePerWeight * 1000
	aliceChanReservation, err := alice.InitChannelReservation(fundingAmt,
		fundingAmt, pushAmt, feePerKw, feePerKw, bobPub, bobAddr, chainHash,
		lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("unable to init channel reservation: %v", err)
	}
	aliceChanReservation.SetNumConfsRequired(numReqConfs)
	aliceChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
		lnwire.NewMSatFromSatoshis(fundingAmt), 10)

	// Verify all contribution fields have been set properly.
	aliceContribution := aliceChanReservation.OurContribution()
	if len(aliceContribution.Inputs) < 1 {
		t.Fatalf("outputs for funding tx not properly selected, have %v "+
			"outputs should at least 1", len(aliceContribution.Inputs))
	}
	if len(aliceContribution.ChangeOutputs) != 1 {
		t.Fatalf("coin selection failed, should have one change outputs, "+
			"instead have: %v", len(aliceContribution.ChangeOutputs))
	}
	aliceContribution.CsvDelay = csvDelay
	assertContributionInitPopulated(t, aliceContribution)

	// Next, Bob receives the initial request, generates a corresponding
	// reservation initiation, then consume Alice's contribution.
	bobChanReservation, err := bob.InitChannelReservation(fundingAmt, 0,
		pushAmt, feePerKw, feePerKw, alicePub, aliceAddr, chainHash,
		lnwire.FFAnnounceChannel)
	if err != nil {
		t.Fatalf("unable to create bob reservation: %v", err)
	}
	bobChanReservation.CommitConstraints(csvDelay, lnwallet.MaxHTLCNumber/2,
		lnwire.NewMSatFromSatoshis(fundingAmt), 10)
	bobChanReservation.SetNumConfsRequired(numReqConfs)

	// We'll ensure that Bob's contribution also gets generated properly.
	bobContribution := bobChanReservation.OurContribution()
	bobContribution.CsvDelay = csvDelay
	assertContributionInitPopulated(t, bobContribution)

	// With his contribution generated, he can now process Alice's
	// contribution.
	err = bobChanReservation.ProcessSingleContribution(aliceContribution)
	if err != nil {
		t.Fatalf("bob unable to process alice's contribution: %v", err)
	}
	assertContributionInitPopulated(t, bobChanReservation.TheirContribution())

	// Bob will next send over his contribution to Alice, we simulate this
	// by having Alice immediately process his contribution.
	err = aliceChanReservation.ProcessContribution(bobContribution)
	if err != nil {
		t.Fatalf("alice unable to process bob's contribution")
	}
	assertContributionInitPopulated(t, bobChanReservation.TheirContribution())

	// At this point, Alice should have generated all the signatures
	// required for the funding transaction, as well as Alice's commitment
	// signature to bob.
	aliceRemoteContribution := aliceChanReservation.TheirContribution()
	aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
	if aliceFundingSigs == nil {
		t.Fatalf("funding sigs not found")
	}
	if aliceCommitSig == nil {
		t.Fatalf("commitment sig not found")
	}

	// Additionally, the funding tx and the funding outpoint should have
	// been populated.
	if aliceChanReservation.FinalFundingTx() == nil {
		t.Fatalf("funding transaction never created!")
	}
	if aliceChanReservation.FundingOutpoint() == nil {
		t.Fatalf("funding outpoint never created!")
	}

	// Their funds should also be filled in.
	if len(aliceRemoteContribution.Inputs) != 0 {
		t.Fatalf("bob shouldn't have any inputs, instead has %v",
			len(aliceRemoteContribution.Inputs))
	}
	if len(aliceRemoteContribution.ChangeOutputs) != 0 {
		t.Fatalf("bob shouldn't have any change outputs, instead "+
			"has %v",
			aliceRemoteContribution.ChangeOutputs[0].Value)
	}

	// Next, Alice will send over her signature for Bob's commitment
	// transaction, as well as the funding outpoint.
	fundingPoint := aliceChanReservation.FundingOutpoint()
	_, err = bobChanReservation.CompleteReservationSingle(
		fundingPoint, aliceCommitSig,
	)
	if err != nil {
		t.Fatalf("bob unable to consume single reservation: %v", err)
	}

	// Finally, we'll conclude the reservation process by sending over
	// Bob's commitment signature, which is the final thing Alice needs to
	// be able to safely broadcast the funding transaction.
	_, bobCommitSig := bobChanReservation.OurSignatures()
	if bobCommitSig == nil {
		t.Fatalf("bob failed to generate commitment signature: %v", err)
	}
	_, err = aliceChanReservation.CompleteReservation(
		nil, bobCommitSig,
	)
	if err != nil {
		t.Fatalf("alice unable to complete reservation: %v", err)
	}

	// The resulting active channel state should have been persisted to the
	// DB for both Alice and Bob.
	fundingTx := aliceChanReservation.FinalFundingTx()
	fundingSha := fundingTx.TxHash()
	aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
	if err != nil {
		t.Fatalf("unable to retrieve channel from DB: %v", err)
	}
	if len(aliceChannels) != 1 {
		t.Fatalf("alice didn't save channel state: %v", err)
	}
	if !bytes.Equal(aliceChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
		t.Fatalf("channel state not properly saved: %v vs %v",
			hex.EncodeToString(aliceChannels[0].FundingOutpoint.Hash[:]),
			hex.EncodeToString(fundingSha[:]))
	}
	if !aliceChannels[0].IsInitiator {
		t.Fatalf("alice not detected as channel initiator")
	}
	if aliceChannels[0].ChanType != channeldb.SingleFunder {
		t.Fatalf("channel type is incorrect, expected %v instead got %v",
			channeldb.SingleFunder, aliceChannels[0].ChanType)
	}

	bobChannels, err := bob.Cfg.Database.FetchOpenChannels(alicePub)
	if err != nil {
		t.Fatalf("unable to retrieve channel from DB: %v", err)
	}
	if len(bobChannels) != 1 {
		t.Fatalf("bob didn't save channel state: %v", err)
	}
	if !bytes.Equal(bobChannels[0].FundingOutpoint.Hash[:], fundingSha[:]) {
		t.Fatalf("channel state not properly saved: %v vs %v",
			hex.EncodeToString(bobChannels[0].FundingOutpoint.Hash[:]),
			hex.EncodeToString(fundingSha[:]))
	}
	if bobChannels[0].IsInitiator {
		t.Fatalf("bob not detected as channel responder")
	}
	if bobChannels[0].ChanType != channeldb.SingleFunder {
		t.Fatalf("channel type is incorrect, expected %v instead got %v",
			channeldb.SingleFunder, bobChannels[0].ChanType)
	}

	// Mine a single block, the funding transaction should be included
	// within this block.
	blockHashes, err := miner.Node.Generate(1)
	if err != nil {
		t.Fatalf("unable to generate block: %v", err)
	}
	block, err := miner.Node.GetBlock(blockHashes[0])
	if err != nil {
		t.Fatalf("unable to find block: %v", err)
	}
	if len(block.Transactions) != 2 {
		t.Fatalf("funding transaction wasn't mined: %v", err)
	}
	blockTx := block.Transactions[1]
	if blockTx.TxHash() != fundingSha {
		t.Fatalf("incorrect transaction was mined")
	}

	assertReservationDeleted(aliceChanReservation, t)
	assertReservationDeleted(bobChanReservation, t)
}

func testListTransactionDetails(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	// Create 5 new outputs spendable by the wallet.
	const numTxns = 5
	const outputAmt = btcutil.SatoshiPerBitcoin
	txids := make(map[chainhash.Hash]struct{})
	for i := 0; i < numTxns; i++ {
		addr, err := alice.NewAddress(lnwallet.WitnessPubKey, false)
		if err != nil {
			t.Fatalf("unable to create new address: %v", err)
		}
		script, err := txscript.PayToAddrScript(addr)
		if err != nil {
			t.Fatalf("unable to create output script: %v", err)
		}

		output := &wire.TxOut{
			Value:    outputAmt,
			PkScript: script,
		}
		txid, err := miner.SendOutputs([]*wire.TxOut{output}, 10)
		if err != nil {
			t.Fatalf("unable to send coinbase: %v", err)
		}
		txids[*txid] = struct{}{}
	}

	// Generate 10 blocks to mine all the transactions created above.
	const numBlocksMined = 10
	blocks, err := miner.Node.Generate(numBlocksMined)
	if err != nil {
		t.Fatalf("unable to mine blocks: %v", err)
	}

	// Next, fetch all the current transaction details.
	// TODO(roasbeef): use ntfn client here instead?
	time.Sleep(time.Second * 2)
	txDetails, err := alice.ListTransactionDetails()
	if err != nil {
		t.Fatalf("unable to fetch tx details: %v", err)
	}

	// This is a mapping from:
	// blockHash -> transactionHash -> transactionOutputs
	blockTxOuts := make(map[chainhash.Hash]map[chainhash.Hash][]*wire.TxOut)

	// Each of the transactions created above should be found with the
	// proper details populated.
	for _, txDetail := range txDetails {
		if _, ok := txids[txDetail.Hash]; !ok {
			continue
		}

		if txDetail.NumConfirmations != numBlocksMined {
			t.Fatalf("num confs incorrect, got %v expected %v",
				txDetail.NumConfirmations, numBlocksMined)
		}
		if txDetail.Value != outputAmt {
			t.Fatalf("tx value incorrect, got %v expected %v",
				txDetail.Value, outputAmt)
		}

		if !bytes.Equal(txDetail.BlockHash[:], blocks[0][:]) {
			t.Fatalf("block hash mismatch, got %v expected %v",
				txDetail.BlockHash, blocks[0])
		}

		// This fetches the transactions in a block so that we can compare the
		// txouts stored in the mined transaction against the ones in the transaction
		// details
		if _, ok := blockTxOuts[*txDetail.BlockHash]; !ok {
			fetchedBlock, err := alice.Cfg.ChainIO.GetBlock(txDetail.BlockHash)
			if err != nil {
				t.Fatalf("err fetching block: %s", err)
			}

			transactions :=
				make(map[chainhash.Hash][]*wire.TxOut, len(fetchedBlock.Transactions))
			for _, tx := range fetchedBlock.Transactions {
				transactions[tx.TxHash()] = tx.TxOut
			}

			blockTxOuts[fetchedBlock.BlockHash()] = transactions
		}

		if txOuts, ok := blockTxOuts[*txDetail.BlockHash][txDetail.Hash]; !ok {
			t.Fatalf("tx (%v) not found in block (%v)",
				txDetail.Hash, txDetail.BlockHash)
		} else {
			var destinationAddresses []btcutil.Address

			for _, txOut := range txOuts {
				_, addrs, _, err :=
					txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams)
				if err != nil {
					t.Fatalf("err extract script addresses: %s", err)
				}
				destinationAddresses = append(destinationAddresses, addrs...)
			}

			if !reflect.DeepEqual(txDetail.DestAddresses, destinationAddresses) {
				t.Fatalf("destination addresses mismatch, got %v expected %v",
					txDetail.DestAddresses, destinationAddresses)
			}
		}

		delete(txids, txDetail.Hash)
	}
	if len(txids) != 0 {
		t.Fatalf("all transactions not found in details!")
	}

	// Next create a transaction paying to an output which isn't under the
	// wallet's control.
	b := txscript.NewScriptBuilder()
	b.AddOp(txscript.OP_0)
	outputScript, err := b.Script()
	if err != nil {
		t.Fatalf("unable to make output script: %v", err)
	}
	burnOutput := wire.NewTxOut(outputAmt, outputScript)
	burnTXID, err := alice.SendOutputs([]*wire.TxOut{burnOutput}, 10)
	if err != nil {
		t.Fatalf("unable to create burn tx: %v", err)
	}
	burnBlock, err := miner.Node.Generate(1)
	if err != nil {
		t.Fatalf("unable to mine block: %v", err)
	}

	// Fetch the transaction details again, the new transaction should be
	// shown as debiting from the wallet's balance.
	time.Sleep(time.Second * 2)
	txDetails, err = alice.ListTransactionDetails()
	if err != nil {
		t.Fatalf("unable to fetch tx details: %v", err)
	}
	var burnTxFound bool
	for _, txDetail := range txDetails {
		if !bytes.Equal(txDetail.Hash[:], burnTXID[:]) {
			continue
		}

		burnTxFound = true
		if txDetail.NumConfirmations != 1 {
			t.Fatalf("num confs incorrect, got %v expected %v",
				txDetail.NumConfirmations, 1)
		}

		// We assert that the value is greater than the amount we
		// attempted to send, as the wallet should've paid some amount
		// of network fees.
		if txDetail.Value >= -outputAmt {
			fmt.Println(spew.Sdump(txDetail))
			t.Fatalf("tx value incorrect, got %v expected %v",
				int64(txDetail.Value), -int64(outputAmt))
		}
		if !bytes.Equal(txDetail.BlockHash[:], burnBlock[0][:]) {
			t.Fatalf("block hash mismatch, got %v expected %v",
				txDetail.BlockHash, burnBlock[0])
		}
	}
	if !burnTxFound {
		t.Fatal("tx burning btc not found")
	}
}

func testTransactionSubscriptions(miner *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	// First, check to see if this wallet meets the TransactionNotifier
	// interface, if not then we'll skip this test for this particular
	// implementation of the WalletController.
	txClient, err := alice.SubscribeTransactions()
	if err != nil {
		t.Fatalf("unable to generate tx subscription: %v", err)
	}
	defer txClient.Cancel()

	const (
		outputAmt = btcutil.SatoshiPerBitcoin
		numTxns   = 3
	)
	unconfirmedNtfns := make(chan struct{})
	go func() {
		for i := 0; i < numTxns; i++ {
			txDetail := <-txClient.UnconfirmedTransactions()
			if txDetail.NumConfirmations != 0 {
				t.Fatalf("incorrect number of confs, expected %v got %v",
					0, txDetail.NumConfirmations)
			}
			if txDetail.Value != outputAmt {
				t.Fatalf("incorrect output amt, expected %v got %v",
					outputAmt, txDetail.Value)
			}
			if txDetail.BlockHash != nil {
				t.Fatalf("block hash should be nil, is instead %v",
					txDetail.BlockHash)
			}
		}

		close(unconfirmedNtfns)
	}()

	// Next, fetch a fresh address from the wallet, create 3 new outputs
	// with the pkScript.
	for i := 0; i < numTxns; i++ {
		addr, err := alice.NewAddress(lnwallet.WitnessPubKey, false)
		if err != nil {
			t.Fatalf("unable to create new address: %v", err)
		}
		script, err := txscript.PayToAddrScript(addr)
		if err != nil {
			t.Fatalf("unable to create output script: %v", err)
		}

		output := &wire.TxOut{
			Value:    outputAmt,
			PkScript: script,
		}
		if _, err := miner.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
			t.Fatalf("unable to send coinbase: %v", err)
		}
	}

	// We should receive a notification for all three transactions
	// generated above.
	select {
	case <-time.After(time.Second * 5):
		t.Fatalf("transactions not received after 3 seconds")
	case <-unconfirmedNtfns: // Fall through on successs
	}

	confirmedNtfns := make(chan struct{})
	go func() {
		for i := 0; i < numTxns; i++ {
			txDetail := <-txClient.ConfirmedTransactions()
			if txDetail.NumConfirmations != 1 {
				t.Fatalf("incorrect number of confs, expected %v got %v",
					1, txDetail.NumConfirmations)
			}
			if txDetail.Value != outputAmt {
				t.Fatalf("incorrect output amt, expected %v got %v",
					outputAmt, txDetail.Value)
			}
		}
		close(confirmedNtfns)
	}()

	// Next mine a single block, all the transactions generated above
	// should be included.
	if _, err := miner.Node.Generate(1); err != nil {
		t.Fatalf("unable to generate block: %v", err)
	}

	// We should receive a notification for all three transactions
	// since they should be mined in the next block.
	select {
	case <-time.After(time.Second * 5):
		t.Fatalf("transactions not received after 3 seconds")
	case <-confirmedNtfns: // Fall through on success
	}
}

func testSignOutputUsingTweaks(r *rpctest.Harness,
	alice, _ *lnwallet.LightningWallet, t *testing.T) {

	// We'd like to test the ability of the wallet's Signer implementation
	// to be able to sign with a private key derived from tweaking the
	// specific public key. This scenario exercises the case when the
	// wallet needs to sign for a sweep of a revoked output, or just claim
	// any output that pays to a tweaked key.

	// First, generate a new public key under the control of the wallet,
	// then generate a revocation key using it.
	pubKey, err := alice.NewRawKey()
	if err != nil {
		t.Fatalf("unable to obtain public key: %v", err)
	}

	// As we'd like to test both single tweak, and double tweak spends,
	// we'll generate a commitment pre-image, then derive a revocation key
	// and single tweak from that.
	commitPreimage := bytes.Repeat([]byte{2}, 32)
	commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
		commitPreimage)

	revocationKey := lnwallet.DeriveRevocationPubkey(pubKey, commitPoint)
	commitTweak := lnwallet.SingleTweakBytes(commitPoint, pubKey)

	tweakedPub := lnwallet.TweakPubKey(pubKey, commitPoint)

	// As we'd like to test both single and double tweaks, we'll repeat
	// the same set up twice. The first will use a regular single tweak,
	// and the second will use a double tweak.
	baseKey := pubKey
	for i := 0; i < 2; i++ {
		var tweakedKey *btcec.PublicKey
		if i == 0 {
			tweakedKey = tweakedPub
		} else {
			tweakedKey = revocationKey
		}

		// Using the given key for the current iteration, we'll
		// generate a regular p2wkh from that.
		pubkeyHash := btcutil.Hash160(tweakedKey.SerializeCompressed())
		keyAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubkeyHash,
			&chaincfg.SimNetParams)
		if err != nil {
			t.Fatalf("unable to create addr: %v", err)
		}
		keyScript, err := txscript.PayToAddrScript(keyAddr)
		if err != nil {
			t.Fatalf("unable to generate script: %v", err)
		}

		// With the script fully assembled, instruct the wallet to fund
		// the output with a newly created transaction.
		newOutput := &wire.TxOut{
			Value:    btcutil.SatoshiPerBitcoin,
			PkScript: keyScript,
		}
		txid, err := alice.SendOutputs([]*wire.TxOut{newOutput}, 10)
		if err != nil {
			t.Fatalf("unable to create output: %v", err)
		}

		// Query for the transaction generated above so we can located
		// the index of our output.
		tx, err := r.Node.GetRawTransaction(txid)
		if err != nil {
			t.Fatalf("unable to query for tx: %v", err)
		}
		var outputIndex uint32
		if bytes.Equal(tx.MsgTx().TxOut[0].PkScript, keyScript) {
			outputIndex = 0
		} else {
			outputIndex = 1
		}

		// With the index located, we can create a transaction spending
		// the referenced output.
		sweepTx := wire.NewMsgTx(2)
		sweepTx.AddTxIn(&wire.TxIn{
			PreviousOutPoint: wire.OutPoint{
				Hash:  tx.MsgTx().TxHash(),
				Index: outputIndex,
			},
		})
		sweepTx.AddTxOut(&wire.TxOut{
			Value:    1000,
			PkScript: keyScript,
		})

		// Now we can populate the sign descriptor which we'll use to
		// generate the signature. Within the descriptor we set the
		// private tweak value as the key in the script is derived
		// based on this tweak value and the key we originally
		// generated above.
		signDesc := &lnwallet.SignDescriptor{
			PubKey:        baseKey,
			WitnessScript: keyScript,
			Output:        newOutput,
			HashType:      txscript.SigHashAll,
			SigHashes:     txscript.NewTxSigHashes(sweepTx),
			InputIndex:    0,
		}

		// If this is the first, loop, we'll use the generated single
		// tweak, otherwise, we'll use the double tweak.
		if i == 0 {
			signDesc.SingleTweak = commitTweak
		} else {
			signDesc.DoubleTweak = commitSecret
		}

		// With the descriptor created, we use it to generate a
		// signature, then manually create a valid witness stack we'll
		// use for signing.
		spendSig, err := alice.Cfg.Signer.SignOutputRaw(sweepTx, signDesc)
		if err != nil {
			t.Fatalf("unable to generate signature: %v", err)
		}
		witness := make([][]byte, 2)
		witness[0] = append(spendSig, byte(txscript.SigHashAll))
		witness[1] = tweakedKey.SerializeCompressed()
		sweepTx.TxIn[0].Witness = witness

		// Finally, attempt to validate the completed transaction. This
		// should succeed if the wallet was able to properly generate
		// the proper private key.
		vm, err := txscript.NewEngine(keyScript,
			sweepTx, 0, txscript.StandardVerifyFlags, nil,
			nil, int64(btcutil.SatoshiPerBitcoin))
		if err != nil {
			t.Fatalf("unable to create engine: %v", err)
		}
		if err := vm.Execute(); err != nil {
			t.Fatalf("spend #%v is invalid: %v", i, err)
		}
	}
}

func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
	_ *lnwallet.LightningWallet, t *testing.T) {
	// We first mine a few blocks to ensure any transactions still in the
	// mempool confirm, and then get the original balance, before a
	// reorganization that doesn't invalidate any existing transactions or
	// create any new non-coinbase transactions. We'll then check if it's
	// the same after the empty reorg.
	_, err := r.Node.Generate(5)
	if err != nil {
		t.Fatalf("unable to generate blocks on passed node: %v", err)
	}

	// Give wallet time to catch up.
	err = waitForWalletSync(w)
	if err != nil {
		t.Fatalf("unable to sync wallet: %v", err)
	}

	// Send some money from the miner to the wallet
	err = loadTestCredits(r, w, 20, 4)
	if err != nil {
		t.Fatalf("unable to send money to lnwallet: %v", err)
	}

	// Send some money from the wallet back to the miner.
	// Grab a fresh address from the miner to house this output.
	minerAddr, err := r.NewAddress()
	if err != nil {
		t.Fatalf("unable to generate address for miner: %v", err)
	}
	script, err := txscript.PayToAddrScript(minerAddr)
	if err != nil {
		t.Fatalf("unable to create pay to addr script: %v", err)
	}
	output := &wire.TxOut{
		Value:    1e8,
		PkScript: script,
	}
	if _, err = w.SendOutputs([]*wire.TxOut{output}, 10); err != nil {
		t.Fatalf("unable to send outputs: %v", err)
	}
	_, err = r.Node.Generate(50)
	if err != nil {
		t.Fatalf("unable to generate blocks on passed node: %v", err)
	}

	// Give wallet time to catch up.
	err = waitForWalletSync(w)
	if err != nil {
		t.Fatalf("unable to sync wallet: %v", err)
	}

	// Get the original balance.
	origBalance, err := w.ConfirmedBalance(1, false)
	if err != nil {
		t.Fatalf("unable to query for balance: %v", err)
	}

	// Now we cause a reorganization as follows.
	// Step 1: create a new miner and start it.
	r2, err := rpctest.New(r.ActiveNet, nil, nil)
	if err != nil {
		t.Fatalf("unable to create mining node: %v", err)
	}
	err = r2.SetUp(false, 0)
	if err != nil {
		t.Fatalf("unable to set up mining node: %v", err)
	}
	defer r2.TearDown()
	newBalance, err := w.ConfirmedBalance(1, false)
	if err != nil {
		t.Fatalf("unable to query for balance: %v", err)
	}
	if origBalance != newBalance {
		t.Fatalf("wallet balance incorrect, should have %v, "+
			"instead have %v", origBalance, newBalance)
	}

	// Step 2: connect the miner to the passed miner and wait for
	// synchronization.
	err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANAdd)
	if err != nil {
		t.Fatalf("unable to connect mining nodes together: %v", err)
	}
	err = rpctest.JoinNodes([]*rpctest.Harness{r2, r}, rpctest.Blocks)
	if err != nil {
		t.Fatalf("unable to synchronize mining nodes: %v", err)
	}

	// Step 3: Do a set of reorgs by disconecting the two miners, mining
	// one block on the passed miner and two on the created miner,
	// connecting them, and waiting for them to sync.
	for i := 0; i < 5; i++ {
		peers, err := r2.Node.GetPeerInfo()
		if err != nil {
			t.Fatalf("unable to get peer info: %v", err)
		}
		numPeers := len(peers)
		err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANRemove)
		if err != nil {
			t.Fatalf("unable to disconnect mining nodes: %v", err)
		}
		// Wait for disconnection
		timeout := time.After(30 * time.Second)
		for true {
			// Allow for timeout
			select {
			case <-timeout:
				t.Fatalf("timeout waiting for miner disconnect")
			default:
			}
			peers, err = r2.Node.GetPeerInfo()
			if err != nil {
				t.Fatalf("unable to get peer info: %v", err)
			}
			if len(peers) < numPeers {
				break
			}
			time.Sleep(100 * time.Millisecond)
		}
		_, err = r.Node.Generate(2)
		if err != nil {
			t.Fatalf("unable to generate blocks on passed node: %v",
				err)
		}
		_, err = r2.Node.Generate(3)
		if err != nil {
			t.Fatalf("unable to generate blocks on created node: %v",
				err)
		}

		// Step 5: Reconnect the miners and wait for them to synchronize.
		err = r2.Node.AddNode(r.P2PAddress(), rpcclient.ANAdd)
		if err != nil {
			t.Fatalf("unable to connect mining nodes together: %v",
				err)
		}
		err = rpctest.JoinNodes([]*rpctest.Harness{r2, r},
			rpctest.Blocks)
		if err != nil {
			t.Fatalf("unable to synchronize mining nodes: %v", err)
		}

		// Give wallet time to catch up.
		err = waitForWalletSync(w)
		if err != nil {
			t.Fatalf("unable to sync wallet: %v", err)
		}
	}

	// Now we check that the wallet balance stays the same.
	newBalance, err = w.ConfirmedBalance(1, false)
	if err != nil {
		t.Fatalf("unable to query for balance: %v", err)
	}
	if origBalance != newBalance {
		t.Fatalf("wallet balance incorrect, should have %v, "+
			"instead have %v", origBalance, newBalance)
	}
}

type walletTestCase struct {
	name string
	test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
		test *testing.T)
}

var walletTests = []walletTestCase{
	{
		name: "insane fee reject",
		test: testReservationInitiatorBalanceBelowDustCancel,
	},
	{
		name: "single funding workflow",
		test: testSingleFunderReservationWorkflow,
	},
	{
		name: "dual funder workflow",
		test: testDualFundingReservationWorkflow,
	},
	{
		name: "output locking",
		test: testFundingTransactionLockedOutputs,
	},
	{
		name: "reservation insufficient funds",
		test: testFundingCancellationNotEnoughFunds,
	},
	{
		name: "transaction subscriptions",
		test: testTransactionSubscriptions,
	},
	{
		name: "transaction details",
		test: testListTransactionDetails,
	},
	{
		name: "signed with tweaked pubkeys",
		test: testSignOutputUsingTweaks,
	},
	{
		name: "test cancel non-existent reservation",
		test: testCancelNonExistantReservation,
	},
	{
		name: "reorg wallet balance",
		test: testReorgWalletBalance,
	},
}

func clearWalletStates(a, b *lnwallet.LightningWallet) error {
	a.ResetReservations()
	b.ResetReservations()

	if err := a.Cfg.Database.Wipe(); err != nil {
		return err
	}

	return b.Cfg.Database.Wipe()
}

func waitForWalletSync(w *lnwallet.LightningWallet) error {
	var synced bool
	var err error
	timeout := time.After(10 * time.Second)
	for !synced {
		synced, err = w.IsSynced()
		if err != nil {
			return err
		}
		select {
		case <-timeout:
			return fmt.Errorf("timeout after 10s")
		default:
		}
		time.Sleep(100 * time.Millisecond)
	}
	return nil
}

// TestInterfaces tests all registered interfaces with a unified set of tests
// which excersie each of the required methods found within the WalletController
// interface.
//
// NOTE: In the future, when additional implementations of the WalletController
// interface have been implemented, in order to ensure the new concrete
// implementation is automatically tested, two steps must be undertaken. First,
// one needs add a "non-captured" (_) import from the new sub-package. This
// import should trigger an init() method within the package which registers
// the interface. Second, an additional case in the switch within the main loop
// below needs to be added which properly initializes the interface.
//
// TODO(roasbeef): purge bobNode in favor of dual lnwallet's
func TestLightningWallet(t *testing.T) {
	t.Parallel()

	// Initialize the harness around a btcd node which will serve as our
	// dedicated miner to generate blocks, cause re-orgs, etc. We'll set
	// up this node with a chain length of 125, so we have plentyyy of BTC
	// to play around with.
	miningNode, err := rpctest.New(netParams, nil, nil)
	if err != nil {
		t.Fatalf("unable to create mining node: %v", err)
	}
	defer miningNode.TearDown()
	if err := miningNode.SetUp(true, 25); err != nil {
		t.Fatalf("unable to set up mining node: %v", err)
	}

	// Next mine enough blocks in order for segwit and the CSV package
	// soft-fork to activate on SimNet.
	numBlocks := netParams.MinerConfirmationWindow * 2
	if _, err := miningNode.Node.Generate(numBlocks); err != nil {
		t.Fatalf("unable to generate blocks: %v", err)
	}

	rpcConfig := miningNode.RPCConfig()

	chainNotifier, err := btcdnotify.New(&rpcConfig)
	if err != nil {
		t.Fatalf("unable to create notifier: %v", err)
	}
	if err := chainNotifier.Start(); err != nil {
		t.Fatalf("unable to start notifier: %v", err)
	}

	var (
		bio lnwallet.BlockChainIO

		aliceSigner lnwallet.Signer
		bobSigner   lnwallet.Signer

		aliceWalletController lnwallet.WalletController
		bobWalletController   lnwallet.WalletController
	)
	for _, walletDriver := range lnwallet.RegisteredWallets() {
		tempTestDirAlice, err := ioutil.TempDir("", "lnwallet")
		if err != nil {
			t.Fatalf("unable to create temp directory: %v", err)
		}
		defer os.RemoveAll(tempTestDirAlice)

		tempTestDirBob, err := ioutil.TempDir("", "lnwallet")
		if err != nil {
			t.Fatalf("unable to create temp directory: %v", err)
		}
		defer os.RemoveAll(tempTestDirBob)

		walletType := walletDriver.WalletType
		switch walletType {
		case "btcwallet":
			aliceChainRPC, err := chain.NewRPCClient(netParams,
				rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
				rpcConfig.Certificates, false, 20)
			if err != nil {
				t.Fatalf("unable to make chain rpc: %v", err)
			}
			aliceWalletConfig := &btcwallet.Config{
				PrivatePass:  []byte("alice-pass"),
				HdSeed:       aliceHDSeed[:],
				DataDir:      tempTestDirAlice,
				NetParams:    netParams,
				ChainSource:  aliceChainRPC,
				FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
			}
			aliceWalletController, err = walletDriver.New(aliceWalletConfig)
			if err != nil {
				t.Fatalf("unable to create btcwallet: %v", err)
			}
			aliceSigner = aliceWalletController.(*btcwallet.BtcWallet)

			bobChainRPC, err := chain.NewRPCClient(netParams,
				rpcConfig.Host, rpcConfig.User, rpcConfig.Pass,
				rpcConfig.Certificates, false, 20)
			if err != nil {
				t.Fatalf("unable to make chain rpc: %v", err)
			}
			bobWalletConfig := &btcwallet.Config{
				PrivatePass:  []byte("bob-pass"),
				HdSeed:       bobHDSeed[:],
				DataDir:      tempTestDirBob,
				NetParams:    netParams,
				ChainSource:  bobChainRPC,
				FeeEstimator: lnwallet.StaticFeeEstimator{FeeRate: 250},
			}
			bobWalletController, err = walletDriver.New(bobWalletConfig)
			if err != nil {
				t.Fatalf("unable to create btcwallet: %v", err)
			}
			bobSigner = bobWalletController.(*btcwallet.BtcWallet)
			bio = bobWalletController.(*btcwallet.BtcWallet)
		default:
			// TODO(roasbeef): add neutrino case
			t.Fatalf("unknown wallet driver: %v", walletType)
		}

		// Funding via 20 outputs with 4BTC each.
		alice, err := createTestWallet(tempTestDirAlice, miningNode,
			netParams, chainNotifier, aliceWalletController,
			aliceSigner, bio)
		if err != nil {
			t.Fatalf("unable to create test ln wallet: %v", err)
		}
		defer alice.Shutdown()

		bob, err := createTestWallet(tempTestDirBob, miningNode,
			netParams, chainNotifier, bobWalletController,
			bobSigner, bio)
		if err != nil {
			t.Fatalf("unable to create test ln wallet: %v", err)
		}
		defer bob.Shutdown()

		// Both wallets should now have 80BTC available for spending.
		assertProperBalance(t, alice, 1, 80)
		assertProperBalance(t, bob, 1, 80)

		// Execute every test, clearing possibly mutated wallet state
		// after each step.
		for _, walletTest := range walletTests {
			testName := fmt.Sprintf("%v:%v", walletType,
				walletTest.name)
			success := t.Run(testName, func(t *testing.T) {
				walletTest.test(miningNode, alice, bob, t)
			})
			if !success {
				break
			}

			// TODO(roasbeef): possible reset mining node's
			// chainstate to initial level, cleanly wipe buckets
			if err := clearWalletStates(alice, bob); err != nil &&
				err != bolt.ErrBucketNotFound {
				t.Fatalf("unable to wipe wallet state: %v", err)
			}
		}
	}
}