private async Task ReorgOneAsync()
        {
            // 1. Rollback index
            using (await IndexLock.LockAsync())
            {
                Logger.LogInfo <IndexBuilderService>($"REORG invalid block: {Index.Last().BlockHash}");
                Index.RemoveLast();
            }

            // 2. Serialize Index. (Remove last line.)
            var lines = await File.ReadAllLinesAsync(IndexFilePath);

            await File.WriteAllLinesAsync(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())));
            }
        }
 private void PrepareBech32UtxoSetHistory()
 {
     if (Bech32UtxoSetHistory.Count >= 100)
     {
         Bech32UtxoSetHistory.RemoveFirst();
     }
     Bech32UtxoSetHistory.Add(new ActionHistoryHelper());
 }
        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 didn't yet initialized syncInfo, do so.
                                if (syncInfo is null)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

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

                                // If not synchronized or already 5 min passed since last update, get the latest blockchain info.
                                if (!syncInfo.IsCoreSynchornized || (syncInfo.BlockchainInfoUpdated - DateTimeOffset.UtcNow) > 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 <IndexBuilderService>("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 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.IsScriptType(ScriptType.P2WPKH))
                                        {
                                            var outpoint = new OutPoint(tx.GetHash(), i);
                                            Bech32UtxoSet.Add(outpoint, output.ScriptPubKey);
                                            if (isImmature)
                                            {
                                                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 (isImmature)
                                            {
                                                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 = heightToRequest,
                                    Filter      = filter
                                };

                                await File.AppendAllLinesAsync(IndexFilePath, new[] { filterModel.ToHeightlessLine() });
                                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 (syncInfo.BlockCount - heightToRequest.Value <= 3 || heightToRequest % 100 == 0)
                                {
                                    Logger.LogInfo <IndexBuilderService>($"Created filter for block: {heightToRequest}.");
                                }
                                else
                                {
                                    Logger.LogDebug <IndexBuilderService>($"Created filter for block: {heightToRequest}.");
                                }
                            }
                            catch (Exception ex)
                            {
                                Logger.LogDebug <IndexBuilderService>(ex);
                            }
                        }
                    }
                    finally
                    {
                        Interlocked.CompareExchange(ref _running, 3, 2);                         // If IsStopping, make it stopped.
                        Interlocked.Decrement(ref _runner);
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError <IndexBuilderService>($"Synchronization attempt failed to start: {ex}");
                }
            });
        }