public void Init()
        {
            var fakeHeaders = new FakeHeaders();

            this.chainedHeader0 = new ChainedHeader(fakeHeaders.Genesis(), 0, 0);
            this.chainedHeader1 = new ChainedHeader(fakeHeaders.Next(), 1, 0);

            var fakeHeadersA = new FakeHeaders(fakeHeaders);

            this.chainedHeaderA2 = new ChainedHeader(fakeHeadersA.Next(), 2, 0);
            this.chainedHeaderA3 = new ChainedHeader(fakeHeadersA.Next(), 3, 0);
            this.chainedHeaderA4 = new ChainedHeader(fakeHeadersA.Next(), 4, 0);

            var fakeHeadersB = new FakeHeaders(fakeHeaders);

            this.chainedHeaderB2 = new ChainedHeader(fakeHeadersB.Next(), 2, 0);
            this.chainedHeaderB3 = new ChainedHeader(fakeHeadersB.Next(), 3, 0);
            this.chainedHeaderB4 = new ChainedHeader(fakeHeadersB.Next(), 4, 0);

            var fakeHeadersX = new FakeHeaders();

            this.chainedHeaderX0 = new ChainedHeader(fakeHeadersX.Genesis(), 0, 0);
            this.chainedHeaderX1 = new ChainedHeader(fakeHeadersX.Next(), 1, 0);

            this.chain = ImmutableDictionary.CreateRange(
                new[] { chainedHeader0, chainedHeader1, chainedHeaderA2, chainedHeaderA3, chainedHeaderA4, chainedHeaderB2, chainedHeaderB3, chainedHeaderB4, chainedHeaderX0, chainedHeaderX1 }
                .Select(x => new KeyValuePair <UInt256, ChainedHeader>(x.Hash, x)));

            this.getChainedHeader = blockHash => this.chain[blockHash];
        }
        public void TestSimpleTargetBlock()
        {
            // prepare test kernel
            var kernel             = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule(), new CoreCacheModule());
            var chainedHeaderCache = kernel.Get <ChainedHeaderCache>();

            // initialize data
            var fakeHeaders    = new FakeHeaders();
            var chainedHeader0 = new ChainedHeader(fakeHeaders.Genesis(), height: 0, totalWork: 0);
            var chainedHeader1 = new ChainedHeader(fakeHeaders.Next(), height: 1, totalWork: 1);

            // initialize the target block watcher
            using (var targetBlockWorker = kernel.Get <TargetBlockWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: false, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // monitor event firing
                var workNotifyEvent           = new AutoResetEvent(false);
                var workStoppedEvent          = new AutoResetEvent(false);
                var onTargetBlockChangedCount = 0;

                targetBlockWorker.OnNotifyWork         += () => workNotifyEvent.Set();
                targetBlockWorker.OnWorkStopped        += () => workStoppedEvent.Set();
                targetBlockWorker.OnTargetBlockChanged += () => onTargetBlockChangedCount++;

                // start worker and wait for intial target
                targetBlockWorker.Start();
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();

                // verify initial state
                Assert.IsNull(targetBlockWorker.TargetBlock);

                // add block 0
                chainedHeaderCache[chainedHeader0.Hash] = chainedHeader0;

                // wait for worker
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();

                // verify block 0
                Assert.AreEqual(chainedHeader0, targetBlockWorker.TargetBlock);
                Assert.AreEqual(1, onTargetBlockChangedCount);

                // add block 1
                chainedHeaderCache[chainedHeader1.Hash] = chainedHeader1;

                // wait for worker
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();

                // verify block 1
                Assert.AreEqual(chainedHeader1, targetBlockWorker.TargetBlock);
                Assert.AreEqual(2, onTargetBlockChangedCount);

                // verify no other work was done
                Assert.IsFalse(workNotifyEvent.WaitOne(0));
                Assert.IsFalse(workStoppedEvent.WaitOne(0));
            }
        }
        public void TestTargetChainReorganize()
        {
            // prepare test kernel
            var kernel = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule());
            kernel.Bind<CoreStorage>().ToSelf().InSingletonScope();
            var coreStorage = kernel.Get<CoreStorage>();

            // initialize data
            var fakeHeaders = new FakeHeaders();
            var header0 = fakeHeaders.Genesis();
            var header1 = fakeHeaders.Next();
            var header2 = fakeHeaders.Next();

            var fakeHeadersA = new FakeHeaders(fakeHeaders);
            var header3A = fakeHeadersA.Next();
            var header4A = fakeHeadersA.Next();
            var header5A = fakeHeadersA.Next();

            var fakeHeadersB = new FakeHeaders(fakeHeaders);
            var header3B = fakeHeadersB.Next();
            var header4B = fakeHeadersB.Next(DataCalculator.TargetToBits(UnitTestParams.Target2));

            // store genesis block
            var chainedHeader0 = ChainedHeader.CreateForGenesisBlock(header0);
            coreStorage.AddGenesisBlock(chainedHeader0);

            // mock chain params
            var mockChainParams = new Mock<IChainParams>();
            mockChainParams.Setup(rules => rules.GenesisChainedHeader).Returns(chainedHeader0);
            kernel.Bind<IChainParams>().ToConstant(mockChainParams.Object);

            // initialize the target chain worker
            using (var targetChainWorker = kernel.Get<TargetChainWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // monitor event firing
                var targetChainChangedEvent = new AutoResetEvent(false);
                var onTargetChainChangedCount = 0;
                targetChainWorker.OnTargetChainChanged += () => { onTargetChainChangedCount++; targetChainChangedEvent.Set(); };

                // start worker and wait for initial chain
                targetChainWorker.Start();
                targetChainChangedEvent.WaitOne();

                // verify chained to block 0
                Assert.AreEqual(chainedHeader0, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(1, onTargetChainChangedCount);

                // add block 1
                ChainedHeader chainedHeader1;
                coreStorage.TryChainHeader(header1, out chainedHeader1);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 1
                Assert.AreEqual(chainedHeader1, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(2, onTargetChainChangedCount);

                // add block 2
                ChainedHeader chainedHeader2;
                coreStorage.TryChainHeader(header2, out chainedHeader2);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 2
                Assert.AreEqual(chainedHeader2, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(3, onTargetChainChangedCount);

                // add block 3A
                ChainedHeader chainedHeader3A;
                coreStorage.TryChainHeader(header3A, out chainedHeader3A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 3A
                Assert.AreEqual(chainedHeader3A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(4, onTargetChainChangedCount);

                // add block 4A
                ChainedHeader chainedHeader4A;
                coreStorage.TryChainHeader(header4A, out chainedHeader4A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 4A
                Assert.AreEqual(chainedHeader4A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(5, onTargetChainChangedCount);

                // add block 5A
                ChainedHeader chainedHeader5A;
                coreStorage.TryChainHeader(header5A, out chainedHeader5A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 5A
                Assert.AreEqual(chainedHeader5A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A, chainedHeader5A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(6, onTargetChainChangedCount);

                // add block 3B
                ChainedHeader chainedHeader3B;
                coreStorage.TryChainHeader(header3B, out chainedHeader3B);

                // wait for worker, it should not fire
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));

                // verify no chaining done
                Assert.AreEqual(chainedHeader5A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A, chainedHeader5A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(6, onTargetChainChangedCount);

                // add block 4B
                ChainedHeader chainedHeader4B;
                coreStorage.TryChainHeader(header4B, out chainedHeader4B);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 4B
                Assert.AreEqual(chainedHeader4B, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3B, chainedHeader4B }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(7, onTargetChainChangedCount);

                // verify no other work was done
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));
            }
        }
        public void TestSimpleChain()
        {
            // prepare test kernel
            var kernel = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule());
            kernel.Bind<CoreStorage>().ToSelf().InSingletonScope();
            var coreStorage = kernel.Get<CoreStorage>();

            // initialize data
            var fakeHeaders = new FakeHeaders();
            var header0 = fakeHeaders.Genesis();
            var header1 = fakeHeaders.Next();
            var header2 = fakeHeaders.Next();

            // store genesis block
            var chainedHeader0 = ChainedHeader.CreateForGenesisBlock(header0);
            coreStorage.AddGenesisBlock(chainedHeader0);

            // mock rules
            var mockRules = new Mock<IBlockchainRules>();
            mockRules.Setup(rules => rules.GenesisChainedHeader).Returns(chainedHeader0);
            kernel.Bind<IBlockchainRules>().ToConstant(mockRules.Object);

            // initialize the target chain worker
            using (var targetChainWorker = kernel.Get<TargetChainWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // monitor event firing
                var targetChainChangedEvent = new AutoResetEvent(false);
                var onTargetChainChangedCount = 0;
                targetChainWorker.OnTargetChainChanged += () => { onTargetChainChangedCount++; targetChainChangedEvent.Set(); };

                // start worker and wait for initial chain
                targetChainWorker.Start();
                targetChainChangedEvent.WaitOne();

                // verify chained to block 0
                Assert.AreEqual(chainedHeader0, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(1, onTargetChainChangedCount);

                // add block 1
                ChainedHeader chainedHeader1;
                coreStorage.TryChainHeader(header1, out chainedHeader1);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 1
                Assert.AreEqual(chainedHeader1, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(2, onTargetChainChangedCount);

                // add block 2
                ChainedHeader chainedHeader2;
                coreStorage.TryChainHeader(header2, out chainedHeader2);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 2
                Assert.AreEqual(chainedHeader2, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(3, onTargetChainChangedCount);

                // verify no other work was done
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));
            }
        }
Beispiel #5
0
        public void TestSimpleChain()
        {
            // prepare test kernel
            var kernel = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule(), new CoreCacheModule());

            // initialize data
            var fakeHeaders    = new FakeHeaders();
            var chainedHeader0 = new ChainedHeader(fakeHeaders.Genesis(), height: 0, totalWork: 0);
            var chainedHeader1 = new ChainedHeader(fakeHeaders.Next(), height: 1, totalWork: 1);
            var chainedHeader2 = new ChainedHeader(fakeHeaders.Next(), height: 2, totalWork: 2);

            // mock rules
            var mockRules = new Mock <IBlockchainRules>();

            mockRules.Setup(rules => rules.GenesisChainedHeader).Returns(chainedHeader0);
            kernel.Bind <IBlockchainRules>().ToConstant(mockRules.Object);

            // store genesis block
            var chainedHeaderCache = kernel.Get <ChainedHeaderCache>();

            chainedHeaderCache[chainedHeader0.Hash] = chainedHeader0;

            // initialize the target chain worker
            using (var targetChainWorker = kernel.Get <TargetChainWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: false, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // verify initial state
                Assert.AreEqual(null, targetChainWorker.TargetBlock);
                Assert.AreEqual(null, targetChainWorker.TargetChain);

                // monitor event firing
                var workNotifyEvent           = new AutoResetEvent(false);
                var workStoppedEvent          = new AutoResetEvent(false);
                var onTargetChainChangedCount = 0;

                targetChainWorker.OnNotifyWork         += () => workNotifyEvent.Set();
                targetChainWorker.OnWorkStopped        += () => workStoppedEvent.Set();
                targetChainWorker.OnTargetChainChanged += () => onTargetChainChangedCount++;

                // start worker and wait for initial chain
                targetChainWorker.Start();
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();

                // verify chained to block 0
                Assert.AreEqual(chainedHeader0, targetChainWorker.TargetBlock);
                AssertBlockListEquals(new[] { chainedHeader0 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(1, onTargetChainChangedCount);

                // add block 1
                chainedHeaderCache[chainedHeader1.Hash] = chainedHeader1;

                // wait for worker (chained block addition)
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();
                // wait for worker (target block changed)
                workNotifyEvent.WaitOneOrFail(1000);
                workStoppedEvent.WaitOneOrFail(1000);

                // verify chained to block 1
                Assert.AreEqual(chainedHeader1, targetChainWorker.TargetBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(2, onTargetChainChangedCount);

                // add block 2
                chainedHeaderCache[chainedHeader2.Hash] = chainedHeader2;

                // wait for worker (chained block addition)
                workNotifyEvent.WaitOne();
                workStoppedEvent.WaitOne();
                // wait for worker (target block changed)
                workNotifyEvent.WaitOneOrFail(1000);
                workStoppedEvent.WaitOneOrFail(1000);

                // verify chained to block 2
                Assert.AreEqual(chainedHeader2, targetChainWorker.TargetBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(3, onTargetChainChangedCount);

                // verify no other work was done
                Assert.IsFalse(workNotifyEvent.WaitOne(0));
                Assert.IsFalse(workStoppedEvent.WaitOne(0));
            }
        }
Beispiel #6
0
        public void TestSimpleChain()
        {
            // prepare test kernel
            var kernel = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule());

            kernel.Bind <CoreStorage>().ToSelf().InSingletonScope();
            var coreStorage = kernel.Get <CoreStorage>();

            // initialize data
            var fakeHeaders = new FakeHeaders();
            var header0     = fakeHeaders.Genesis();
            var header1     = fakeHeaders.Next();
            var header2     = fakeHeaders.Next();

            // store genesis block
            var chainedHeader0 = ChainedHeader.CreateForGenesisBlock(header0);

            coreStorage.AddGenesisBlock(chainedHeader0);

            // mock rules
            var mockRules = new Mock <IBlockchainRules>();

            mockRules.Setup(rules => rules.GenesisChainedHeader).Returns(chainedHeader0);
            kernel.Bind <IBlockchainRules>().ToConstant(mockRules.Object);

            // initialize the target chain worker
            using (var targetChainWorker = kernel.Get <TargetChainWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // verify initial state
                Assert.AreEqual(null, targetChainWorker.TargetChain);

                // monitor event firing
                var targetChainChangedEvent   = new AutoResetEvent(false);
                var onTargetChainChangedCount = 0;
                targetChainWorker.OnTargetChainChanged += () => { onTargetChainChangedCount++; targetChainChangedEvent.Set(); };

                // start worker and wait for initial chain
                targetChainWorker.Start();
                targetChainChangedEvent.WaitOne();

                // verify chained to block 0
                Assert.AreEqual(chainedHeader0, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(1, onTargetChainChangedCount);

                // add block 1
                ChainedHeader chainedHeader1;
                coreStorage.TryChainHeader(header1, out chainedHeader1);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 1
                Assert.AreEqual(chainedHeader1, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(2, onTargetChainChangedCount);

                // add block 2
                ChainedHeader chainedHeader2;
                coreStorage.TryChainHeader(header2, out chainedHeader2);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 2
                Assert.AreEqual(chainedHeader2, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(3, onTargetChainChangedCount);

                // verify no other work was done
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));
            }
        }
Beispiel #7
0
        public void TestTargetChainReorganize()
        {
            // prepare test kernel
            var kernel = new StandardKernel(new ConsoleLoggingModule(), new MemoryStorageModule());

            kernel.Bind <CoreStorage>().ToSelf().InSingletonScope();
            var coreStorage = kernel.Get <CoreStorage>();

            // initialize data
            var fakeHeaders = new FakeHeaders();
            var header0     = fakeHeaders.Genesis();
            var header1     = fakeHeaders.Next();
            var header2     = fakeHeaders.Next();

            var fakeHeadersA = new FakeHeaders(fakeHeaders);
            var header3A     = fakeHeadersA.Next();
            var header4A     = fakeHeadersA.Next();
            var header5A     = fakeHeadersA.Next();

            var fakeHeadersB = new FakeHeaders(fakeHeaders);
            var header3B     = fakeHeadersB.Next();
            var header4B     = fakeHeadersB.Next(DataCalculator.ToCompact(UnitTestParams.Target2));

            // store genesis block
            var chainedHeader0 = ChainedHeader.CreateForGenesisBlock(header0);

            coreStorage.AddGenesisBlock(chainedHeader0);

            // mock chain params
            var mockChainParams = new Mock <IChainParams>();

            mockChainParams.Setup(rules => rules.GenesisChainedHeader).Returns(chainedHeader0);
            kernel.Bind <IChainParams>().ToConstant(mockChainParams.Object);

            // initialize the target chain worker
            using (var targetChainWorker = kernel.Get <TargetChainWorker>(new ConstructorArgument("workerConfig", new WorkerConfig(initialNotify: true, minIdleTime: TimeSpan.Zero, maxIdleTime: TimeSpan.MaxValue))))
            {
                // monitor event firing
                var targetChainChangedEvent   = new AutoResetEvent(false);
                var onTargetChainChangedCount = 0;
                targetChainWorker.OnTargetChainChanged += () => { onTargetChainChangedCount++; targetChainChangedEvent.Set(); };

                // start worker and wait for initial chain
                targetChainWorker.Start();
                targetChainChangedEvent.WaitOne();

                // verify chained to block 0
                Assert.AreEqual(chainedHeader0, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(1, onTargetChainChangedCount);

                // add block 1
                ChainedHeader chainedHeader1;
                coreStorage.TryChainHeader(header1, out chainedHeader1);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 1
                Assert.AreEqual(chainedHeader1, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(2, onTargetChainChangedCount);

                // add block 2
                ChainedHeader chainedHeader2;
                coreStorage.TryChainHeader(header2, out chainedHeader2);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 2
                Assert.AreEqual(chainedHeader2, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2 }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(3, onTargetChainChangedCount);

                // add block 3A
                ChainedHeader chainedHeader3A;
                coreStorage.TryChainHeader(header3A, out chainedHeader3A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 3A
                Assert.AreEqual(chainedHeader3A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(4, onTargetChainChangedCount);

                // add block 4A
                ChainedHeader chainedHeader4A;
                coreStorage.TryChainHeader(header4A, out chainedHeader4A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 4A
                Assert.AreEqual(chainedHeader4A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(5, onTargetChainChangedCount);

                // add block 5A
                ChainedHeader chainedHeader5A;
                coreStorage.TryChainHeader(header5A, out chainedHeader5A);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 5A
                Assert.AreEqual(chainedHeader5A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A, chainedHeader5A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(6, onTargetChainChangedCount);

                // add block 3B
                ChainedHeader chainedHeader3B;
                coreStorage.TryChainHeader(header3B, out chainedHeader3B);

                // wait for worker, it should not fire
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));

                // verify no chaining done
                Assert.AreEqual(chainedHeader5A, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3A, chainedHeader4A, chainedHeader5A }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(6, onTargetChainChangedCount);

                // add block 4B
                ChainedHeader chainedHeader4B;
                coreStorage.TryChainHeader(header4B, out chainedHeader4B);

                // wait for worker
                targetChainChangedEvent.WaitOne();

                // verify chained to block 4B
                Assert.AreEqual(chainedHeader4B, targetChainWorker.TargetChain.LastBlock);
                AssertBlockListEquals(new[] { chainedHeader0, chainedHeader1, chainedHeader2, chainedHeader3B, chainedHeader4B }, targetChainWorker.TargetChain.Blocks);
                Assert.AreEqual(7, onTargetChainChangedCount);

                // verify no other work was done
                Assert.IsFalse(targetChainChangedEvent.WaitOne(50));
            }
        }