chainntnfs/txnotifier_test: extend tests to handle spend notifications
This commit is contained in:
parent
2935392f51
commit
1fe3d59836
@ -10,7 +10,10 @@ import (
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
)
|
||||
|
||||
var zeroHash chainhash.Hash
|
||||
var (
|
||||
zeroHash chainhash.Hash
|
||||
zeroOutPoint wire.OutPoint
|
||||
)
|
||||
|
||||
type mockHintCache struct {
|
||||
mu sync.Mutex
|
||||
@ -96,9 +99,9 @@ func newMockHintCache() *mockHintCache {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxConfFutureDispatch tests that the TxNotifier dispatches registered
|
||||
// notifications when the transaction confirms after registration.
|
||||
func TestTxConfFutureDispatch(t *testing.T) {
|
||||
// TestTxNotifierFutureConfDispatch tests that the TxNotifier dispatches
|
||||
// registered notifications when a transaction confirms after registration.
|
||||
func TestTxNotifierFutureConfDispatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
@ -113,7 +116,7 @@ func TestTxConfFutureDispatch(t *testing.T) {
|
||||
)
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache)
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache, hintCache)
|
||||
|
||||
// Create the test transactions and register them with the TxNotifier
|
||||
// before including them in a block to receive future
|
||||
@ -192,7 +195,7 @@ func TestTxConfFutureDispatch(t *testing.T) {
|
||||
BlockHeight: 11,
|
||||
TxIndex: 0,
|
||||
}
|
||||
assertEqualTxConf(t, txConf, &expectedConf)
|
||||
assertConfDetails(t, txConf, &expectedConf)
|
||||
default:
|
||||
t.Fatalf("Expected confirmation for tx1")
|
||||
}
|
||||
@ -263,15 +266,16 @@ func TestTxConfFutureDispatch(t *testing.T) {
|
||||
BlockHeight: 11,
|
||||
TxIndex: 1,
|
||||
}
|
||||
assertEqualTxConf(t, txConf, &expectedConf)
|
||||
assertConfDetails(t, txConf, &expectedConf)
|
||||
default:
|
||||
t.Fatalf("Expected confirmation for tx2")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxConfHistoricalDispatch tests that the TxNotifier dispatches registered
|
||||
// notifications when the transaction is confirmed before registration.
|
||||
func TestTxConfHistoricalDispatch(t *testing.T) {
|
||||
// TestTxNotifierHistoricalConfDispatch tests that the TxNotifier dispatches
|
||||
// registered notifications when the transaction is confirmed before
|
||||
// registration.
|
||||
func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
@ -286,7 +290,7 @@ func TestTxConfHistoricalDispatch(t *testing.T) {
|
||||
)
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache)
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache, hintCache)
|
||||
|
||||
// Create the test transactions at a height before the TxNotifier's
|
||||
// starting height so that they are confirmed once registering them.
|
||||
@ -338,7 +342,7 @@ func TestTxConfHistoricalDispatch(t *testing.T) {
|
||||
// A confirmation notification for tx1 should also be dispatched.
|
||||
select {
|
||||
case txConf := <-ntfn1.Event.Confirmed:
|
||||
assertEqualTxConf(t, txConf, &txConf1)
|
||||
assertConfDetails(t, txConf, &txConf1)
|
||||
default:
|
||||
t.Fatalf("Expected confirmation for tx1")
|
||||
}
|
||||
@ -413,16 +417,547 @@ func TestTxConfHistoricalDispatch(t *testing.T) {
|
||||
// its required number of confirmations.
|
||||
select {
|
||||
case txConf := <-ntfn2.Event.Confirmed:
|
||||
assertEqualTxConf(t, txConf, &txConf2)
|
||||
assertConfDetails(t, txConf, &txConf2)
|
||||
default:
|
||||
t.Fatalf("Expected confirmation for tx2")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxConfChainReorg tests that TxNotifier dispatches Confirmed and
|
||||
// NegativeConf notifications appropriately when there is a chain
|
||||
// reorganization.
|
||||
func TestTxConfChainReorg(t *testing.T) {
|
||||
// TestTxNotifierFutureSpendDispatch tests that the TxNotifier dispatches
|
||||
// registered notifications when an outpoint is spent after registration.
|
||||
func TestTxNotifierFutureSpendDispatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache, hintCache)
|
||||
|
||||
// We'll start off by registering for a spend notification of an
|
||||
// outpoint.
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
// We should not receive a notification as the outpoint has not been
|
||||
// spent yet.
|
||||
select {
|
||||
case <-ntfn.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// Construct the details of the spending transaction of the outpoint
|
||||
// above. We'll include it in the next block, which should trigger a
|
||||
// spend notification.
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
spendTx.AddTxIn(&wire.TxIn{PreviousOutPoint: zeroOutPoint})
|
||||
spendTxHash := spendTx.TxHash()
|
||||
block := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx},
|
||||
})
|
||||
err := n.ConnectTip(block.Hash(), 11, block.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
expectedSpendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &ntfn.OutPoint,
|
||||
SpenderTxHash: &spendTxHash,
|
||||
SpendingTx: spendTx,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: 11,
|
||||
}
|
||||
|
||||
// Ensure that the details of the notification match as expected.
|
||||
select {
|
||||
case spendDetails := <-ntfn.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails)
|
||||
default:
|
||||
t.Fatal("expected to receive spend details")
|
||||
}
|
||||
|
||||
// Finally, we'll ensure that if the spending transaction has also been
|
||||
// spent, then we don't receive another spend notification.
|
||||
prevOut := wire.OutPoint{Hash: spendTxHash, Index: 0}
|
||||
spendOfSpend := wire.NewMsgTx(2)
|
||||
spendOfSpend.AddTxIn(&wire.TxIn{PreviousOutPoint: prevOut})
|
||||
block = btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendOfSpend},
|
||||
})
|
||||
err = n.ConnectTip(block.Hash(), 12, block.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ntfn.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierHistoricalSpendDispatch tests that the TxNotifier dispatches
|
||||
// registered notifications when an outpoint is spent before registration.
|
||||
func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const startingHeight = 10
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// We'll start by constructing the spending details of the outpoint
|
||||
// below.
|
||||
spentOutpoint := zeroOutPoint
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
spendTx.AddTxIn(&wire.TxIn{PreviousOutPoint: zeroOutPoint})
|
||||
spendTxHash := spendTx.TxHash()
|
||||
|
||||
expectedSpendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &spentOutpoint,
|
||||
SpenderTxHash: &spendTxHash,
|
||||
SpendingTx: spendTx,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight - 1,
|
||||
}
|
||||
|
||||
// We'll register for a spend notification of the outpoint and ensure
|
||||
// that a notification isn't dispatched.
|
||||
ntfn := &chainntnfs.SpendNtfn{
|
||||
OutPoint: spentOutpoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ntfn.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// Because we're interested in testing the case of a historical spend,
|
||||
// we'll hand off the spending details of the outpoint to the notifier
|
||||
// as it is not possible for it to view historical events in the chain.
|
||||
// By doing this, we replicate the functionality of the ChainNotifier.
|
||||
err := n.UpdateSpendDetails(ntfn.OutPoint, expectedSpendDetails)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to update spend details: %v", err)
|
||||
}
|
||||
|
||||
// Now that we have the spending details, we should receive a spend
|
||||
// notification. We'll ensure that the details match as intended.
|
||||
select {
|
||||
case spendDetails := <-ntfn.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails)
|
||||
default:
|
||||
t.Fatalf("expected to receive spend details")
|
||||
}
|
||||
|
||||
// Finally, we'll ensure that if the spending transaction has also been
|
||||
// spent, then we don't receive another spend notification.
|
||||
prevOut := wire.OutPoint{Hash: spendTxHash, Index: 0}
|
||||
spendOfSpend := wire.NewMsgTx(2)
|
||||
spendOfSpend.AddTxIn(&wire.TxIn{PreviousOutPoint: prevOut})
|
||||
block := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendOfSpend},
|
||||
})
|
||||
err = n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ntfn.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierMultipleHistoricalRescans ensures that we don't attempt to
|
||||
// request multiple historical confirmation rescans per transactions.
|
||||
func TestTxNotifierMultipleHistoricalConfRescans(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const startingHeight = 10
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// The first registration for a transaction in the notifier should
|
||||
// request a historical confirmation rescan as it does not have a
|
||||
// historical view of the chain.
|
||||
confNtfn1 := &chainntnfs.ConfNtfn{
|
||||
ConfID: 0,
|
||||
TxID: &zeroHash,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
historicalConfDispatch1, err := n.RegisterConf(confNtfn1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalConfDispatch1 == nil {
|
||||
t.Fatal("expected to receive historical dispatch request")
|
||||
}
|
||||
|
||||
// We'll register another confirmation notification for the same
|
||||
// transaction. This should not request a historical confirmation rescan
|
||||
// since the first one is still pending.
|
||||
confNtfn2 := &chainntnfs.ConfNtfn{
|
||||
ConfID: 1,
|
||||
TxID: &zeroHash,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
historicalConfDispatch2, err := n.RegisterConf(confNtfn2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalConfDispatch2 != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
|
||||
// Finally, we'll mark the ongoing historical rescan as complete and
|
||||
// register another notification. We should also expect not to see a
|
||||
// historical rescan request since the confirmation details should be
|
||||
// cached.
|
||||
confDetails := &chainntnfs.TxConfirmation{
|
||||
BlockHeight: startingHeight - 1,
|
||||
}
|
||||
if err := n.UpdateConfDetails(*confNtfn2.TxID, confDetails); err != nil {
|
||||
t.Fatalf("unable to update conf details: %v", err)
|
||||
}
|
||||
|
||||
confNtfn3 := &chainntnfs.ConfNtfn{
|
||||
ConfID: 2,
|
||||
TxID: &zeroHash,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
historicalConfDispatch3, err := n.RegisterConf(confNtfn3)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalConfDispatch3 != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierMultipleHistoricalRescans ensures that we don't attempt to
|
||||
// request multiple historical spend rescans per outpoints.
|
||||
func TestTxNotifierMultipleHistoricalSpendRescans(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const startingHeight = 10
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// The first registration for an outpoint in the notifier should request
|
||||
// a historical spend rescan as it does not have a historical view of
|
||||
// the chain.
|
||||
ntfn1 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 0,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
historicalDispatch1, err := n.RegisterSpend(ntfn1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalDispatch1 == nil {
|
||||
t.Fatal("expected to receive historical dispatch request")
|
||||
}
|
||||
|
||||
// We'll register another spend notification for the same outpoint. This
|
||||
// should not request a historical spend rescan since the first one is
|
||||
// still pending.
|
||||
ntfn2 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 1,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
historicalDispatch2, err := n.RegisterSpend(ntfn2)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalDispatch2 != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
|
||||
// Finally, we'll mark the ongoing historical rescan as complete and
|
||||
// register another notification. We should also expect not to see a
|
||||
// historical rescan request since the confirmation details should be
|
||||
// cached.
|
||||
spendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &ntfn2.OutPoint,
|
||||
SpenderTxHash: &zeroHash,
|
||||
SpendingTx: wire.NewMsgTx(2),
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight - 1,
|
||||
}
|
||||
err = n.UpdateSpendDetails(ntfn2.OutPoint, spendDetails)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to update spend details: %v", err)
|
||||
}
|
||||
|
||||
ntfn3 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 2,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
historicalDispatch3, err := n.RegisterSpend(ntfn3)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalDispatch3 != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierMultipleHistoricalNtfns ensures that the TxNotifier will only
|
||||
// request one rescan for a transaction/outpoint when having multiple client
|
||||
// registrations. Once the rescan has completed and retrieved the
|
||||
// confirmation/spend details, a notification should be dispatched to _all_
|
||||
// clients.
|
||||
func TestTxNotifierMultipleHistoricalNtfns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
numNtfns = 5
|
||||
startingHeight = 10
|
||||
)
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// We'll start off by registered 5 clients for a confirmation
|
||||
// notification on the same transaction.
|
||||
confNtfns := make([]*chainntnfs.ConfNtfn, numNtfns)
|
||||
for i := uint64(0); i < numNtfns; i++ {
|
||||
confNtfns[i] = &chainntnfs.ConfNtfn{
|
||||
ConfID: i,
|
||||
TxID: &zeroHash,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
if _, err := n.RegisterConf(confNtfns[i]); err != nil {
|
||||
t.Fatalf("unable to register conf ntfn #%d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure none of them have received the confirmation details.
|
||||
for i, ntfn := range confNtfns {
|
||||
select {
|
||||
case <-ntfn.Event.Confirmed:
|
||||
t.Fatalf("request #%d received unexpected confirmation "+
|
||||
"notification", i)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// We'll assume a historical rescan was dispatched and found the
|
||||
// following confirmation details. We'll let the notifier know so that
|
||||
// it can stop watching at tip.
|
||||
expectedConfDetails := &chainntnfs.TxConfirmation{
|
||||
BlockHeight: startingHeight - 1,
|
||||
}
|
||||
err := n.UpdateConfDetails(*confNtfns[0].TxID, expectedConfDetails)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to update conf details: %v", err)
|
||||
}
|
||||
|
||||
// With the confirmation details retrieved, each client should now have
|
||||
// been notified of the confirmation.
|
||||
for i, ntfn := range confNtfns {
|
||||
select {
|
||||
case confDetails := <-ntfn.Event.Confirmed:
|
||||
assertConfDetails(t, confDetails, expectedConfDetails)
|
||||
default:
|
||||
t.Fatalf("request #%d expected to received "+
|
||||
"confirmation notification", i)
|
||||
}
|
||||
}
|
||||
|
||||
// In order to ensure that the confirmation details are properly cached,
|
||||
// we'll register another client for the same transaction. We should not
|
||||
// see a historical rescan request and the confirmation notification
|
||||
// should come through immediately.
|
||||
extraConfNtfn := &chainntnfs.ConfNtfn{
|
||||
ConfID: numNtfns + 1,
|
||||
TxID: &zeroHash,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
historicalConfRescan, err := n.RegisterConf(extraConfNtfn)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register conf ntfn: %v", err)
|
||||
}
|
||||
if historicalConfRescan != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
|
||||
select {
|
||||
case confDetails := <-extraConfNtfn.Event.Confirmed:
|
||||
assertConfDetails(t, confDetails, expectedConfDetails)
|
||||
default:
|
||||
t.Fatal("expected to receive spend notification")
|
||||
}
|
||||
|
||||
// Similarly, we'll do the same thing but for spend notifications.
|
||||
spendNtfns := make([]*chainntnfs.SpendNtfn, numNtfns)
|
||||
for i := uint64(0); i < numNtfns; i++ {
|
||||
spendNtfns[i] = &chainntnfs.SpendNtfn{
|
||||
SpendID: i,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(spendNtfns[i]); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn #%d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure none of them have received the spend details.
|
||||
for i, ntfn := range spendNtfns {
|
||||
select {
|
||||
case <-ntfn.Event.Spend:
|
||||
t.Fatalf("request #%d received unexpected spend "+
|
||||
"notification", i)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// We'll assume a historical rescan was dispatched and found the
|
||||
// following spend details. We'll let the notifier know so that it can
|
||||
// stop watching at tip.
|
||||
expectedSpendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &spendNtfns[0].OutPoint,
|
||||
SpenderTxHash: &zeroHash,
|
||||
SpendingTx: wire.NewMsgTx(2),
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight - 1,
|
||||
}
|
||||
err = n.UpdateSpendDetails(spendNtfns[0].OutPoint, expectedSpendDetails)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to update spend details: %v", err)
|
||||
}
|
||||
|
||||
// With the spend details retrieved, each client should now have been
|
||||
// notified of the spend.
|
||||
for i, ntfn := range spendNtfns {
|
||||
select {
|
||||
case spendDetails := <-ntfn.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails)
|
||||
default:
|
||||
t.Fatalf("request #%d expected to received spend "+
|
||||
"notification", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, in order to ensure that the spend details are properly
|
||||
// cached, we'll register another client for the same outpoint. We
|
||||
// should not see a historical rescan request and the spend notification
|
||||
// should come through immediately.
|
||||
extraSpendNtfn := &chainntnfs.SpendNtfn{
|
||||
SpendID: numNtfns + 1,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
historicalSpendRescan, err := n.RegisterSpend(extraSpendNtfn)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
if historicalSpendRescan != nil {
|
||||
t.Fatal("received unexpected historical rescan request")
|
||||
}
|
||||
|
||||
select {
|
||||
case spendDetails := <-extraSpendNtfn.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails)
|
||||
default:
|
||||
t.Fatal("expected to receive spend notification")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierCancelSpend ensures that a spend notification after a client
|
||||
// has canceled their intent to receive one.
|
||||
func TestTxNotifierCancelSpend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const startingHeight = 10
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// We'll register two notification requests. Only the second one will be
|
||||
// canceled.
|
||||
ntfn1 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 0,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn1); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
ntfn2 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 1,
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn2); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
// Construct the spending details of the outpoint and create a dummy
|
||||
// block containing it.
|
||||
spendTx := wire.NewMsgTx(2)
|
||||
spendTx.AddTxIn(&wire.TxIn{PreviousOutPoint: ntfn1.OutPoint})
|
||||
spendTxHash := spendTx.TxHash()
|
||||
expectedSpendDetails := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &ntfn1.OutPoint,
|
||||
SpenderTxHash: &spendTxHash,
|
||||
SpendingTx: spendTx,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight + 1,
|
||||
}
|
||||
|
||||
block := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx},
|
||||
})
|
||||
|
||||
// Before extending the notifier's tip with the dummy block above, we'll
|
||||
// cancel the second request.
|
||||
n.CancelSpend(ntfn2.OutPoint, ntfn2.SpendID)
|
||||
|
||||
err := n.ConnectTip(block.Hash(), startingHeight+1, block.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// The first request should still be active, so we should receive a
|
||||
// spend notification with the correct spending details.
|
||||
select {
|
||||
case spendDetails := <-ntfn1.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails)
|
||||
default:
|
||||
t.Fatalf("expected to receive spend notification")
|
||||
}
|
||||
|
||||
// The second one, however, should not have. The event's Spend channel
|
||||
// must have also been closed to indicate the caller that the TxNotifier
|
||||
// can no longer fulfill their canceled request.
|
||||
select {
|
||||
case _, ok := <-ntfn2.Event.Spend:
|
||||
if ok {
|
||||
t.Fatal("expected Spend channel to be closed")
|
||||
}
|
||||
default:
|
||||
t.Fatal("expected Spend channel to be closed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierConfReorg ensures that clients are notified of a reorg when a
|
||||
// transaction for which they registered a confirmation notification has been
|
||||
// reorged out of the chain.
|
||||
func TestTxNotifierConfReorg(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
@ -438,7 +973,7 @@ func TestTxConfChainReorg(t *testing.T) {
|
||||
)
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(7, 100, hintCache)
|
||||
n := chainntnfs.NewTxNotifier(7, 100, hintCache, hintCache)
|
||||
|
||||
// Tx 1 will be confirmed in block 9 and requires 2 confs.
|
||||
tx1Hash := tx1.TxHash()
|
||||
@ -649,7 +1184,7 @@ func TestTxConfChainReorg(t *testing.T) {
|
||||
BlockHeight: 12,
|
||||
TxIndex: 0,
|
||||
}
|
||||
assertEqualTxConf(t, txConf, &expectedConf)
|
||||
assertConfDetails(t, txConf, &expectedConf)
|
||||
default:
|
||||
t.Fatalf("Expected confirmation for tx2")
|
||||
}
|
||||
@ -679,18 +1214,221 @@ func TestTxConfChainReorg(t *testing.T) {
|
||||
BlockHeight: 12,
|
||||
TxIndex: 1,
|
||||
}
|
||||
assertEqualTxConf(t, txConf, &expectedConf)
|
||||
assertConfDetails(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. This test
|
||||
// also asserts that the height hints are not updated until the simulated
|
||||
// TestTxNotifierSpendReorg ensures that clients are notified of a reorg when
|
||||
// the spending transaction of an outpoint for which they registered a spend
|
||||
// notification for has been reorged out of the chain.
|
||||
func TestTxNotifierSpendReorg(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const startingHeight = 10
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// We'll have two outpoints that will be spent throughout the test. The
|
||||
// first will be spent and will not experience a reorg, while the second
|
||||
// one will.
|
||||
op1 := zeroOutPoint
|
||||
op1.Index = 1
|
||||
spendTx1 := wire.NewMsgTx(2)
|
||||
spendTx1.AddTxIn(&wire.TxIn{PreviousOutPoint: op1})
|
||||
spendTxHash1 := spendTx1.TxHash()
|
||||
expectedSpendDetails1 := &chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &op1,
|
||||
SpenderTxHash: &spendTxHash1,
|
||||
SpendingTx: spendTx1,
|
||||
SpenderInputIndex: 0,
|
||||
SpendingHeight: startingHeight + 1,
|
||||
}
|
||||
|
||||
op2 := zeroOutPoint
|
||||
op2.Index = 2
|
||||
spendTx2 := wire.NewMsgTx(2)
|
||||
spendTx2.AddTxIn(&wire.TxIn{PreviousOutPoint: zeroOutPoint})
|
||||
spendTx2.AddTxIn(&wire.TxIn{PreviousOutPoint: op2})
|
||||
spendTxHash2 := spendTx2.TxHash()
|
||||
|
||||
// The second outpoint will experience a reorg and get re-spent at a
|
||||
// different height, so we'll need to construct the spend details for
|
||||
// before and after the reorg.
|
||||
expectedSpendDetails2BeforeReorg := chainntnfs.SpendDetail{
|
||||
SpentOutPoint: &op2,
|
||||
SpenderTxHash: &spendTxHash2,
|
||||
SpendingTx: spendTx2,
|
||||
SpenderInputIndex: 1,
|
||||
SpendingHeight: startingHeight + 2,
|
||||
}
|
||||
|
||||
// The spend details after the reorg will be exactly the same, except
|
||||
// for the spend confirming at the next height.
|
||||
expectedSpendDetails2AfterReorg := expectedSpendDetails2BeforeReorg
|
||||
expectedSpendDetails2AfterReorg.SpendingHeight++
|
||||
|
||||
// We'll register for a spend notification for each outpoint above.
|
||||
ntfn1 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 78,
|
||||
OutPoint: op1,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn1); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
ntfn2 := &chainntnfs.SpendNtfn{
|
||||
SpendID: 21,
|
||||
OutPoint: op2,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn2); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
// We'll extend the chain by connecting a new block at tip. This block
|
||||
// will only contain the spending transaction of the first outpoint.
|
||||
block1 := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx1},
|
||||
})
|
||||
err := n.ConnectTip(
|
||||
block1.Hash(), startingHeight+1, block1.Transactions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// We should receive a spend notification for the first outpoint with
|
||||
// its correct spending details.
|
||||
select {
|
||||
case spendDetails := <-ntfn1.Event.Spend:
|
||||
assertSpendDetails(t, spendDetails, expectedSpendDetails1)
|
||||
default:
|
||||
t.Fatal("expected to receive spend details")
|
||||
}
|
||||
|
||||
// We should not, however, receive one for the second outpoint as it has
|
||||
// yet to be spent.
|
||||
select {
|
||||
case <-ntfn2.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// Now, we'll extend the chain again, this time with a block containing
|
||||
// the spending transaction of the second outpoint.
|
||||
block2 := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx2},
|
||||
})
|
||||
err = n.ConnectTip(
|
||||
block2.Hash(), startingHeight+2, block2.Transactions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// We should not receive another spend notification for the first
|
||||
// outpoint.
|
||||
select {
|
||||
case <-ntfn1.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// We should receive one for the second outpoint.
|
||||
select {
|
||||
case spendDetails := <-ntfn2.Event.Spend:
|
||||
assertSpendDetails(
|
||||
t, spendDetails, &expectedSpendDetails2BeforeReorg,
|
||||
)
|
||||
default:
|
||||
t.Fatal("expected to receive spend details")
|
||||
}
|
||||
|
||||
// Now, to replicate a chain reorg, we'll disconnect the block that
|
||||
// contained the spending transaction of the second outpoint.
|
||||
if err := n.DisconnectTip(startingHeight + 2); err != nil {
|
||||
t.Fatalf("unable to disconnect block: %v", err)
|
||||
}
|
||||
|
||||
// No notifications should be dispatched for the first outpoint as it
|
||||
// was spent at a previous height.
|
||||
select {
|
||||
case <-ntfn1.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
case <-ntfn1.Event.Reorg:
|
||||
t.Fatal("received unexpected spend reorg notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// We should receive a reorg notification for the second outpoint.
|
||||
select {
|
||||
case <-ntfn2.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
case <-ntfn2.Event.Reorg:
|
||||
default:
|
||||
t.Fatal("expected spend reorg notification")
|
||||
}
|
||||
|
||||
// We'll now extend the chain with an empty block, to ensure that we can
|
||||
// properly detect when an outpoint has been re-spent at a later height.
|
||||
emptyBlock := btcutil.NewBlock(&wire.MsgBlock{})
|
||||
err = n.ConnectTip(
|
||||
emptyBlock.Hash(), startingHeight+2, emptyBlock.Transactions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to disconnect block: %v", err)
|
||||
}
|
||||
|
||||
// We shouldn't receive notifications for either of the outpoints.
|
||||
select {
|
||||
case <-ntfn1.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
case <-ntfn1.Event.Reorg:
|
||||
t.Fatal("received unexpected spend reorg notification")
|
||||
case <-ntfn2.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
case <-ntfn2.Event.Reorg:
|
||||
t.Fatal("received unexpected spend reorg notification")
|
||||
default:
|
||||
}
|
||||
|
||||
// Finally, extend the chain with another block containing the same
|
||||
// spending transaction of the second outpoint.
|
||||
err = n.ConnectTip(
|
||||
block2.Hash(), startingHeight+3, block2.Transactions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// We should now receive a spend notification once again for the second
|
||||
// outpoint containing the new spend details.
|
||||
select {
|
||||
case spendDetails := <-ntfn2.Event.Spend:
|
||||
assertSpendDetails(
|
||||
t, spendDetails, &expectedSpendDetails2AfterReorg,
|
||||
)
|
||||
default:
|
||||
t.Fatalf("expected to receive spend notification")
|
||||
}
|
||||
|
||||
// Once again, we should not receive one for the first outpoint.
|
||||
select {
|
||||
case <-ntfn1.Event.Spend:
|
||||
t.Fatal("received unexpected spend notification")
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierConfirmHintCache ensures that the height hints for transactions
|
||||
// are kept track of correctly with each new block connected/disconnected. This
|
||||
// test also asserts that the height hints are not updated until the simulated
|
||||
// historical dispatches have returned, and we know the transactions aren't
|
||||
// already in the chain.
|
||||
func TestTxConfHeightHintCache(t *testing.T) {
|
||||
func TestTxNotifierConfirmHintCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
@ -702,9 +1440,7 @@ func TestTxConfHeightHintCache(t *testing.T) {
|
||||
|
||||
// Initialize our TxNotifier instance backed by a height hint cache.
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(
|
||||
startingHeight, 100, hintCache,
|
||||
)
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// Create two test transactions and register them for notifications.
|
||||
tx1 := wire.MsgTx{Version: 1}
|
||||
@ -884,129 +1620,284 @@ func TestTxConfHeightHintCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxConfTearDown(t *testing.T) {
|
||||
// TestTxNotifierSpendHintCache ensures that the height hints for outpoints are
|
||||
// kept track of correctly with each new block connected/disconnected. This test
|
||||
// also asserts that the height hints are not updated until the simulated
|
||||
// historical dispatches have returned, and we know the outpoints haven't
|
||||
// already been spent in the chain.
|
||||
func TestTxNotifierSpendHintCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
tx1 = wire.MsgTx{Version: 1}
|
||||
tx2 = wire.MsgTx{Version: 2}
|
||||
const (
|
||||
startingHeight = 200
|
||||
dummyHeight = 201
|
||||
op1Height = 202
|
||||
op2Height = 203
|
||||
)
|
||||
|
||||
// Intiialize our TxNotifier instance backed by a height hint cache.
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache)
|
||||
n := chainntnfs.NewTxNotifier(startingHeight, 100, hintCache, hintCache)
|
||||
|
||||
// Create the test transactions and register them with the TxNotifier to
|
||||
// receive notifications.
|
||||
tx1Hash := tx1.TxHash()
|
||||
ntfn1 := chainntnfs.ConfNtfn{
|
||||
TxID: &tx1Hash,
|
||||
// Create two test outpoints and register them for spend notifications.
|
||||
op1 := wire.OutPoint{Hash: zeroHash, Index: 1}
|
||||
ntfn1 := &chainntnfs.SpendNtfn{
|
||||
OutPoint: op1,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
op2 := wire.OutPoint{Hash: zeroHash, Index: 2}
|
||||
ntfn2 := &chainntnfs.SpendNtfn{
|
||||
OutPoint: op2,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
|
||||
if _, err := n.RegisterSpend(ntfn1); err != nil {
|
||||
t.Fatalf("unable to register spend for op1: %v", err)
|
||||
}
|
||||
if _, err := n.RegisterSpend(ntfn2); err != nil {
|
||||
t.Fatalf("unable to register spend for op2: %v", err)
|
||||
}
|
||||
|
||||
// Both outpoints should not have a spend hint set upon registration, as
|
||||
// we must first determine whether they have already been spent in the
|
||||
// chain.
|
||||
_, err := hintCache.QuerySpendHint(op1)
|
||||
if err != chainntnfs.ErrSpendHintNotFound {
|
||||
t.Fatalf("unexpected error when querying for height hint "+
|
||||
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
|
||||
err)
|
||||
}
|
||||
_, err = hintCache.QuerySpendHint(op2)
|
||||
if err != chainntnfs.ErrSpendHintNotFound {
|
||||
t.Fatalf("unexpected error when querying for height hint "+
|
||||
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
|
||||
err)
|
||||
}
|
||||
|
||||
// Create a new empty block and extend the chain.
|
||||
emptyBlock := btcutil.NewBlock(&wire.MsgBlock{})
|
||||
err = n.ConnectTip(
|
||||
emptyBlock.Hash(), dummyHeight, emptyBlock.Transactions(),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// Since we haven't called UpdateSpendDetails on any of the test
|
||||
// outpoints, this implies that there is a still a pending historical
|
||||
// rescan for them, so their spend hints should not be created/updated.
|
||||
_, err = hintCache.QuerySpendHint(op1)
|
||||
if err != chainntnfs.ErrSpendHintNotFound {
|
||||
t.Fatalf("unexpected error when querying for height hint "+
|
||||
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
|
||||
err)
|
||||
}
|
||||
_, err = hintCache.QuerySpendHint(op2)
|
||||
if err != chainntnfs.ErrSpendHintNotFound {
|
||||
t.Fatalf("unexpected error when querying for height hint "+
|
||||
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
|
||||
err)
|
||||
}
|
||||
|
||||
// Now, we'll simulate that their historical rescans have finished by
|
||||
// calling UpdateSpendDetails. This should allow their spend hints to be
|
||||
// updated upon every block connected/disconnected.
|
||||
if err := n.UpdateSpendDetails(ntfn1.OutPoint, nil); err != nil {
|
||||
t.Fatalf("unable to update spend details: %v", err)
|
||||
}
|
||||
if err := n.UpdateSpendDetails(ntfn2.OutPoint, nil); err != nil {
|
||||
t.Fatalf("unable to update spend details: %v", err)
|
||||
}
|
||||
|
||||
// We'll create a new block that only contains the spending transaction
|
||||
// of the first outpoint.
|
||||
spendTx1 := wire.NewMsgTx(2)
|
||||
spendTx1.AddTxIn(&wire.TxIn{PreviousOutPoint: ntfn1.OutPoint})
|
||||
block1 := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx1},
|
||||
})
|
||||
err = n.ConnectTip(block1.Hash(), op1Height, block1.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// Both outpoints should have their spend hints reflect the height of
|
||||
// the new block being connected due to the first outpoint being spent
|
||||
// at this height, and the second outpoint still being unspent.
|
||||
op1Hint, err := hintCache.QuerySpendHint(ntfn1.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op1: %v", err)
|
||||
}
|
||||
if op1Hint != op1Height {
|
||||
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
|
||||
}
|
||||
op2Hint, err := hintCache.QuerySpendHint(ntfn2.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op2: %v", err)
|
||||
}
|
||||
if op2Hint != op1Height {
|
||||
t.Fatalf("expected hint %d, got %d", op1Height, op2Hint)
|
||||
}
|
||||
|
||||
// Then, we'll create another block that spends the second outpoint.
|
||||
spendTx2 := wire.NewMsgTx(2)
|
||||
spendTx2.AddTxIn(&wire.TxIn{PreviousOutPoint: ntfn2.OutPoint})
|
||||
block2 := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{spendTx2},
|
||||
})
|
||||
err = n.ConnectTip(block2.Hash(), op2Height, block2.Transactions())
|
||||
if err != nil {
|
||||
t.Fatalf("unable to connect block: %v", err)
|
||||
}
|
||||
|
||||
// Only the second outpoint should have its spend hint updated due to
|
||||
// being spent within the new block. The first outpoint's spend hint
|
||||
// should remain the same as it's already been spent before.
|
||||
op1Hint, err = hintCache.QuerySpendHint(ntfn1.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op1: %v", err)
|
||||
}
|
||||
if op1Hint != op1Height {
|
||||
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
|
||||
}
|
||||
op2Hint, err = hintCache.QuerySpendHint(ntfn2.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op2: %v", err)
|
||||
}
|
||||
if op2Hint != op2Height {
|
||||
t.Fatalf("expected hint %d, got %d", op2Height, op2Hint)
|
||||
}
|
||||
|
||||
// Finally, we'll attempt do disconnect the last block in order to
|
||||
// simulate a chain reorg.
|
||||
if err := n.DisconnectTip(op2Height); err != nil {
|
||||
t.Fatalf("unable to disconnect block: %v", err)
|
||||
}
|
||||
|
||||
// This should update the second outpoint's spend hint within the cache
|
||||
// to the previous height, as that's where its spending transaction was
|
||||
// included in within the chain. The first outpoint's spend hint should
|
||||
// remain the same.
|
||||
op1Hint, err = hintCache.QuerySpendHint(ntfn1.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op1: %v", err)
|
||||
}
|
||||
if op1Hint != op1Height {
|
||||
t.Fatalf("expected hint %d, got %d", op1Height, op1Hint)
|
||||
}
|
||||
op2Hint, err = hintCache.QuerySpendHint(ntfn2.OutPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to query for spend hint of op2: %v", err)
|
||||
}
|
||||
if op2Hint != op1Height {
|
||||
t.Fatalf("expected hint %d, got %d", op1Height, op2Hint)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxNotifierTearDown ensures that the TxNotifier properly alerts clients
|
||||
// that it is shutting down and will be unable to deliver notifications.
|
||||
func TestTxNotifierTearDown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hintCache := newMockHintCache()
|
||||
n := chainntnfs.NewTxNotifier(10, 100, hintCache, hintCache)
|
||||
|
||||
// To begin the test, we'll register for a confirmation and spend
|
||||
// notification.
|
||||
confNtfn := &chainntnfs.ConfNtfn{
|
||||
TxID: &zeroHash,
|
||||
NumConfirmations: 1,
|
||||
Event: chainntnfs.NewConfirmationEvent(1),
|
||||
}
|
||||
if _, err := n.RegisterConf(&ntfn1); err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
if err := n.UpdateConfDetails(*ntfn1.TxID, nil); err != nil {
|
||||
t.Fatalf("unable to update conf details: %v", err)
|
||||
if _, err := n.RegisterConf(confNtfn); err != nil {
|
||||
t.Fatalf("unable to register conf ntfn: %v", err)
|
||||
}
|
||||
|
||||
tx2Hash := tx2.TxHash()
|
||||
ntfn2 := chainntnfs.ConfNtfn{
|
||||
TxID: &tx2Hash,
|
||||
NumConfirmations: 2,
|
||||
Event: chainntnfs.NewConfirmationEvent(2),
|
||||
spendNtfn := &chainntnfs.SpendNtfn{
|
||||
OutPoint: zeroOutPoint,
|
||||
Event: chainntnfs.NewSpendEvent(nil),
|
||||
}
|
||||
if _, err := n.RegisterConf(&ntfn2); err != nil {
|
||||
t.Fatalf("unable to register ntfn: %v", err)
|
||||
}
|
||||
if err := n.UpdateConfDetails(*ntfn2.TxID, nil); err != nil {
|
||||
t.Fatalf("unable to update conf details: %v", err)
|
||||
if _, err := n.RegisterSpend(spendNtfn); err != nil {
|
||||
t.Fatalf("unable to register spend ntfn: %v", err)
|
||||
}
|
||||
|
||||
// Include the transactions in a block and add it to the TxNotifier.
|
||||
// This should confirm tx1, but not tx2.
|
||||
block := btcutil.NewBlock(&wire.MsgBlock{
|
||||
Transactions: []*wire.MsgTx{&tx1, &tx2},
|
||||
})
|
||||
|
||||
err := n.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.
|
||||
// With the notifications registered, we'll now tear down the notifier.
|
||||
// The notification channels should be closed for notifications, whether
|
||||
// they have been dispatched or not, so we should not expect to receive
|
||||
// any more updates.
|
||||
n.TearDown()
|
||||
|
||||
// tx1 should not receive any more updates because it has already been
|
||||
// confirmed and the TxNotifier 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)
|
||||
case _, ok := <-confNtfn.Event.Confirmed:
|
||||
if ok {
|
||||
t.Fatal("expected closed Confirmed channel for conf ntfn")
|
||||
}
|
||||
case _, ok := <-confNtfn.Event.Updates:
|
||||
if ok {
|
||||
t.Fatal("expected closed Updates channel for conf ntfn")
|
||||
}
|
||||
case _, ok := <-confNtfn.Event.NegativeConf:
|
||||
if ok {
|
||||
t.Fatal("expected closed NegativeConf channel for conf ntfn")
|
||||
}
|
||||
case _, ok := <-spendNtfn.Event.Spend:
|
||||
if ok {
|
||||
t.Fatal("expected closed Spend channel for spend ntfn")
|
||||
}
|
||||
case _, ok := <-spendNtfn.Event.Reorg:
|
||||
if ok {
|
||||
t.Fatalf("expected closed Reorg channel for spend ntfn")
|
||||
}
|
||||
default:
|
||||
t.Fatalf("expected closed notification channels for all ntfns")
|
||||
}
|
||||
|
||||
// tx2 should not receive any more updates after the notifications
|
||||
// channels have been closed and the TxNotifier 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")
|
||||
// Now that the notifier is torn down, we should no longer be able to
|
||||
// register notification requests.
|
||||
if _, err := n.RegisterConf(confNtfn); err == nil {
|
||||
t.Fatal("expected confirmation registration to fail")
|
||||
}
|
||||
if _, err := n.RegisterSpend(spendNtfn); err == nil {
|
||||
t.Fatal("expected spend registration to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqualTxConf(t *testing.T,
|
||||
actualConf, expectedConf *chainntnfs.TxConfirmation) {
|
||||
func assertConfDetails(t *testing.T, result, expected *chainntnfs.TxConfirmation) {
|
||||
t.Helper()
|
||||
|
||||
if actualConf.BlockHeight != expectedConf.BlockHeight {
|
||||
if result.BlockHeight != expected.BlockHeight {
|
||||
t.Fatalf("Incorrect block height in confirmation details: "+
|
||||
"expected %d, got %d",
|
||||
expectedConf.BlockHeight, actualConf.BlockHeight)
|
||||
"expected %d, got %d", expected.BlockHeight,
|
||||
result.BlockHeight)
|
||||
}
|
||||
if !actualConf.BlockHash.IsEqual(expectedConf.BlockHash) {
|
||||
if !result.BlockHash.IsEqual(expected.BlockHash) {
|
||||
t.Fatalf("Incorrect block hash in confirmation details: "+
|
||||
"expected %d, got %d", expectedConf.BlockHash, actualConf.BlockHash)
|
||||
"expected %d, got %d", expected.BlockHash,
|
||||
result.BlockHash)
|
||||
}
|
||||
if actualConf.TxIndex != expectedConf.TxIndex {
|
||||
if result.TxIndex != expected.TxIndex {
|
||||
t.Fatalf("Incorrect tx index in confirmation details: "+
|
||||
"expected %d, got %d", expectedConf.TxIndex, actualConf.TxIndex)
|
||||
"expected %d, got %d", expected.TxIndex, result.TxIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSpendDetails(t *testing.T, result, expected *chainntnfs.SpendDetail) {
|
||||
t.Helper()
|
||||
|
||||
if *result.SpentOutPoint != *expected.SpentOutPoint {
|
||||
t.Fatalf("expected spent outpoint %v, got %v",
|
||||
expected.SpentOutPoint, result.SpentOutPoint)
|
||||
}
|
||||
if !result.SpenderTxHash.IsEqual(expected.SpenderTxHash) {
|
||||
t.Fatalf("expected spender tx hash %v, got %v",
|
||||
expected.SpenderTxHash, result.SpenderTxHash)
|
||||
}
|
||||
if result.SpenderInputIndex != expected.SpenderInputIndex {
|
||||
t.Fatalf("expected spender input index %d, got %d",
|
||||
expected.SpenderInputIndex, result.SpenderInputIndex)
|
||||
}
|
||||
if result.SpendingHeight != expected.SpendingHeight {
|
||||
t.Fatalf("expected spending height %d, got %d",
|
||||
expected.SpendingHeight, result.SpendingHeight)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user