예제 #1
0
        private async Task <SyncInfo> GetSyncInfoAsync()
        {
            var bcinfo = await RpcClient.GetBlockchainInfoAsync();

            var pbcinfo = new SyncInfo(bcinfo);

            return(pbcinfo);
        }
        public void Synchronize()
        {
            // Check permissions.
            using var _ = File.Open(IndexFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);

            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;
                            }
		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 || 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("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}");
				}
			});
		}
        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.
                                if (syncInfo is null)
                                {
                                    syncInfo = await GetSyncInfoAsync();
                                }

                                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}.");
                                }
                            }