public async Task StopAsync() { await(BlockchainProcessor?.StopAsync() ?? Task.CompletedTask); await(BlockProducer?.StopAsync() ?? Task.CompletedTask); await(PeerPool?.StopAsync() ?? Task.CompletedTask); await(Synchronizer?.StopAsync() ?? Task.CompletedTask); Logger?.Flush(); }
/// <summary> /// Downloads blocks from <paramref name="peers"/> in parallel, /// using the given <paramref name="blockFetcher"/> function. /// </summary> /// <param name="peers">A list of peers to download blocks.</param> /// <param name="blockFetcher">A function to take demands and a peer, and then /// download corresponding blocks.</param> /// <param name="singleSessionTimeout">A maximum time to wait each single call of /// <paramref name="blockFetcher"/>. If a call is timed out unsatisfied demands /// are automatically retried to fetch from other peers.</param> /// <param name="cancellationToken">A cancellation token to observe while waiting /// for the task to complete.</param> /// <returns>An async enumerable that yields pairs of a fetched block and its source /// peer. It terminates when all demands are satisfied.</returns> public async IAsyncEnumerable <Tuple <Block <TAction>, TPeer> > Complete( IReadOnlyList <TPeer> peers, BlockFetcher blockFetcher, TimeSpan singleSessionTimeout, [EnumeratorCancellation] CancellationToken cancellationToken = default ) { if (!peers.Any()) { throw new ArgumentException("The list of peers must not be empty.", nameof(peers)); } var pool = new PeerPool(peers); var queue = new AsyncProducerConsumerQueue <Tuple <Block <TAction>, TPeer> >(); var completion = new ConcurrentDictionary <HashDigest <SHA256>, bool>(_satisfiedBlocks); await foreach (var hashes in EnumerateChunks(cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); IList <HashDigest <SHA256> > hashDigests = hashes is IList <HashDigest <SHA256> > l ? l : hashes.ToList(); foreach (HashDigest <SHA256> hash in hashDigests) { completion.TryAdd(hash, false); } cancellationToken.ThrowIfCancellationRequested(); await pool.SpawnAsync( async (peer, ct) => { ct.ThrowIfCancellationRequested(); var demands = new HashSet <HashDigest <SHA256> >(hashDigests); try { _logger.Debug( "Request blocks {BlockHashes} to {Peer}...", hashDigests, peer ); var timeout = new CancellationTokenSource(singleSessionTimeout); CancellationToken timeoutToken = timeout.Token; timeoutToken.Register(() => _logger.Debug("Timed out to wait a response from {Peer}.", peer) ); ct.Register(() => timeout.Cancel()); try { ConfiguredCancelableAsyncEnumerable <Block <TAction> > blocks = blockFetcher(peer, hashDigests, timeoutToken) .WithCancellation(timeoutToken); await foreach (Block <TAction> block in blocks) { _logger.Debug( "Downloaded a block #{BlockIndex} {BlockHash} " + "from {Peer}.", block.Index, block.Hash, peer ); if (Satisfy(block)) { await queue.EnqueueAsync( Tuple.Create(block, peer), cancellationToken ); } demands.Remove(block.Hash); } } catch (OperationCanceledException e) { if (ct.IsCancellationRequested) { _logger.Error( e, "A blockFetcher job (peer: {Peer}) is cancelled.", peer ); throw; } _logger.Debug( e, "Timed out to wait a response from {Peer}.", peer ); } } finally { if (demands.Any()) { _logger.Verbose( "Fetched blocks from {Peer}, but there are still " + "unsatisfied demands ({UnsatisfiedDemandsNumber}) so " + "enqueue them again: {UnsatisfiedDemands}.", peer, demands.Count, demands ); Demand(demands, retry: true); } else { _logger.Verbose("Fetched blocks from {Peer}.", peer); } } }, cancellationToken : cancellationToken ); } while (!completion.All(kv => kv.Value)) { Tuple <Block <TAction>, TPeer> pair; try { pair = await queue.DequeueAsync(cancellationToken); } catch (InvalidOperationException) { break; } yield return(pair); _logger.Verbose( "Completed a block {BlockIndex} {BlockHash} from {Peer}.", pair.Item1.Index, pair.Item1.Hash, pair.Item2 ); completion[pair.Item1.Hash] = true; } _logger.Verbose("Completed all blocks ({Number}).", completion.Count); }
/// <summary> /// Downloads blocks from <paramref name="peers"/> in parallel, /// using the given <paramref name="blockFetcher"/> function. /// </summary> /// <param name="peers">A list of peers to download blocks.</param> /// <param name="blockFetcher">A function to take demands and a peer, and then /// download corresponding blocks.</param> /// <param name="singleSessionTimeout">A maximum time to wait each single call of /// <paramref name="blockFetcher"/>. If a call is timed out unsatisfied demands /// are automatically retried to fetch from other peers.</param> /// <param name="cancellationToken">A cancellation token to observe while waiting /// for the task to complete.</param> /// <returns>An async enumerable that yields pairs of a fetched block and its source /// peer. It terminates when all demands are satisfied.</returns> public async IAsyncEnumerable <Tuple <Block <TAction>, TPeer> > Complete( IReadOnlyList <TPeer> peers, BlockFetcher blockFetcher, TimeSpan singleSessionTimeout, [EnumeratorCancellation] CancellationToken cancellationToken = default ) { if (!peers.Any()) { throw new ArgumentException("The list of peers must not be empty.", nameof(peers)); } var pool = new PeerPool(peers); var queue = new AsyncProducerConsumerQueue <Tuple <Block <TAction>, TPeer> >(); Task producer = Task.Run(async() => { try { await foreach (var hashes in EnumerateChunks(cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); IList <HashDigest <SHA256> > hashDigests = hashes is IList <HashDigest <SHA256> > l ? l : hashes.ToList(); cancellationToken.ThrowIfCancellationRequested(); await pool.SpawnAsync( CreateEnqueuing( hashDigests, blockFetcher, singleSessionTimeout, cancellationToken, queue ), cancellationToken: cancellationToken ); } await pool.WaitAll(cancellationToken); } finally { queue.CompleteAdding(); } }); while (await queue.OutputAvailableAsync(cancellationToken)) { Tuple <Block <TAction>, TPeer> pair; try { pair = await queue.DequeueAsync(cancellationToken); } catch (InvalidOperationException) { break; } yield return(pair); _logger.Verbose( "Completed a block {BlockIndex} {BlockHash} from {Peer}.", pair.Item1.Index, pair.Item1.Hash, pair.Item2 ); } await producer; }