chainntnfs/interface_test: add test for client catchup on block ntfns

This tests the case where a client registers for block notifications with an outdated best block, to ensure that the client is properly caught up on the blocks that it has missed.
This commit is contained in:
Valentine Wallace 2018-08-09 00:05:30 -07:00
parent f6ba64056a
commit dea22eb874
No known key found for this signature in database
GPG Key ID: B0E55E8D1776A58D

@ -1274,12 +1274,111 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.TestChainNotifier
}
}
// testCatchUpClientOnMissedBlocks tests the case of multiple registered client
// receiving historical block epoch notifications due to their best known block
// being out of date.
func testCatchUpClientOnMissedBlocks(miner *rpctest.Harness,
notifier chainntnfs.TestChainNotifier, t *testing.T) {
const numBlocks = 10
const numClients = 5
var wg sync.WaitGroup
outdatedHash, outdatedHeight, err := miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("unable to retrieve current height: %v", err)
}
// This function is used by UnsafeStart to ensure all notifications
// are fully drained before clients register for notifications.
generateBlocks := func() error {
_, err = miner.Node.Generate(numBlocks)
return err
}
// We want to ensure that when a client registers for block notifications,
// the notifier's best block is at the tip of the chain. If it isn't, the
// client may not receive all historical notifications.
bestHeight := outdatedHeight + numBlocks
if err := notifier.UnsafeStart(
bestHeight, nil, bestHeight, generateBlocks); err != nil {
t.Fatalf("Unable to unsafe start the notifier: %v", err)
}
// Create numClients clients whose best known block is 10 blocks behind
// the tip of the chain. We expect each client to receive numBlocks
// notifications, 1 for each block they're behind.
clients := make([]*chainntnfs.BlockEpochEvent, 0, numClients)
outdatedBlock := &chainntnfs.BlockEpoch{
Height: outdatedHeight, Hash: outdatedHash,
}
for i := 0; i < numClients; i++ {
epochClient, err := notifier.RegisterBlockEpochNtfn(outdatedBlock)
if err != nil {
t.Fatalf("unable to register for epoch notification: %v", err)
}
clients = append(clients, epochClient)
}
for expectedHeight := outdatedHeight + 1; expectedHeight <=
bestHeight; expectedHeight++ {
for _, epochClient := range clients {
select {
case block := <-epochClient.Epochs:
if block.Height != expectedHeight {
t.Fatalf("received block of height: %d, "+
"expected: %d", block.Height,
expectedHeight)
}
case <-time.After(20 * time.Second):
t.Fatalf("did not receive historical notification "+
"for height %d", expectedHeight)
}
}
}
// Finally, ensure that an extra block notification wasn't received.
anyExtras := make(chan struct{}, len(clients))
for _, epochClient := range clients {
wg.Add(1)
go func(epochClient *chainntnfs.BlockEpochEvent) {
defer wg.Done()
select {
case <-epochClient.Epochs:
anyExtras <- struct{}{}
case <-time.After(5 * time.Second):
}
}(epochClient)
}
wg.Wait()
close(anyExtras)
var extraCount int
for range anyExtras {
extraCount++
}
if extraCount > 0 {
t.Fatalf("received %d unexpected block notification", extraCount)
}
}
type testCase struct {
name string
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier, t *testing.T)
}
type blockCatchupTestCase struct {
name string
test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
t *testing.T)
}
var ntfnTests = []testCase{
{
name: "single conf ntfn",
@ -1331,6 +1430,13 @@ var ntfnTests = []testCase{
},
}
var blockCatchupTests = []blockCatchupTestCase{
{
name: "catch up client on historical block epoch ntfns",
test: testCatchUpClientOnMissedBlocks,
},
}
// TestInterfaces tests all registered interfaces with a unified set of tests
// which exercise each of the required methods found within the ChainNotifier
// interface.
@ -1511,6 +1617,29 @@ func TestInterfaces(t *testing.T) {
}
notifier.Stop()
// Run catchup tests separately since they require
// restarting the notifier every time.
for _, blockCatchupTest := range blockCatchupTests {
notifier, err = newNotifier()
if err != nil {
t.Fatalf("unable to create %v notifier: %v",
notifierType, err)
}
testName := fmt.Sprintf("%v: %v", notifierType,
blockCatchupTest.name)
success := t.Run(testName, func(t *testing.T) {
blockCatchupTest.test(miner, notifier, t)
})
notifier.Stop()
if !success {
break
}
}
if cleanUp != nil {
cleanUp()
}