chainntnfs/txnotifier_test: add TestTxNotifierSpendDuringHistoricalRescan

This commit is contained in:
Johan T. Halseth 2019-11-14 15:01:21 +01:00
parent e8b306b0ff
commit f0048bb662
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26

@ -2155,6 +2155,210 @@ func TestTxNotifierSpendHintCache(t *testing.T) {
}
}
// TestTxNotifierSpendHinthistoricalRescan checks that the height hints and
// spend notifications behave as expected when a spend is found at tip during a
// historical rescan.
func TestTxNotifierSpendDuringHistoricalRescan(t *testing.T) {
t.Parallel()
const (
startingHeight = 200
reorgSafety = 10
)
// Intiialize our TxNotifier instance backed by a height hint cache.
hintCache := newMockHintCache()
n := chainntnfs.NewTxNotifier(
startingHeight, reorgSafety, hintCache, hintCache,
)
// Create a test outpoint and register it for spend notifications.
op1 := wire.OutPoint{Index: 1}
ntfn1, err := n.RegisterSpend(&op1, testRawScript, 1)
if err != nil {
t.Fatalf("unable to register spend for op1: %v", err)
}
// A historical rescan should be initiated from the height hint to the
// current height.
if ntfn1.HistoricalDispatch.StartHeight != 1 {
t.Fatalf("expected historical dispatch to start at height hint")
}
if ntfn1.HistoricalDispatch.EndHeight != startingHeight {
t.Fatalf("expected historical dispatch to end at current height")
}
// It should not have a spend hint set upon registration, as we must
// first determine whether it has already been spent in the chain.
_, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
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.
height := uint32(startingHeight) + 1
emptyBlock := btcutil.NewBlock(&wire.MsgBlock{})
err = n.ConnectTip(
emptyBlock.Hash(), height, emptyBlock.Transactions(),
)
if err != nil {
t.Fatalf("unable to connect block: %v", err)
}
if err := n.NotifyHeight(height); err != nil {
t.Fatalf("unable to dispatch notifications: %v", err)
}
// Since we haven't called UpdateSpendDetails yet, there should be no
// spend hint found.
_, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != chainntnfs.ErrSpendHintNotFound {
t.Fatalf("unexpected error when querying for height hint "+
"expected: %v, got %v", chainntnfs.ErrSpendHintNotFound,
err)
}
// Simulate a bunch of blocks being mined while the historical rescan
// is still in progress. We make sure to not mine more than reorgSafety
// blocks after the spend, since it will be forgotten then.
var spendHeight uint32
for i := 0; i < reorgSafety; i++ {
height++
// Let the outpoint we are watching be spent midway.
var block *btcutil.Block
if i == 5 {
// We'll create a new block that only contains the
// spending transaction of the outpoint.
spendTx1 := wire.NewMsgTx(2)
spendTx1.AddTxIn(&wire.TxIn{
PreviousOutPoint: op1,
SignatureScript: testSigScript,
})
block = btcutil.NewBlock(&wire.MsgBlock{
Transactions: []*wire.MsgTx{spendTx1},
})
spendHeight = height
} else {
// Otherwise we just create an empty block.
block = btcutil.NewBlock(&wire.MsgBlock{})
}
err = n.ConnectTip(
block.Hash(), height, block.Transactions(),
)
if err != nil {
t.Fatalf("unable to connect block: %v", err)
}
if err := n.NotifyHeight(height); err != nil {
t.Fatalf("unable to dispatch notifications: %v", err)
}
}
// Check that the height hint was set to the spending block.
op1Hint, err := hintCache.QuerySpendHint(
ntfn1.HistoricalDispatch.SpendRequest,
)
if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err)
}
if op1Hint != spendHeight {
t.Fatalf("expected hint %d, got %d", spendHeight, op1Hint)
}
// We should be getting notified about the spend at this point.
select {
case <-ntfn1.Event.Spend:
default:
t.Fatal("expected to receive spend notification")
}
// Now, we'll simulate that the historical rescan finished by
// calling UpdateSpendDetails. Since a the spend actually happened at
// tip while the rescan was in progress, the height hint should not be
// updated to the latest height, but stay at the spend height.
err = n.UpdateSpendDetails(ntfn1.HistoricalDispatch.SpendRequest, nil)
if err != nil {
t.Fatalf("unable to update spend details: %v", err)
}
op1Hint, err = hintCache.QuerySpendHint(
ntfn1.HistoricalDispatch.SpendRequest,
)
if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err)
}
if op1Hint != spendHeight {
t.Fatalf("expected hint %d, got %d", spendHeight, op1Hint)
}
// Then, we'll create another block that spends a second outpoint.
op2 := wire.OutPoint{Index: 2}
spendTx2 := wire.NewMsgTx(2)
spendTx2.AddTxIn(&wire.TxIn{
PreviousOutPoint: op2,
SignatureScript: testSigScript,
})
height++
block2 := btcutil.NewBlock(&wire.MsgBlock{
Transactions: []*wire.MsgTx{spendTx2},
})
err = n.ConnectTip(block2.Hash(), height, block2.Transactions())
if err != nil {
t.Fatalf("unable to connect block: %v", err)
}
if err := n.NotifyHeight(height); err != nil {
t.Fatalf("unable to dispatch notifications: %v", err)
}
// The outpoint's spend hint should remain the same as it's already
// been spent before.
op1Hint, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err)
}
if op1Hint != spendHeight {
t.Fatalf("expected hint %d, got %d", spendHeight, op1Hint)
}
// Now mine enough blocks for the spend notification to be forgotten.
for i := 0; i < 2*reorgSafety; i++ {
height++
block := btcutil.NewBlock(&wire.MsgBlock{})
err := n.ConnectTip(
block.Hash(), height, block.Transactions(),
)
if err != nil {
t.Fatalf("unable to connect block: %v", err)
}
if err := n.NotifyHeight(height); err != nil {
t.Fatalf("unable to dispatch notifications: %v", err)
}
}
// Attempting to update spend details at this point should fail, since
// the spend request should be removed. This is to ensure the height
// hint won't be overwritten if the historical rescan finishes after
// the spend request has been notified and removed because it has
// matured.
err = n.UpdateSpendDetails(ntfn1.HistoricalDispatch.SpendRequest, nil)
if err == nil {
t.Fatalf("expcted updating spend details to fail")
}
// Finally, check that the height hint is still there, unchanged.
op1Hint, err = hintCache.QuerySpendHint(ntfn1.HistoricalDispatch.SpendRequest)
if err != nil {
t.Fatalf("unable to query for spend hint of op1: %v", err)
}
if op1Hint != spendHeight {
t.Fatalf("expected hint %d, got %d", spendHeight, op1Hint)
}
}
// TestTxNotifierNtfnDone ensures that a notification is sent to registered
// clients through the Done channel once the notification request is no longer
// under the risk of being reorged out of the chain.