Example #1
0
 public DiscordReadyStartJobHandler(DiscordSocketClient client, IndexBuilderService indexBuilder, Settings settings, Logger logger)
 {
     _client       = client;
     _indexBuilder = indexBuilder;
     _settings     = settings;
     _logger       = logger;
 }
#pragma warning restore CS8618

    public IndexBuilderService(Settings settings, Logger logger, Stats stats, GitPusherService gitPusherService, IMediator mediator, ISchedulerFactory schedulerFactory, IServiceProvider diContainer)
    {
        _instance         = this;
        _settings         = settings;
        _logger           = logger;
        _stats            = stats;
        _gitPusherService = gitPusherService;
        _mediator         = mediator;
        _diContainer      = diContainer;
        _scheduler        = Task.Run(() => schedulerFactory.GetScheduler()).GetAwaiter().GetResult();
        _scheduler.Start();
    }
Example #3
0
        public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
        {
            Config      = Guard.NotNull(nameof(config), config);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);

            // Make sure RPC works.
            await AssertRpcNodeFullyInitializedAsync();

            // Make sure P2P works.
            await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

            if (roundConfig.FilePath != null)
            {
                HostedServices.Register(
                    new ConfigWatcher(
                        TimeSpan.FromSeconds(10),                         // Every 10 seconds check the config
                        RoundConfig,
                        () =>
                {
                    try
                    {
                        Coordinator.RoundConfig.UpdateOrDefault(RoundConfig, toFile: false);

                        Coordinator.AbortAllRoundsInInputRegistration($"{nameof(RoundConfig)} has changed.");
                    }
                    catch (Exception ex)
                    {
                        Logger.LogDebug(ex);
                    }
                }),
                    "Config Watcher");
            }

            await HostedServices.StartAllAsync(cancel);

            // Initialize index building
            var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
            var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
            var blockNotifier          = HostedServices.FirstOrDefault <BlockNotifier>();

            IndexBuilderService = new IndexBuilderService(RpcClient, blockNotifier, indexFilePath);
            Coordinator         = new Coordinator(RpcClient.Network, blockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
            IndexBuilderService.Synchronize();
            Logger.LogInfo($"{nameof(IndexBuilderService)} is successfully initialized and started synchronization.");

            await Coordinator.MakeSureTwoRunningRoundsAsync();

            Logger.LogInfo("Chaumian CoinJoin Coordinator is successfully initialized and started two new rounds.");
        }
