public void Synchronize()
        {
            Task.Run(async() =>
            {
                try
                {
                    if (Interlocked.Read(ref _runner) >= 2)
                    {
                        return;
                    }

                    Interlocked.Increment(ref _runner);
                    while (Interlocked.Read(ref _runner) != 1)
                    {
                        await Task.Delay(100);
                    }

                    if (Interlocked.Read(ref _running) >= 2)
                    {
                        return;
                    }

                    try
                    {
                        Interlocked.Exchange(ref _running, 1);

                        var isImmature    = false;                      // The last 100 blocks are reorgable. (Assume it is mature at first.)
                        SyncInfo syncInfo = null;
                        while (IsRunning)
                        {
                            try
                            {
                                // If we did not yet initialized syncInfo, do so.
                                if (syncInfo is null)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                uint heightToRequest = StartingHeight;
                                uint256 currentHash  = null;
                                using (await IndexLock.LockAsync())
                                {
                                    if (Index.Count != 0)
                                    {
                                        var lastIndex   = Index.Last();
                                        heightToRequest = lastIndex.Header.Height + 1;
                                        currentHash     = lastIndex.Header.BlockHash;
                                    }
                                }

                                // If not synchronized or already 5 min passed since last update, get the latest blockchain info.
                                if (!syncInfo.IsCoreSynchornized || DateTimeOffset.UtcNow - syncInfo.BlockchainInfoUpdated > TimeSpan.FromMinutes(5))
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                if (syncInfo.BlockCount - heightToRequest <= 100)
                                {
                                    // Both Wasabi and our Core node is in sync. Start doing stuff through P2P from now on.
                                    if (syncInfo.IsCoreSynchornized && syncInfo.BlockCount == heightToRequest - 1)
                                    {
                                        syncInfo = await GetSyncInfoAsync();
                                        // Double it to make sure not to accidentally miss any notification.
                                        if (syncInfo.IsCoreSynchornized && syncInfo.BlockCount == heightToRequest - 1)
                                        {
                                            // Mark the process notstarted, so it can be started again and finally block can mark it is stopped.
                                            Interlocked.Exchange(ref _running, 0);
                                            return;
                                        }
                                    }

                                    // Mark the synchronizing process is working with immature blocks from now on.
                                    isImmature = true;
                                }

                                Block block = await RpcClient.GetBlockAsync(heightToRequest);

                                // Reorg check, except if we're requesting the starting height, because then the "currentHash" wouldn't exist.
                                if (heightToRequest != StartingHeight && currentHash != block.Header.HashPrevBlock)
                                {
                                    // Reorg can happen only when immature. (If it'd not be immature, that'd be a huge issue.)
                                    if (isImmature)
                                    {
                                        await ReorgOneAsync();
                                    }
                                    else
                                    {
                                        Logger.LogCritical("This is something serious! Over 100 block reorg is noticed! We cannot handle that!");
                                    }

                                    // Skip the current block.
                                    continue;
                                }

                                if (isImmature)
                                {
                                    PrepareBech32UtxoSetHistory();
                                }

                                var scripts = new HashSet <Script>();

                                foreach (var tx in block.Transactions)
                                {
                                    // If stop was requested return.
                                    // Because this tx iteration can take even minutes
                                    // It does not need to be accessed with a thread safe fashion with Interlocked through IsRunning, this may have some performance benefit
                                    if (_running != 1)
                                    {
                                        return;
                                    }

                                    for (int i = 0; i < tx.Outputs.Count; i++)
                                    {
                                        var output = tx.Outputs[i];
                                        if (output.ScriptPubKey.IsScriptType(ScriptType.P2WPKH))
                                        {
                                            var outpoint  = new OutPoint(tx.GetHash(), i);
                                            var utxoEntry = new UtxoEntry(outpoint, output.ScriptPubKey);
                                            Bech32UtxoSet.Add(outpoint, utxoEntry);
                                            if (isImmature)
                                            {
                                                Bech32UtxoSetHistory.Last().StoreAction(Operation.Add, outpoint, output.ScriptPubKey);
                                            }
                                            scripts.Add(output.ScriptPubKey);
                                        }
                                    }

                                    foreach (var input in tx.Inputs)
                                    {
                                        OutPoint prevOut = input.PrevOut;
                                        if (Bech32UtxoSet.TryGetValue(prevOut, out UtxoEntry foundUtxoEntry))
                                        {
                                            var foundScript = foundUtxoEntry.Script;
                                            Bech32UtxoSet.Remove(prevOut);
                                            if (isImmature)
                                            {
                                                Bech32UtxoSetHistory.Last().StoreAction(Operation.Remove, prevOut, foundScript);
                                            }
                                            scripts.Add(foundScript);
                                        }
                                    }
                                }

                                GolombRiceFilter filter;
                                if (scripts.Any())
                                {
                                    filter = new GolombRiceFilterBuilder()
                                             .SetKey(block.GetHash())
                                             .SetP(20)
                                             .SetM(1 << 20)
                                             .AddEntries(scripts.Select(x => x.ToCompressedBytes()))
                                             .Build();
                                }
                                else
                                {
                                    // We cannot have empty filters, because there was a bug in GolombRiceFilterBuilder that evaluates empty filters to true.
                                    // And this must be fixed in a backwards compatible way, so we create a fake filter with a random scp instead.
                                    filter = CreateDummyEmptyFilter(block.GetHash());
                                }

                                var smartHeader = new SmartHeader(block.GetHash(), block.Header.HashPrevBlock, heightToRequest, block.Header.BlockTime);
                                var filterModel = new FilterModel(smartHeader, filter);

                                await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
                                using (await IndexLock.LockAsync())
                                {
                                    Index.Add(filterModel);
                                }
                                if (File.Exists(Bech32UtxoSetFilePath))
                                {
                                    File.Delete(Bech32UtxoSetFilePath);
                                }
                                var bech32UtxoSetLines = Bech32UtxoSet.Select(entry => entry.Value.Line);

                                // Keep it sync unless you fix the performance issue with async.
                                File.WriteAllLines(Bech32UtxoSetFilePath, bech32UtxoSetLines);

                                // If not close to the tip, just log debug.
                                // Use height.Value instead of simply height, because it cannot be negative height.
                                if (syncInfo.BlockCount - heightToRequest <= 3 || heightToRequest % 100 == 0)
                                {
                                    Logger.LogInfo($"Created filter for block: {heightToRequest}.");
                                }
                                else
                                {
                                    Logger.LogDebug($"Created filter for block: {heightToRequest}.");
                                }
                            }
                            catch (Exception ex)
                            {
                                Logger.LogDebug(ex);
                            }
                        }
                    }
                    finally
                    {
                        Interlocked.CompareExchange(ref _running, 3, 2);                         // If IsStopping, make it stopped.
                        Interlocked.Decrement(ref _runner);
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError($"Synchronization attempt failed to start: {ex}");
                }
            });
        }
Beispiel #2
0
        public void Synchronize()
        {
            Interlocked.Exchange(ref _running, 1);

            Task.Run(async() =>
            {
                try
                {
                    var blockCount = await RpcClient.GetBlockCountAsync();
                    var isIIB      = true;                // Initial Index Building phase

                    while (IsRunning)
                    {
                        try
                        {
                            // If stop was requested return.
                            if (IsRunning == false)
                            {
                                return;
                            }

                            var height       = StartingHeight;
                            uint256 prevHash = null;
                            using (await IndexLock.LockAsync())
                            {
                                if (Index.Count != 0)
                                {
                                    var lastIndex = Index.Last();
                                    height        = lastIndex.BlockHeight + 1;
                                    prevHash      = lastIndex.BlockHash;
                                }
                            }

                            if (blockCount - (int)height <= 100)
                            {
                                isIIB = false;
                            }

                            Block block = null;
                            try
                            {
                                block = await RpcClient.GetBlockAsync(height);
                            }
                            catch (RPCException)                             // if the block didn't come yet
                            {
                                await Task.Delay(1000);
                                continue;
                            }

                            if (prevHash != null)
                            {
                                // In case of reorg:
                                if (prevHash != block.Header.HashPrevBlock && !isIIB)                                 // There is no reorg in IIB
                                {
                                    Logger.LogInfo <IndexBuilderService>($"REORG Invalid Block: {prevHash}");
                                    // 1. Rollback index
                                    using (await IndexLock.LockAsync())
                                    {
                                        Index.RemoveLast();
                                    }

                                    // 2. Serialize Index. (Remove last line.)
                                    var lines = File.ReadAllLines(IndexFilePath);
                                    File.WriteAllLines(IndexFilePath, lines.Take(lines.Length - 1).ToArray());

                                    // 3. Rollback Bech32UtxoSet
                                    if (Bech32UtxoSetHistory.Count != 0)
                                    {
                                        Bech32UtxoSetHistory.Last().Rollback(Bech32UtxoSet);                                         // The Bech32UtxoSet MUST be recovered to its previous state.
                                        Bech32UtxoSetHistory.RemoveLast();

                                        // 4. Serialize Bech32UtxoSet.
                                        await File.WriteAllLinesAsync(Bech32UtxoSetFilePath, Bech32UtxoSet
                                                                      .Select(entry => entry.Key.Hash + ":" + entry.Key.N + ":" + ByteHelpers.ToHex(entry.Value.ToCompressedBytes())));
                                    }

                                    // 5. Skip the current block.
                                    continue;
                                }
                            }

                            if (!isIIB)
                            {
                                if (Bech32UtxoSetHistory.Count >= 100)
                                {
                                    Bech32UtxoSetHistory.RemoveFirst();
                                }
                                Bech32UtxoSetHistory.Add(new ActionHistoryHelper());
                            }

                            var scripts = new HashSet <Script>();

                            foreach (var tx in block.Transactions)
                            {
                                for (int i = 0; i < tx.Outputs.Count; i++)
                                {
                                    var output = tx.Outputs[i];
                                    if (!output.ScriptPubKey.IsPayToScriptHash && output.ScriptPubKey.IsWitness)
                                    {
                                        var outpoint = new OutPoint(tx.GetHash(), i);
                                        Bech32UtxoSet.Add(outpoint, output.ScriptPubKey);
                                        if (!isIIB)
                                        {
                                            Bech32UtxoSetHistory.Last().StoreAction(ActionHistoryHelper.Operation.Add, outpoint, output.ScriptPubKey);
                                        }
                                        scripts.Add(output.ScriptPubKey);
                                    }
                                }

                                foreach (var input in tx.Inputs)
                                {
                                    var found = Bech32UtxoSet.SingleOrDefault(x => x.Key == input.PrevOut);
                                    if (found.Key != default)
                                    {
                                        Script val = Bech32UtxoSet[input.PrevOut];
                                        Bech32UtxoSet.Remove(input.PrevOut);
                                        if (!isIIB)
                                        {
                                            Bech32UtxoSetHistory.Last().StoreAction(ActionHistoryHelper.Operation.Remove, input.PrevOut, val);
                                        }
                                        scripts.Add(found.Value);
                                    }
                                }
                            }

                            // https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki
                            // The parameter k MUST be set to the first 16 bytes of the hash of the block for which the filter
                            // is constructed.This ensures the key is deterministic while still varying from block to block.
                            var key = block.GetHash().ToBytes().Take(16).ToArray();

                            GolombRiceFilter filter = null;
                            if (scripts.Count != 0)
                            {
                                filter = GolombRiceFilter.Build(key, scripts.Select(x => x.ToCompressedBytes()));
                            }

                            var filterModel = new FilterModel
                            {
                                BlockHash   = block.GetHash(),
                                BlockHeight = height,
                                Filter      = filter
                            };

                            await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
                            using (await IndexLock.LockAsync())
                            {
                                Index.Add(filterModel);
                            }
                            if (File.Exists(Bech32UtxoSetFilePath))
                            {
                                File.Delete(Bech32UtxoSetFilePath);
                            }
                            await File.WriteAllLinesAsync(Bech32UtxoSetFilePath, Bech32UtxoSet
                                                          .Select(entry => entry.Key.Hash + ":" + entry.Key.N + ":" + ByteHelpers.ToHex(entry.Value.ToCompressedBytes())));

                            if (blockCount - height <= 3 || height % 100 == 0)                             // If not close to the tip, just log debug.
                            {
                                Logger.LogInfo <IndexBuilderService>($"Created filter for block: {height}.");
                            }
                            else
                            {
                                Logger.LogDebug <IndexBuilderService>($"Created filter for block: {height}.");
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogDebug <IndexBuilderService>(ex);
                        }
                    }
                }
                finally
                {
                    if (IsStopping)
                    {
                        Interlocked.Exchange(ref _running, 3);
                    }
                }
            });
        }
Beispiel #3
0
 /// <summary>
 /// There was a bug when the server was sending wrong filters to the users for 24h. This function detects if the software tries to process the first wrong filter.
 /// </summary>
 private bool IsWrongFilter(FilterModel filter) =>
 Network == Network.Main && filter.Header.Height == 627278 && filter.ToLine() == "627278:00000000000000000002edea14cfbbcde1cdb6a14275d9ad36491aa5d8862747:fd4b018270de28c44316c049e4e4050d8a7de5746c7bb31d093831c56196e5f5464956c9ab7142b998a93cb80149b09353cb5d46dfeb44ecb0c8255fb0eb64247a405ab2305713e3418707be4fe87286b467ac86603749aeeac6c182022f0105b6c547b22b89608d0b57eaee2767150bff2354e4cdecef069d1a7f9356e5972ac7323c907b2e42775d032b4a12cc45e96eaa86d232e14808beca91f21c09003734bf77005d2dbfdfeaf19108e971f99b91046db0a021a128bb17b91c83766c65e924bb48af50c473f80e8e8569fd68aaba856b9e4f60efba08519d4ca0f1c0453e60f50a86398b11c860607f049e2bc5e1b6201470f5383601fcfbbea766f510768bac3145dc33443131e50d41bdfb92f5b3d9affc0bbaa85a4c40be2d9e582c3ca0c82251d191ec83dbd197cc1a9f070e6754d84c8ca1c0258d21264619cb523a9bda5556aded4f82e9a8955180c8c8772304bb5f2a5498d15f28b3f0d5d0b22aba14a18be7c8a100bb35b73385ce2b410126ac614f2260557444c3279b73dab148cd14e8800815a1248fa802901a4430817b59d936ceb3e1594d002c1b8d88ec8641f2b2d9827a42948c61c888fc32f070eeba19dda8f773c6cef04485575652f3e929507a9e24dcee53bdc548a317f1e019fbc7ac87c5314548cca6c0b85ebbca79bed2685ed7024a21349189d9a6c92b05aa53a7e241b6885575a19bd737040c263ac05b9920d2e31568afff3c545a827338e103096fbd8fb60ef317e55146b74260577064627bba812c7ca06c39b45d291d7bb9142c338012ccb97330873a0e256ca8aaff778348085e1c9e9942cd10a8444f0c708a798c1d701b4e1879d78ee51f3044ee0012e9929c6e5bfddba40ed04872065373af111ebe53a832f5563078ef274cd39a6b77c8155d8996b6a5617c2ff447dcf4a37a84bfbd1ab34b8a4012f0ccb82c8085668a52e722f8a59a63a07420d2fc67a4da39209fc0cdcd335b2b4670817218f92aee62c8d0e3e895d7aa0f3c69ba36687c9559cf38adfef8ef0ec90128d1efc3b69006ed2c026a1a904bdd1bc0aa1924c74e05b4fdd8316a4cc400d9ced30eaf0ed01f82a6ab59bdf1fbd7a7c6f7186e33411140b57673a0075946902c5890e5647df67183a84f5b2001be152a23741582b529116e2d3bd9964968b40080173e5339018edf609199f25021c757ff3b8d1add3731002784c4da7176cd8b201e3931c61272d17e4e58a2487666510889935d054f0b72817700:000000000000000000028dbd5c398fa064b00e07805af0a8806e6f3b6ffce2c0:1587639149";
Beispiel #4
0
        public void Synchronize()
        {
            Task.Run(async() =>
            {
                try
                {
                    if (Interlocked.Read(ref _workerCount) >= 2)
                    {
                        return;
                    }

                    Interlocked.Increment(ref _workerCount);
                    while (Interlocked.Read(ref _workerCount) != 1)
                    {
                        await Task.Delay(100);
                    }

                    if (IsStopping)
                    {
                        return;
                    }

                    try
                    {
                        Interlocked.Exchange(ref _serviceStatus, Running);

                        SyncInfo syncInfo = null;
                        while (IsRunning)
                        {
                            try
                            {
                                // If we did not yet initialized syncInfo, do so.
                                syncInfo ??= await GetSyncInfoAsync().ConfigureAwait(false);

                                uint currentHeight  = 0;
                                uint256 currentHash = null;
                                using (await IndexLock.LockAsync())
                                {
                                    if (Index.Count != 0)
                                    {
                                        var lastIndex = Index[^ 1];
                                        currentHeight = lastIndex.Header.Height;
                                        currentHash   = lastIndex.Header.BlockHash;
                                    }
                                    else
                                    {
                                        currentHash = StartingHeight == 0
                                                                                        ? uint256.Zero
                                                                                        : await RpcClient.GetBlockHashAsync((int)StartingHeight - 1);
                                        currentHeight = StartingHeight - 1;
                                    }
                                }

                                var coreNotSynced   = !syncInfo.IsCoreSynchornized;
                                var tipReached      = syncInfo.BlockCount == currentHeight;
                                var isTimeToRefresh = DateTimeOffset.UtcNow - syncInfo.BlockchainInfoUpdated > TimeSpan.FromMinutes(5);
                                if (coreNotSynced || tipReached || isTimeToRefresh)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                // If wasabi filter height is the same as core we may be done.
                                if (syncInfo.BlockCount == currentHeight)
                                {
                                    // Check that core is fully synced
                                    if (syncInfo.IsCoreSynchornized && !syncInfo.InitialBlockDownload)
                                    {
                                        // Mark the process notstarted, so it can be started again
                                        // and finally block can mark it as stopped.
                                        Interlocked.Exchange(ref _serviceStatus, NotStarted);
                                        return;
                                    }
                                    else
                                    {
                                        // Knots is catching up give it a 10 seconds
                                        await Task.Delay(10000);
                                        continue;
                                    }
                                }

                                uint nextHeight        = currentHeight + 1;
                                uint256 blockHash      = await RpcClient.GetBlockHashAsync((int)nextHeight);
                                VerboseBlockInfo block = await RpcClient.GetVerboseBlockAsync(blockHash);

                                // Check if we are still on the best chain,
                                // if not rewind filters till we find the fork.
                                if (currentHash != block.PrevBlockHash)
                                {
                                    Logger.LogWarning("Reorg observed on the network.");

                                    await ReorgOneAsync();

                                    // Skip the current block.
                                    continue;
                                }

                                var scripts = FetchScripts(block);

                                GolombRiceFilter filter;
                                if (scripts.Any())
                                {
                                    filter = new GolombRiceFilterBuilder()
                                             .SetKey(block.Hash)
                                             .SetP(20)
                                             .SetM(1 << 20)
                                             .AddEntries(scripts.Select(x => x.ToCompressedBytes()))
                                             .Build();
                                }
                                else
                                {
                                    // We cannot have empty filters, because there was a bug in GolombRiceFilterBuilder that evaluates empty filters to true.
                                    // And this must be fixed in a backwards compatible way, so we create a fake filter with a random scp instead.
                                    filter = CreateDummyEmptyFilter(block.Hash);
                                }

                                var smartHeader = new SmartHeader(block.Hash, block.PrevBlockHash, nextHeight, block.BlockTime);
                                var filterModel = new FilterModel(smartHeader, filter);

                                await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });

                                using (await IndexLock.LockAsync())
                                {
                                    Index.Add(filterModel);
                                }

                                // If not close to the tip, just log debug.
                                if (syncInfo.BlockCount - nextHeight <= 3 || nextHeight % 100 == 0)
                                {
                                    Logger.LogInfo($"Created filter for block: {nextHeight}.");
                                }
                                else
                                {
                                    Logger.LogDebug($"Created filter for block: {nextHeight}.");
                                }
                                LastFilterBuildTime = DateTimeOffset.UtcNow;
                            }
    /// <inheritdoc />
    public override void WriteJson(JsonWriter writer, FilterModel?value, JsonSerializer serializer)
    {
        var filterModel = value?.ToLine() ?? throw new ArgumentNullException(nameof(value));

        writer.WriteValue(filterModel);
    }
        public void Synchronize()
        {
            Interlocked.Exchange(ref _running, 1);

            Task.Run(async() =>
            {
                try
                {
                    var blockCount = await RpcClient.GetBlockCountAsync();
                    var isIIB      = true;                // Initial Index Building phase

                    while (IsRunning)
                    {
                        try
                        {
                            // If stop was requested return.
                            if (IsRunning == false)
                            {
                                return;
                            }

                            Height height    = StartingHeight;
                            uint256 prevHash = null;
                            using (await IndexLock.LockAsync())
                            {
                                if (Index.Count != 0)
                                {
                                    var lastIndex = Index.Last();
                                    height        = lastIndex.BlockHeight + 1;
                                    prevHash      = lastIndex.BlockHash;
                                }
                            }

                            if (blockCount - height <= 100)
                            {
                                isIIB = false;
                            }

                            Block block = null;
                            try
                            {
                                block = await RpcClient.GetBlockAsync(height);
                            }
                            catch (RPCException)                             // if the block didn't come yet
                            {
                                await Task.Delay(1000);
                                continue;
                            }

                            if (blockCount - height <= 2)
                            {
                                NewBlock?.Invoke(this, block);
                            }

                            if (prevHash != null)
                            {
                                // In case of reorg:
                                if (prevHash != block.Header.HashPrevBlock && !isIIB)                                 // There is no reorg in IIB
                                {
                                    Logger.LogInfo <IndexBuilderService>($"REORG Invalid Block: {prevHash}");
                                    // 1. Rollback index
                                    using (await IndexLock.LockAsync())
                                    {
                                        Index.RemoveLast();
                                    }

                                    // 2. Serialize Index. (Remove last line.)
                                    var lines = File.ReadAllLines(IndexFilePath);
                                    File.WriteAllLines(IndexFilePath, lines.Take(lines.Length - 1).ToArray());

                                    // 3. Rollback Bech32UtxoSet
                                    if (Bech32UtxoSetHistory.Count != 0)
                                    {
                                        Bech32UtxoSetHistory.Last().Rollback(Bech32UtxoSet);                                         // The Bech32UtxoSet MUST be recovered to its previous state.
                                        Bech32UtxoSetHistory.RemoveLast();

                                        // 4. Serialize Bech32UtxoSet.
                                        await File.WriteAllLinesAsync(Bech32UtxoSetFilePath, Bech32UtxoSet
                                                                      .Select(entry => entry.Key.Hash + ":" + entry.Key.N + ":" + ByteHelpers.ToHex(entry.Value.ToCompressedBytes())));
                                    }

                                    // 5. Skip the current block.
                                    continue;
                                }
                            }

                            if (!isIIB)
                            {
                                if (Bech32UtxoSetHistory.Count >= 100)
                                {
                                    Bech32UtxoSetHistory.RemoveFirst();
                                }
                                Bech32UtxoSetHistory.Add(new ActionHistoryHelper());
                            }

                            var scripts = new HashSet <Script>();

                            foreach (var tx in block.Transactions)
                            {
                                // If stop was requested return.
                                // Because this tx iteration can take even minutes
                                // It doesn't need to be accessed with a thread safe fasion with Interlocked through IsRunning, this may have some performance benefit
                                if (_running != 1)
                                {
                                    return;
                                }

                                for (int i = 0; i < tx.Outputs.Count; i++)
                                {
                                    var output = tx.Outputs[i];
                                    if (!output.ScriptPubKey.IsPayToScriptHash && output.ScriptPubKey.IsWitness)
                                    {
                                        var outpoint = new OutPoint(tx.GetHash(), i);
                                        Bech32UtxoSet.Add(outpoint, output.ScriptPubKey);
                                        if (!isIIB)
                                        {
                                            Bech32UtxoSetHistory.Last().StoreAction(ActionHistoryHelper.Operation.Add, outpoint, output.ScriptPubKey);
                                        }
                                        scripts.Add(output.ScriptPubKey);
                                    }
                                }

                                foreach (var input in tx.Inputs)
                                {
                                    OutPoint prevOut = input.PrevOut;
                                    if (Bech32UtxoSet.TryGetValue(prevOut, out Script foundScript))
                                    {
                                        Bech32UtxoSet.Remove(prevOut);
                                        if (!isIIB)
                                        {
                                            Bech32UtxoSetHistory.Last().StoreAction(ActionHistoryHelper.Operation.Remove, prevOut, foundScript);
                                        }
                                        scripts.Add(foundScript);
                                    }
                                }
                            }

                            GolombRiceFilter filter = null;
                            if (scripts.Count != 0)
                            {
                                filter = new GolombRiceFilterBuilder()
                                         .SetKey(block.GetHash())
                                         .SetP(20)
                                         .SetM(1 << 20)
                                         .AddEntries(scripts.Select(x => x.ToCompressedBytes()))
                                         .Build();
                            }

                            var filterModel = new FilterModel
                            {
                                BlockHash   = block.GetHash(),
                                BlockHeight = height,
                                Filter      = filter
                            };

                            await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() });
                            using (await IndexLock.LockAsync())
                            {
                                Index.Add(filterModel);
                            }
                            if (File.Exists(Bech32UtxoSetFilePath))
                            {
                                File.Delete(Bech32UtxoSetFilePath);
                            }
                            await File.WriteAllLinesAsync(Bech32UtxoSetFilePath, Bech32UtxoSet
                                                          .Select(entry => entry.Key.Hash + ":" + entry.Key.N + ":" + ByteHelpers.ToHex(entry.Value.ToCompressedBytes())));

                            // If not close to the tip, just log debug.
                            // Use height.Value instead of simply height, because it cannot be negative height.
                            if (blockCount - height.Value <= 3 || height % 100 == 0)
                            {
                                Logger.LogInfo <IndexBuilderService>($"Created filter for block: {height}.");
                            }
                            else
                            {
                                Logger.LogDebug <IndexBuilderService>($"Created filter for block: {height}.");
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogDebug <IndexBuilderService>(ex);
                        }
                    }
                }
                finally
                {
                    if (IsStopping)
                    {
                        Interlocked.Exchange(ref _running, 3);
                    }
                }
            });
        }
    public void Synchronize()
    {
        Task.Run(async() =>
        {
            try
            {
                if (Interlocked.Read(ref _workerCount) >= 2)
                {
                    return;
                }

                Interlocked.Increment(ref _workerCount);
                while (Interlocked.Read(ref _workerCount) != 1)
                {
                    await Task.Delay(100).ConfigureAwait(false);
                }

                if (IsStopping)
                {
                    return;
                }

                try
                {
                    Interlocked.Exchange(ref _serviceStatus, Running);

                    while (IsRunning)
                    {
                        try
                        {
                            SyncInfo syncInfo = await GetSyncInfoAsync().ConfigureAwait(false);

                            uint currentHeight;
                            uint256?currentHash = null;
                            using (await IndexLock.LockAsync())
                            {
                                if (Index.Count != 0)
                                {
                                    var lastIndex = Index[^ 1];
                                    currentHeight = lastIndex.Header.Height;
                                    currentHash   = lastIndex.Header.BlockHash;
                                }
                                else
                                {
                                    currentHash = StartingHeight == 0
                                                                                ? uint256.Zero
                                                                                : await RpcClient.GetBlockHashAsync((int)StartingHeight - 1).ConfigureAwait(false);
                                    currentHeight = StartingHeight - 1;
                                }
                            }

                            var coreNotSynced   = !syncInfo.IsCoreSynchronized;
                            var tipReached      = syncInfo.BlockCount == currentHeight;
                            var isTimeToRefresh = DateTimeOffset.UtcNow - syncInfo.BlockchainInfoUpdated > TimeSpan.FromMinutes(5);
                            if (coreNotSynced || tipReached || isTimeToRefresh)
                            {
                                syncInfo = await GetSyncInfoAsync().ConfigureAwait(false);
                            }

                            // If wasabi filter height is the same as core we may be done.
                            if (syncInfo.BlockCount == currentHeight)
                            {
                                // Check that core is fully synced
                                if (syncInfo.IsCoreSynchronized && !syncInfo.InitialBlockDownload)
                                {
                                    // Mark the process notstarted, so it can be started again
                                    // and finally block can mark it as stopped.
                                    Interlocked.Exchange(ref _serviceStatus, NotStarted);
                                    return;
                                }
                                else
                                {
                                    // Knots is catching up give it a 10 seconds
                                    await Task.Delay(10000).ConfigureAwait(false);
                                    continue;
                                }
                            }

                            uint nextHeight        = currentHeight + 1;
                            uint256 blockHash      = await RpcClient.GetBlockHashAsync((int)nextHeight).ConfigureAwait(false);
                            VerboseBlockInfo block = await RpcClient.GetVerboseBlockAsync(blockHash).ConfigureAwait(false);

                            // Check if we are still on the best chain,
                            // if not rewind filters till we find the fork.
                            if (currentHash != block.PrevBlockHash)
                            {
                                Logger.LogWarning("Reorg observed on the network.");

                                await ReorgOneAsync().ConfigureAwait(false);

                                // Skip the current block.
                                continue;
                            }

                            var filter = BuildFilterForBlock(block);

                            var smartHeader = new SmartHeader(block.Hash, block.PrevBlockHash, nextHeight, block.BlockTime);
                            var filterModel = new FilterModel(smartHeader, filter);

                            await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToLine() }).ConfigureAwait(false);

                            using (await IndexLock.LockAsync())
                            {
                                Index.Add(filterModel);
                            }

                            // If not close to the tip, just log debug.
                            if (syncInfo.BlockCount - nextHeight <= 3 || nextHeight % 100 == 0)
                            {
                                Logger.LogInfo($"Created filter for block: {nextHeight}.");
                            }
                            else
                            {
                                Logger.LogDebug($"Created filter for block: {nextHeight}.");
                            }
                            LastFilterBuildTime = DateTimeOffset.UtcNow;
                        }