package chainntnfs_test

import (
	"sync"
	"testing"

	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd/chainntnfs"
)

var zeroHash chainhash.Hash

type mockHintCache struct {
	mu         sync.Mutex
	confHints  map[chainhash.Hash]uint32
	spendHints map[wire.OutPoint]uint32
}

var _ chainntnfs.SpendHintCache = (*mockHintCache)(nil)
var _ chainntnfs.ConfirmHintCache = (*mockHintCache)(nil)

func (c *mockHintCache) CommitSpendHint(heightHint uint32, ops ...wire.OutPoint) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, op := range ops {
		c.spendHints[op] = heightHint
	}

	return nil
}

func (c *mockHintCache) QuerySpendHint(op wire.OutPoint) (uint32, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	hint, ok := c.spendHints[op]
	if !ok {
		return 0, chainntnfs.ErrSpendHintNotFound
	}

	return hint, nil
}

func (c *mockHintCache) PurgeSpendHint(ops ...wire.OutPoint) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, op := range ops {
		delete(c.spendHints, op)
	}

	return nil
}

func (c *mockHintCache) CommitConfirmHint(heightHint uint32, txids ...chainhash.Hash) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, txid := range txids {
		c.confHints[txid] = heightHint
	}

	return nil
}

func (c *mockHintCache) QueryConfirmHint(txid chainhash.Hash) (uint32, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	hint, ok := c.confHints[txid]
	if !ok {
		return 0, chainntnfs.ErrConfirmHintNotFound
	}

	return hint, nil
}

func (c *mockHintCache) PurgeConfirmHint(txids ...chainhash.Hash) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, txid := range txids {
		delete(c.confHints, txid)
	}

	return nil
}

func newMockHintCache() *mockHintCache {
	return &mockHintCache{
		confHints:  make(map[chainhash.Hash]uint32),
		spendHints: make(map[wire.OutPoint]uint32),
	}
}