Example #4
0
    public void DummyFilterMatchesToFalse()
    {
        var rnd       = new Random(123456);
        var blockHash = new byte[32];

        rnd.NextBytes(blockHash);

        var filter = IndexBuilderService.CreateDummyEmptyFilter(new uint256(blockHash));

        var scriptPubKeys = Enumerable.Range(0, 1000).Select(x =>
        {
            var buffer = new byte[20];
            rnd.NextBytes(buffer);
            return(buffer);
        });
        var key = blockHash[0..16];
Example #5
0
        public async Task InitializeAsync(Config config, CcjRoundConfig roundConfig, RPCClient rpc)
        {
            Config      = Guard.NotNull(nameof(config), config);
            RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
            RpcClient   = Guard.NotNull(nameof(rpc), rpc);

            // Make sure RPC works.
            await AssertRpcNodeFullyInitializedAsync();

            // Make sure P2P works.
            await InitializeP2pAsync(config.Network, config.GetBitcoinCoreEndPoint());

            // Initialize index building
            var indexBuilderServiceDir = Path.Combine(DataDir, nameof(IndexBuilderService));
            var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
            var utxoSetFilePath        = Path.Combine(indexBuilderServiceDir, $"UtxoSet{RpcClient.Network}.dat");

            IndexBuilderService = new IndexBuilderService(RpcClient, TrustedNodeNotifyingBehavior, indexFilePath, utxoSetFilePath);
            Coordinator         = new CcjCoordinator(RpcClient.Network, TrustedNodeNotifyingBehavior, Path.Combine(DataDir, nameof(CcjCoordinator)), RpcClient, roundConfig);
            IndexBuilderService.Synchronize();
            Logger.LogInfo <IndexBuilderService>("IndexBuilderService is successfully initialized and started synchronization.");

            await Coordinator.MakeSureTwoRunningRoundsAsync();

            Logger.LogInfo <CcjCoordinator>("Chaumian CoinJoin Coordinator is successfully initialized and started two new rounds.");

            if (roundConfig.FilePath != null)
            {
                RoundConfigWatcher = new ConfigWatcher(RoundConfig);
                RoundConfigWatcher.Start(TimeSpan.FromSeconds(10), () =>
                {
                    try
                    {
                        Coordinator.UpdateRoundConfig(RoundConfig);

                        Coordinator.AbortAllRoundsInInputRegistration(nameof(ConfigWatcher), $"{nameof(RoundConfig)} has changed.");
                    }
                    catch (Exception ex)
                    {
                        Logger.LogDebug <ConfigWatcher>(ex);
                    }

                    return(Task.CompletedTask);
                });                 // Every 10 seconds check the config
                Logger.LogInfo <ConfigWatcher>($"{nameof(RoundConfigWatcher)} is successfully started.");
            }
        }
Example #6
0
        public async Task UnsynchronizedBitcoinNodeAsync()
        {
            var rpc = new MockRpcClient
            {
                OnGetBlockchainInfoAsync = () => Task.FromResult(new BlockchainInfo
                {
                    Headers = 0,
                    Blocks  = 0,
                    InitialBlockDownload = false
                }),
            };
            var blockNotifier = new BlockNotifier(TimeSpan.MaxValue, rpc);
            var indexer       = new IndexBuilderService(rpc, blockNotifier, ".");

            indexer.Synchronize();

            await Task.Delay(TimeSpan.FromSeconds(1));

            //// Assert.False(indexer.IsRunning);     // <------------ ERROR: it should have stopped but there is a bug for RegTest
            Assert.Throws <ArgumentOutOfRangeException>(() => indexer.GetLastFilter());             // There are no filters
        }
    public static FilterModel GetStartingFilter(Network network)
    {
        var startingHeader = SmartHeader.GetStartingHeader(network);

        if (network == Network.Main)
        {
            return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:02832810ec08a0:{startingHeader.PrevHash}:{startingHeader.EpochBlockTime}"));
        }
        else if (network == Network.TestNet)
        {
            return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:00000000000f0d5edcaeba823db17f366be49a80d91d15b77747c2e017b8c20a:{startingHeader.PrevHash}:{startingHeader.EpochBlockTime}"));
        }
        else if (network == Network.RegTest)
        {
            GolombRiceFilter filter = IndexBuilderService.CreateDummyEmptyFilter(startingHeader.BlockHash);
            return(FilterModel.FromLine($"{startingHeader.Height}:{startingHeader.BlockHash}:{filter}:{startingHeader.PrevHash}:{startingHeader.EpochBlockTime}"));
        }
        else
        {
            throw new NotSupportedNetworkException(network);
        }
    }
Example #8
0
        public async static Task InitializeAsync(Network network = null, string rpcuser = null, string rpcpassword = null, RPCClient rpc = null)
        {
            _dataDir = null;

            if (network != null || rpcuser != null || rpcpassword != null)
            {
                Config = new Config(network, rpcuser, rpcpassword);
            }
            else
            {
                await InitializeConfigAsync();
            }

            if (rpc != null)
            {
                RpcClient = rpc;
            }
            else
            {
                RpcClient = new RPCClient(
                    credentials: new RPCCredentialString
                {
                    UserPassword = new NetworkCredential(Config.BitcoinRpcUser, Config.BitcoinRpcPassword)
                },
                    network: Config.Network);
            }

            await AssertRpcNodeFullyInitializedAsync();

            var indexBuilderServiceDir = Path.Combine(DataDir, nameof(IndexBuilderService));
            var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
            var utxoSetFilePath        = Path.Combine(indexBuilderServiceDir, $"UtxoSet{RpcClient.Network}.dat");

            IndexBuilderService = new IndexBuilderService(RpcClient, indexFilePath, utxoSetFilePath);
            IndexBuilderService.Synchronize();
        }
Example #9
0
        public async Task FilterBuilderTestAsync()
        {
            (_, IRPCClient rpc, _, _, _, _, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            var indexBuilderServiceDir = Tests.Common.GetWorkDir();
            var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{rpc.Network}.dat");

            var indexBuilderService = new IndexBuilderService(rpc, global.HostedServices.FirstOrDefault <BlockNotifier>(), indexFilePath);

            try
            {
                indexBuilderService.Synchronize();

                // Test initial synchronization.
                var     times     = 0;
                uint256 firstHash = await rpc.GetBlockHashAsync(0);

                while (indexBuilderService.GetFilterLinesExcluding(firstHash, 101, out _).filters.Count() != 101)
                {
                    if (times > 500)                     // 30 sec
                    {
                        throw new TimeoutException($"{nameof(IndexBuilderService)} test timed out.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                // Test later synchronization.
                await rpc.GenerateAsync(10);

                times = 0;
                while (indexBuilderService.GetFilterLinesExcluding(firstHash, 111, out bool found5).filters.Count() != 111)
                {
                    Assert.True(found5);
                    if (times > 500)                     // 30 sec
                    {
                        throw new TimeoutException($"{nameof(IndexBuilderService)} test timed out.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                // Test correct number of filters is received.
                var hundredthHash = await rpc.GetBlockHashAsync(100);

                Assert.Equal(11, indexBuilderService.GetFilterLinesExcluding(hundredthHash, 11, out bool found).filters.Count());
                Assert.True(found);
                var bestHash = await rpc.GetBestBlockHashAsync();

                Assert.Empty(indexBuilderService.GetFilterLinesExcluding(bestHash, 1, out bool found2).filters);
                Assert.Empty(indexBuilderService.GetFilterLinesExcluding(uint256.Zero, 1, out bool found3).filters);
                Assert.False(found3);

                // Test filter block hashes are correct.
                var filters = indexBuilderService.GetFilterLinesExcluding(firstHash, 111, out bool found4).filters.ToArray();
                Assert.True(found4);
                for (int i = 0; i < 111; i++)
                {
                    var expectedHash = await rpc.GetBlockHashAsync(i + 1);

                    var filterModel = filters[i];
                    Assert.Equal(expectedHash, filterModel.Header.BlockHash);
                }
            }
            finally
            {
                if (indexBuilderService is { })
Example #10
0
        public async Task FilterDownloaderTestAsync()
        {
            (_, IRPCClient rpc, _, _, _, BitcoinStore bitcoinStore, _) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            var httpClientFactory = new HttpClientFactory(torEndPoint: null, backendUriGetter: () => new Uri(RegTestFixture.BackendEndPoint));
            var synchronizer      = new WasabiSynchronizer(rpc.Network, bitcoinStore, httpClientFactory);

            try
            {
                synchronizer.Start(requestInterval: TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), 1000);

                var blockCount = await rpc.GetBlockCountAsync() + 1;                 // Plus one because of the zeroth.

                // Test initial synchronization.
                var times = 0;
                int filterCount;
                while ((filterCount = bitcoinStore.SmartHeaderChain.HashCount) < blockCount)
                {
                    if (times > 500)                     // 30 sec
                    {
                        throw new TimeoutException($"{nameof(WasabiSynchronizer)} test timed out. Needed filters: {blockCount}, got only: {filterCount}.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                Assert.Equal(blockCount, bitcoinStore.SmartHeaderChain.HashCount);

                // Test later synchronization.
                await RegTestFixture.BackendRegTestNode.GenerateAsync(10);

                times = 0;
                while ((filterCount = bitcoinStore.SmartHeaderChain.HashCount) < blockCount + 10)
                {
                    if (times > 500)                     // 30 sec
                    {
                        throw new TimeoutException($"{nameof(WasabiSynchronizer)} test timed out. Needed filters: {blockCount + 10}, got only: {filterCount}.");
                    }
                    await Task.Delay(100);

                    times++;
                }

                // Test correct number of filters is received.
                Assert.Equal(blockCount + 10, bitcoinStore.SmartHeaderChain.HashCount);

                // Test filter block hashes are correct.
                var filterList = new List <FilterModel>();
                await bitcoinStore.IndexStore.ForeachFiltersAsync(async x =>
                {
                    filterList.Add(x);
                    await Task.CompletedTask;
                },
                                                                  new Height(0));

                FilterModel[] filters = filterList.ToArray();
                for (int i = 0; i < 101; i++)
                {
                    var expectedHash = await rpc.GetBlockHashAsync(i);

                    var filter = filters[i];
                    Assert.Equal(i, (int)filter.Header.Height);
                    Assert.Equal(expectedHash, filter.Header.BlockHash);
                    Assert.Equal(IndexBuilderService.CreateDummyEmptyFilter(expectedHash).ToString(), filter.Filter.ToString());
                }
            }
            finally
            {
                if (synchronizer is { })
 public ForceRunCommand(IndexBuilderService builder)
 {
     _builder = builder;
 }
Example #12
0
    public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, IRPCClient rpc, CancellationToken cancel)
    {
        Config      = Guard.NotNull(nameof(config), config);
        RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
        RpcClient   = Guard.NotNull(nameof(rpc), rpc);

        // Make sure RPC works.
        await AssertRpcNodeFullyInitializedAsync(cancel);

        // Make sure P2P works.
        await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);

        var p2pNode = Guard.NotNull(nameof(P2pNode), P2pNode);

        HostedServices.Register <MempoolMirror>(() => new MempoolMirror(TimeSpan.FromSeconds(21), RpcClient, p2pNode), "Full Node Mempool Mirror");

        // Initialize index building
        var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
        var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{RpcClient.Network}.dat");
        var blockNotifier          = HostedServices.Get <BlockNotifier>();

        CoordinatorParameters coordinatorParameters = new(DataDir);

        Coordinator = new(RpcClient.Network, blockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
        Coordinator.CoinJoinBroadcasted += Coordinator_CoinJoinBroadcasted;

        var coordinator = Guard.NotNull(nameof(Coordinator), Coordinator);

        if (!string.IsNullOrWhiteSpace(roundConfig.FilePath))
        {
            HostedServices.Register <ConfigWatcher>(() =>
                                                    new ConfigWatcher(
                                                        TimeSpan.FromSeconds(10), // Every 10 seconds check the config
                                                        RoundConfig,
                                                        () =>
            {
                try
                {
                    coordinator.RoundConfig.UpdateOrDefault(RoundConfig, toFile: false);

                    coordinator.AbortAllRoundsInInputRegistration($"{nameof(RoundConfig)} has changed.");
                }
                catch (Exception ex)
                {
                    Logger.LogDebug(ex);
                }
            }),
                                                    "Config Watcher");
        }

        CoinJoinIdStore = CoinJoinIdStore.Create(Coordinator.CoinJoinsFilePath, coordinatorParameters.CoinJoinIdStoreFilePath);
        var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(coordinatorParameters.CoinJoinScriptStoreFilePath);

        WabiSabiCoordinator = new WabiSabiCoordinator(coordinatorParameters, RpcClient, CoinJoinIdStore, coinJoinScriptStore);
        HostedServices.Register <WabiSabiCoordinator>(() => WabiSabiCoordinator, "WabiSabi Coordinator");
        HostedServices.Register <RoundBootstrapper>(() => new RoundBootstrapper(TimeSpan.FromMilliseconds(100), Coordinator), "Round Bootstrapper");

        await HostedServices.StartAllAsync(cancel);

        IndexBuilderService = new(RpcClient, blockNotifier, indexFilePath);
        IndexBuilderService.Synchronize();
        Logger.LogInfo($"{nameof(IndexBuilderService)} is successfully initialized and started synchronization.");
    }
Example #13
0
        public async Task FilterBuilderTestAsync()
        {
            using (var builder = await NodeBuilder.CreateAsync())
            {
                await builder.CreateNodeAsync();

                await builder.StartAllAsync();

                CoreNode regtestNode = builder.Nodes[0];
                regtestNode.Generate(101);
                RPCClient rpc = regtestNode.CreateRpcClient();

                var indexBuilderServiceDir = Path.Combine(SharedFixture.DataDir, nameof(IndexBuilderService));
                var indexFilePath          = Path.Combine(indexBuilderServiceDir, $"Index{rpc.Network}.dat");
                var utxoSetFilePath        = Path.Combine(indexBuilderServiceDir, $"UtxoSet{rpc.Network}.dat");

                var indexBuilderService = new IndexBuilderService(rpc, indexFilePath, utxoSetFilePath);
                try
                {
                    indexBuilderService.Synchronize();

                    // Test initial synchronization.
                    var     times     = 0;
                    uint256 firstHash = await rpc.GetBlockHashAsync(0);

                    while (indexBuilderService.GetFilterLinesExcluding(firstHash, 102, out _).filters.Count() != 101)
                    {
                        if (times > 500)                         // 30 sec
                        {
                            throw new TimeoutException($"{nameof(IndexBuilderService)} test timed out.");
                        }
                        await Task.Delay(100);

                        times++;
                    }

                    // Test later synchronization.
                    regtestNode.Generate(10);
                    times = 0;
                    while (indexBuilderService.GetFilterLinesExcluding(firstHash, 112, out bool found5).filters.Count() != 111)
                    {
                        Assert.True(found5);
                        if (times > 500)                         // 30 sec
                        {
                            throw new TimeoutException($"{nameof(IndexBuilderService)} test timed out.");
                        }
                        await Task.Delay(100);

                        times++;
                    }

                    // Test correct number of filters is received.
                    var hundredthHash = await rpc.GetBlockHashAsync(100);

                    Assert.Equal(11, indexBuilderService.GetFilterLinesExcluding(hundredthHash, 12, out bool found).filters.Count());
                    Assert.True(found);
                    var bestHash = await rpc.GetBestBlockHashAsync();

                    Assert.Empty(indexBuilderService.GetFilterLinesExcluding(bestHash, 1, out bool found2).filters);
                    Assert.True(found2);
                    Assert.Empty(indexBuilderService.GetFilterLinesExcluding(uint256.Zero, 1, out bool found3).filters);
                    Assert.False(found3);

                    // Test filter block hashes are correct.
                    var filters = indexBuilderService.GetFilterLinesExcluding(firstHash, 112, out bool found4).filters.ToArray();
                    Assert.True(found4);
                    for (int i = 0; i < 111; i++)
                    {
                        var expectedHash = await rpc.GetBlockHashAsync(i + 1);

                        var filterModel = FilterModel.FromLine(filters[i], i);
                        Assert.Equal(expectedHash, filterModel.BlockHash);
                        Assert.Null(filterModel.Filter);
                    }
                }
                finally
                {
                    if (indexBuilderService != null)
                    {
                        await indexBuilderService.StopAsync();
                    }
                }
            }
        }
Example #14
0
 public RemoveSourceCommand(Settings settings, IndexBuilderService indexBuilder)
 {
     _settings     = settings;
     _indexBuilder = indexBuilder;
 }
 public AddSourceCommand(Logger logger, Settings settings, IndexBuilderService indexBuilder)
 {
     _logger       = logger;
     _settings     = settings;
     _indexBuilder = indexBuilder;
 }
Example #16
0
        public async Task ReorgTestAsync()
        {
            (string password, IRPCClient rpc, Network network, _, _, BitcoinStore bitcoinStore, Backend.Global global) = await Common.InitializeTestEnvironmentAsync(RegTestFixture, 1);

            var keyManager = KeyManager.CreateNew(out _, password, network);

            // Mine some coins, make a few bech32 transactions then make it confirm.
            await rpc.GenerateAsync(1);

            var key = keyManager.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, isInternal: false);
            var tx2 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

            key = keyManager.GenerateNewKey(SmartLabel.Empty, KeyState.Clean, isInternal: false);
            var tx3 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m));

            var tx4 = await rpc.SendToAddressAsync(key.GetP2pkhAddress(network), Money.Coins(0.1m));

            var tx5 = await rpc.SendToAddressAsync(key.GetP2shOverP2wpkhAddress(network), Money.Coins(0.1m));

            var tx1 = await rpc.SendToAddressAsync(key.GetP2wpkhAddress(network), Money.Coins(0.1m), replaceable : true);

            await rpc.GenerateAsync(2);             // Generate two, so we can test for two reorg

            var node = RegTestFixture.BackendRegTestNode;

            using HttpClientFactory httpClientFactory = new(torEndPoint : null, backendUriGetter : () => new Uri(RegTestFixture.BackendEndPoint));
            WasabiSynchronizer synchronizer = new(bitcoinStore, httpClientFactory);

            try
            {
                synchronizer.Start(requestInterval: TimeSpan.FromSeconds(3), 1000);

                var reorgAwaiter = new EventsAwaiter <FilterModel>(
                    h => bitcoinStore.IndexStore.Reorged += h,
                    h => bitcoinStore.IndexStore.Reorged -= h,
                    2);

                // Test initial synchronization.
                await WaitForIndexesToSyncAsync(global, TimeSpan.FromSeconds(90), bitcoinStore);

                var tip = await rpc.GetBestBlockHashAsync();

                Assert.Equal(tip, bitcoinStore.SmartHeaderChain.TipHash);
                var tipBlock = await rpc.GetBlockHeaderAsync(tip);

                Assert.Equal(tipBlock.HashPrevBlock, bitcoinStore.SmartHeaderChain.GetChain().Select(x => x.header.BlockHash).ToArray()[bitcoinStore.SmartHeaderChain.HashCount - 2]);

                // Test synchronization after fork.
                await rpc.InvalidateBlockAsync(tip);                 // Reorg 1

                tip = await rpc.GetBestBlockHashAsync();

                await rpc.InvalidateBlockAsync(tip);                 // Reorg 2

                var tx1bumpRes = await rpc.BumpFeeAsync(tx1);        // RBF it

                await rpc.GenerateAsync(5);
                await WaitForIndexesToSyncAsync(global, TimeSpan.FromSeconds(90), bitcoinStore);

                var hashes = bitcoinStore.SmartHeaderChain.GetChain().Select(x => x.header.BlockHash).ToArray();
                Assert.DoesNotContain(tip, hashes);
                Assert.DoesNotContain(tipBlock.HashPrevBlock, hashes);

                tip = await rpc.GetBestBlockHashAsync();

                Assert.Equal(tip, bitcoinStore.SmartHeaderChain.TipHash);

                var filterList = new List <FilterModel>();
                await bitcoinStore.IndexStore.ForeachFiltersAsync(async x =>
                {
                    filterList.Add(x);
                    await Task.CompletedTask;
                },
                                                                  new Height(0));

                var filterTip = filterList.Last();
                Assert.Equal(tip, filterTip.Header.BlockHash);

                // Test filter block hashes are correct after fork.
                var blockCountIncludingGenesis = await rpc.GetBlockCountAsync() + 1;

                filterList.Clear();
                await bitcoinStore.IndexStore.ForeachFiltersAsync(async x =>
                {
                    filterList.Add(x);
                    await Task.CompletedTask;
                },
                                                                  new Height(0));

                FilterModel[] filters = filterList.ToArray();
                for (int i = 0; i < blockCountIncludingGenesis; i++)
                {
                    var expectedHash = await rpc.GetBlockHashAsync(i);

                    var filter = filters[i];
                    Assert.Equal(i, (int)filter.Header.Height);
                    Assert.Equal(expectedHash, filter.Header.BlockHash);
                    if (i < 101)                     // Later other tests may fill the filter.
                    {
                        Assert.Equal(IndexBuilderService.CreateDummyEmptyFilter(expectedHash).ToString(), filter.Filter.ToString());
                    }
                }

                // Test the serialization, too.
                tip = await rpc.GetBestBlockHashAsync();

                var blockHash = tip;
                for (var i = 0; i < hashes.Length; i++)
                {
                    var block = await rpc.GetBlockHeaderAsync(blockHash);

                    Assert.Equal(blockHash, hashes[hashes.Length - i - 1]);
                    blockHash = block.HashPrevBlock;
                }

                // Assert reorg happened exactly as many times as we reorged.
                await reorgAwaiter.WaitAsync(TimeSpan.FromSeconds(10));
            }
            finally
            {
                await synchronizer.StopAsync();
            }
        }