// TestTxConfFutureDispatch tests that the TxConfNotifier dispatches
// registered notifications when the transaction confirms after registration.
func TestTxConfFutureDispatch(t *testing.T) {
	t.Parallel()

	const (
		tx1NumConfs uint32 = 1
		tx2NumConfs uint32 = 2
	)

	var (
		tx1 = wire.MsgTx{Version: 1}
		tx2 = wire.MsgTx{Version: 2}
		tx3 = wire.MsgTx{Version: 3}
	)

	hintCache := newMockHintCache()
	txConfNotifier := chainntnfs.NewTxConfNotifier(10, 100, hintCache)

	// Create the test transactions and register them with the
	// TxConfNotifier before including them in a block to receive future
	// notifications.
	tx1Hash := tx1.TxHash()
	ntfn1 := chainntnfs.ConfNtfn{
		TxID:             &tx1Hash,
		NumConfirmations: tx1NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx1NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn1); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	tx2Hash := tx2.TxHash()
	ntfn2 := chainntnfs.ConfNtfn{
		TxID:             &tx2Hash,
		NumConfirmations: tx2NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx2NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn2); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// We should not receive any notifications from both transactions
	// since they have not been included in a block yet.
	select {
	case <-ntfn1.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx1")
	case txConf := <-ntfn1.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx1: %v", txConf)
	default:
	}

	select {
	case <-ntfn2.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx2")
	case txConf := <-ntfn2.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx2: %v", txConf)
	default:
	}

	// Include the transactions in a block and add it to the TxConfNotifier.
	// This should confirm tx1, but not tx2.
	block1 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx1, &tx2, &tx3},
	})

	err := txConfNotifier.ConnectTip(
		block1.Hash(), 11, block1.Transactions(),
	)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We should only receive one update for tx1 since it only requires
	// one confirmation and it already met it.
	select {
	case numConfsLeft := <-ntfn1.Event.Updates:
		const expected = 0
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx1 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx1")
	}

	// A confirmation notification for this tranaction should be dispatched,
	// as it only required one confirmation.
	select {
	case txConf := <-ntfn1.Event.Confirmed:
		expectedConf := chainntnfs.TxConfirmation{
			BlockHash:   block1.Hash(),
			BlockHeight: 11,
			TxIndex:     0,
		}
		assertEqualTxConf(t, txConf, &expectedConf)
	default:
		t.Fatalf("Expected confirmation for tx1")
	}

	// We should only receive one update for tx2 since it only has one
	// confirmation so far and it requires two.
	select {
	case numConfsLeft := <-ntfn2.Event.Updates:
		const expected = 1
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx2 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	// A confirmation notification for tx2 should not be dispatched yet, as
	// it requires one more confirmation.
	select {
	case txConf := <-ntfn2.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx2: %v", txConf)
	default:
	}

	// Create a new block and add it to the TxConfNotifier at the next
	// height. This should confirm tx2.
	block2 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx3},
	})

	err = txConfNotifier.ConnectTip(block2.Hash(), 12, block2.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We should not receive any event notifications for tx1 since it has
	// already been confirmed.
	select {
	case <-ntfn1.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx1")
	case txConf := <-ntfn1.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx1: %v", txConf)
	default:
	}

	// We should only receive one update since the last at the new height,
	// indicating how many confirmations are still left.
	select {
	case numConfsLeft := <-ntfn2.Event.Updates:
		const expected = 0
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx2 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	// A confirmation notification for tx2 should be dispatched, since it
	// now meets its required number of confirmations.
	select {
	case txConf := <-ntfn2.Event.Confirmed:
		expectedConf := chainntnfs.TxConfirmation{
			BlockHash:   block1.Hash(),
			BlockHeight: 11,
			TxIndex:     1,
		}
		assertEqualTxConf(t, txConf, &expectedConf)
	default:
		t.Fatalf("Expected confirmation for tx2")
	}
}

// TestTxConfHistoricalDispatch tests that the TxConfNotifier dispatches
// registered notifications when the transaction is confirmed before
// registration.
func TestTxConfHistoricalDispatch(t *testing.T) {
	t.Parallel()

	const (
		tx1NumConfs uint32 = 1
		tx2NumConfs uint32 = 3
	)

	var (
		tx1 = wire.MsgTx{Version: 1}
		tx2 = wire.MsgTx{Version: 2}
		tx3 = wire.MsgTx{Version: 3}
	)

	hintCache := newMockHintCache()
	txConfNotifier := chainntnfs.NewTxConfNotifier(10, 100, hintCache)

	// Create the test transactions at a height before the TxConfNotifier's
	// starting height so that they are confirmed once registering them.
	tx1Hash := tx1.TxHash()
	ntfn1 := chainntnfs.ConfNtfn{
		ConfID:           0,
		TxID:             &tx1Hash,
		NumConfirmations: tx1NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx1NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn1); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	tx2Hash := tx2.TxHash()
	ntfn2 := chainntnfs.ConfNtfn{
		ConfID:           1,
		TxID:             &tx2Hash,
		NumConfirmations: tx2NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx2NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn2); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// Update tx1 with its confirmation details. We should only receive one
	// update since it only requires one confirmation and it already met it.
	txConf1 := chainntnfs.TxConfirmation{
		BlockHash:   &zeroHash,
		BlockHeight: 9,
		TxIndex:     1,
	}
	err := txConfNotifier.UpdateConfDetails(tx1Hash, ntfn1.ConfID, &txConf1)
	if err != nil {
		t.Fatalf("unable to update conf details: %v", err)
	}
	select {
	case numConfsLeft := <-ntfn1.Event.Updates:
		const expected = 0
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx1 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx1")
	}

	// A confirmation notification for tx1 should also be dispatched.
	select {
	case txConf := <-ntfn1.Event.Confirmed:
		assertEqualTxConf(t, txConf, &txConf1)
	default:
		t.Fatalf("Expected confirmation for tx1")
	}

	// Update tx2 with its confirmation details. This should not trigger a
	// confirmation notification since it hasn't reached its required number
	// of confirmations, but we should receive a confirmation update
	// indicating how many confirmation are left.
	txConf2 := chainntnfs.TxConfirmation{
		BlockHash:   &zeroHash,
		BlockHeight: 9,
		TxIndex:     2,
	}
	err = txConfNotifier.UpdateConfDetails(tx2Hash, ntfn2.ConfID, &txConf2)
	if err != nil {
		t.Fatalf("unable to update conf details: %v", err)
	}
	select {
	case numConfsLeft := <-ntfn2.Event.Updates:
		const expected = 1
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx2 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	select {
	case txConf := <-ntfn2.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx2: %v", txConf)
	default:
	}

	// Create a new block and add it to the TxConfNotifier at the next
	// height. This should confirm tx2.
	block := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx3},
	})

	err = txConfNotifier.ConnectTip(block.Hash(), 11, block.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We should not receive any event notifications for tx1 since it has
	// already been confirmed.
	select {
	case <-ntfn1.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx1")
	case txConf := <-ntfn1.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx1: %v", txConf)
	default:
	}

	// We should only receive one update for tx2 since the last one,
	// indicating how many confirmations are still left.
	select {
	case numConfsLeft := <-ntfn2.Event.Updates:
		const expected = 0
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx2 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	// A confirmation notification for tx2 should be dispatched, as it met
	// its required number of confirmations.
	select {
	case txConf := <-ntfn2.Event.Confirmed:
		assertEqualTxConf(t, txConf, &txConf2)
	default:
		t.Fatalf("Expected confirmation for tx2")
	}
}

// TestTxConfChainReorg tests that TxConfNotifier dispatches Confirmed and
// NegativeConf notifications appropriately when there is a chain
// reorganization.
func TestTxConfChainReorg(t *testing.T) {
	t.Parallel()

	const (
		tx1NumConfs uint32 = 2
		tx2NumConfs uint32 = 1
		tx3NumConfs uint32 = 2
	)

	var (
		tx1 = wire.MsgTx{Version: 1}
		tx2 = wire.MsgTx{Version: 2}
		tx3 = wire.MsgTx{Version: 3}
	)

	hintCache := newMockHintCache()
	txConfNotifier := chainntnfs.NewTxConfNotifier(7, 100, hintCache)

	// Tx 1 will be confirmed in block 9 and requires 2 confs.
	tx1Hash := tx1.TxHash()
	ntfn1 := chainntnfs.ConfNtfn{
		TxID:             &tx1Hash,
		NumConfirmations: tx1NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx1NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn1); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// Tx 2 will be confirmed in block 10 and requires 1 conf.
	tx2Hash := tx2.TxHash()
	ntfn2 := chainntnfs.ConfNtfn{
		TxID:             &tx2Hash,
		NumConfirmations: tx2NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx2NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn2); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// Tx 3 will be confirmed in block 10 and requires 2 confs.
	tx3Hash := tx3.TxHash()
	ntfn3 := chainntnfs.ConfNtfn{
		TxID:             &tx3Hash,
		NumConfirmations: tx3NumConfs,
		Event:            chainntnfs.NewConfirmationEvent(tx3NumConfs),
	}
	if err := txConfNotifier.Register(&ntfn3); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// Sync chain to block 10. Txs 1 & 2 should be confirmed.
	block1 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx1},
	})
	err := txConfNotifier.ConnectTip(nil, 8, block1.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}
	err = txConfNotifier.ConnectTip(nil, 9, nil)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	block2 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx2, &tx3},
	})
	err = txConfNotifier.ConnectTip(nil, 10, block2.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We should receive two updates for tx1 since it requires two
	// confirmations and it has already met them.
	for i := 0; i < 2; i++ {
		select {
		case <-ntfn1.Event.Updates:
		default:
			t.Fatal("Expected confirmation update for tx1")
		}
	}

	// A confirmation notification for tx1 should be dispatched, as it met
	// its required number of confirmations.
	select {
	case <-ntfn1.Event.Confirmed:
	default:
		t.Fatalf("Expected confirmation for tx1")
	}

	// We should only receive one update for tx2 since it only requires
	// one confirmation and it already met it.
	select {
	case <-ntfn2.Event.Updates:
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	// A confirmation notification for tx2 should be dispatched, as it met
	// its required number of confirmations.
	select {
	case <-ntfn2.Event.Confirmed:
	default:
		t.Fatalf("Expected confirmation for tx2")
	}

	// We should only receive one update for tx3 since it only has one
	// confirmation so far and it requires two.
	select {
	case <-ntfn3.Event.Updates:
	default:
		t.Fatal("Expected confirmation update for tx3")
	}

	// A confirmation notification for tx3 should not be dispatched yet, as
	// it requires one more confirmation.
	select {
	case txConf := <-ntfn3.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx3: %v", txConf)
	default:
	}

	// The block that included tx2 and tx3 is disconnected and two next
	// blocks without them are connected.
	err = txConfNotifier.DisconnectTip(10)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	err = txConfNotifier.ConnectTip(nil, 10, nil)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	err = txConfNotifier.ConnectTip(nil, 11, nil)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	select {
	case reorgDepth := <-ntfn2.Event.NegativeConf:
		if reorgDepth != 1 {
			t.Fatalf("Incorrect value for negative conf notification: "+
				"expected %d, got %d", 1, reorgDepth)
		}
	default:
		t.Fatalf("Expected negative conf notification for tx1")
	}

	// We should not receive any event notifications from all of the
	// transactions because tx1 has already been confirmed and tx2 and tx3
	// have not been included in the chain since the reorg.
	select {
	case <-ntfn1.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx1")
	case txConf := <-ntfn1.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx1: %v", txConf)
	default:
	}

	select {
	case <-ntfn2.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx2")
	case txConf := <-ntfn2.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx2: %v", txConf)
	default:
	}

	select {
	case <-ntfn3.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx3")
	case txConf := <-ntfn3.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx3: %v", txConf)
	default:
	}

	// Now transactions 2 & 3 are re-included in a new block.
	block3 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx2, &tx3},
	})
	block4 := btcutil.NewBlock(&wire.MsgBlock{})

	err = txConfNotifier.ConnectTip(block3.Hash(), 12, block3.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	err = txConfNotifier.ConnectTip(block4.Hash(), 13, block4.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We should only receive one update for tx2 since it only requires
	// one confirmation and it already met it.
	select {
	case numConfsLeft := <-ntfn2.Event.Updates:
		const expected = 0
		if numConfsLeft != expected {
			t.Fatalf("Received incorrect confirmation update: tx2 "+
				"expected %d confirmations left, got %d",
				expected, numConfsLeft)
		}
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	// A confirmation notification for tx2 should be dispatched, as it met
	// its required number of confirmations.
	select {
	case txConf := <-ntfn2.Event.Confirmed:
		expectedConf := chainntnfs.TxConfirmation{
			BlockHash:   block3.Hash(),
			BlockHeight: 12,
			TxIndex:     0,
		}
		assertEqualTxConf(t, txConf, &expectedConf)
	default:
		t.Fatalf("Expected confirmation for tx2")
	}

	// We should receive two updates for tx3 since it requires two
	// confirmations and it has already met them.
	for i := uint32(1); i <= 2; i++ {
		select {
		case numConfsLeft := <-ntfn3.Event.Updates:
			expected := tx3NumConfs - i
			if numConfsLeft != expected {
				t.Fatalf("Received incorrect confirmation update: tx3 "+
					"expected %d confirmations left, got %d",
					expected, numConfsLeft)
			}
		default:
			t.Fatal("Expected confirmation update for tx2")
		}
	}

	// A confirmation notification for tx3 should be dispatched, as it met
	// its required number of confirmations.
	select {
	case txConf := <-ntfn3.Event.Confirmed:
		expectedConf := chainntnfs.TxConfirmation{
			BlockHash:   block3.Hash(),
			BlockHeight: 12,
			TxIndex:     1,
		}
		assertEqualTxConf(t, txConf, &expectedConf)
	default:
		t.Fatalf("Expected confirmation for tx3")
	}
}

// TestTxConfHeightHintCache ensures that the height hints for transactions are
// kept track of correctly with each new block connected/disconnected.
func TestTxConfHeightHintCache(t *testing.T) {
	t.Parallel()

	const (
		startingHeight = 10
		tx1Height      = 11
		tx2Height      = 12
	)

	// Initialize our TxConfNotifier instance backed by a height hint cache.
	hintCache := newMockHintCache()
	txConfNotifier := chainntnfs.NewTxConfNotifier(
		startingHeight, 100, hintCache,
	)

	// Create two test transactions and register them for notifications.
	tx1 := wire.MsgTx{Version: 1}
	tx1Hash := tx1.TxHash()
	ntfn1 := &chainntnfs.ConfNtfn{
		TxID:             &tx1Hash,
		NumConfirmations: 1,
		Event:            chainntnfs.NewConfirmationEvent(1),
	}

	tx2 := wire.MsgTx{Version: 2}
	tx2Hash := tx2.TxHash()
	ntfn2 := &chainntnfs.ConfNtfn{
		TxID:             &tx2Hash,
		NumConfirmations: 2,
		Event:            chainntnfs.NewConfirmationEvent(2),
	}

	if err := txConfNotifier.Register(ntfn1); err != nil {
		t.Fatalf("unable to register tx1: %v", err)
	}
	if err := txConfNotifier.Register(ntfn2); err != nil {
		t.Fatalf("unable to register tx2: %v", err)
	}

	// Both transactions should have a height hint of the starting height
	// due to registering notifications for them.
	hint, err := hintCache.QueryConfirmHint(tx1Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != startingHeight {
		t.Fatalf("expected hint %d, got %d", startingHeight, hint)
	}

	hint, err = hintCache.QueryConfirmHint(tx2Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != startingHeight {
		t.Fatalf("expected hint %d, got %d", startingHeight, hint)
	}

	// Create a new block that will include the first transaction and extend
	// the chain.
	block1 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx1},
	})

	err = txConfNotifier.ConnectTip(
		block1.Hash(), tx1Height, block1.Transactions(),
	)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// The height hint for the first transaction should now be updated to
	// reflect its confirmation.
	hint, err = hintCache.QueryConfirmHint(tx1Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != tx1Height {
		t.Fatalf("expected hint %d, got %d", tx1Height, hint)
	}

	// The height hint for the second transaction should also be updated due
	// to it still being unconfirmed.
	hint, err = hintCache.QueryConfirmHint(tx2Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != tx1Height {
		t.Fatalf("expected hint %d, got %d", tx1Height, hint)
	}

	// Now, we'll create another block that will include the second
	// transaction and extend the chain.
	block2 := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx2},
	})

	err = txConfNotifier.ConnectTip(
		block2.Hash(), tx2Height, block2.Transactions(),
	)
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// The height hint for the first transaction should remain the same.
	hint, err = hintCache.QueryConfirmHint(tx1Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != tx1Height {
		t.Fatalf("expected hint %d, got %d", tx1Height, hint)
	}

	// The height hint for the second transaction should now be updated to
	// reflect its confirmation.
	hint, err = hintCache.QueryConfirmHint(tx2Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != tx2Height {
		t.Fatalf("expected hint %d, got %d", tx2Height, hint)
	}

	// Now, we'll attempt do disconnect the last block in order to simulate
	// a chain reorg.
	if err := txConfNotifier.DisconnectTip(tx2Height); err != nil {
		t.Fatalf("Failed to disconnect block: %v", err)
	}

	// This should update the second transaction's height hint within the
	// cache to the previous height.
	hint, err = hintCache.QueryConfirmHint(tx2Hash)
	if err != nil {
		t.Fatalf("unable to query for hint: %v", err)
	}
	if hint != tx1Height {
		t.Fatalf("expected hint %d, got %d", tx1Height, hint)
	}
}

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

	var (
		tx1 = wire.MsgTx{Version: 1}
		tx2 = wire.MsgTx{Version: 2}
	)

	hintCache := newMockHintCache()
	txConfNotifier := chainntnfs.NewTxConfNotifier(10, 100, hintCache)

	// Create the test transactions and register them with the
	// TxConfNotifier to receive notifications.
	tx1Hash := tx1.TxHash()
	ntfn1 := chainntnfs.ConfNtfn{
		TxID:             &tx1Hash,
		NumConfirmations: 1,
		Event:            chainntnfs.NewConfirmationEvent(1),
	}
	if err := txConfNotifier.Register(&ntfn1); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	tx2Hash := tx2.TxHash()
	ntfn2 := chainntnfs.ConfNtfn{
		TxID:             &tx2Hash,
		NumConfirmations: 2,
		Event:            chainntnfs.NewConfirmationEvent(2),
	}
	if err := txConfNotifier.Register(&ntfn2); err != nil {
		t.Fatalf("unable to register ntfn: %v", err)
	}

	// Include the transactions in a block and add it to the TxConfNotifier.
	// This should confirm tx1, but not tx2.
	block := btcutil.NewBlock(&wire.MsgBlock{
		Transactions: []*wire.MsgTx{&tx1, &tx2},
	})

	err := txConfNotifier.ConnectTip(block.Hash(), 11, block.Transactions())
	if err != nil {
		t.Fatalf("Failed to connect block: %v", err)
	}

	// We do not care about the correctness of the notifications since they
	// are tested in other methods, but we'll still attempt to retrieve them
	// for the sake of not being able to later once the notification
	// channels are closed.
	select {
	case <-ntfn1.Event.Updates:
	default:
		t.Fatal("Expected confirmation update for tx1")
	}

	select {
	case <-ntfn1.Event.Confirmed:
	default:
		t.Fatalf("Expected confirmation for tx1")
	}

	select {
	case <-ntfn2.Event.Updates:
	default:
		t.Fatal("Expected confirmation update for tx2")
	}

	select {
	case txConf := <-ntfn2.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx2: %v", txConf)
	default:
	}

	// The notification channels should be closed for notifications that
	// have not been dispatched yet, so we should not expect to receive any
	// more updates.
	txConfNotifier.TearDown()

	// tx1 should not receive any more updates because it has already been
	// confirmed and the TxConfNotifier has been shut down.
	select {
	case <-ntfn1.Event.Updates:
		t.Fatal("Received unexpected confirmation update for tx1")
	case txConf := <-ntfn1.Event.Confirmed:
		t.Fatalf("Received unexpected confirmation for tx1: %v", txConf)
	default:
	}

	// tx2 should not receive any more updates after the notifications
	// channels have been closed and the TxConfNotifier shut down.
	select {
	case _, more := <-ntfn2.Event.Updates:
		if more {
			t.Fatal("Expected closed Updates channel for tx2")
		}
	case _, more := <-ntfn2.Event.Confirmed:
		if more {
			t.Fatalf("Expected closed Confirmed channel for tx2")
		}
	default:
		t.Fatalf("Expected closed notification channels for tx2")
	}
}

func assertEqualTxConf(t *testing.T,
	actualConf, expectedConf *chainntnfs.TxConfirmation) {

	if actualConf.BlockHeight != expectedConf.BlockHeight {
		t.Fatalf("Incorrect block height in confirmation details: "+
			"expected %d, got %d",
			expectedConf.BlockHeight, actualConf.BlockHeight)
	}
	if !actualConf.BlockHash.IsEqual(expectedConf.BlockHash) {
		t.Fatalf("Incorrect block hash in confirmation details: "+
			"expected %d, got %d", expectedConf.BlockHash, actualConf.BlockHash)
	}
	if actualConf.TxIndex != expectedConf.TxIndex {
		t.Fatalf("Incorrect tx index in confirmation details: "+
			"expected %d, got %d", expectedConf.TxIndex, actualConf.TxIndex)
	}
}
