public async Task Complete() { // 0, 1: Already existed blocks // 2, 3, 4, 5, 6: first chunk // 7, 8, 9, 10, 11: second chunk // 12, 13, 14: last chunk ImmutableArray <Block <DumbAction> > fixture = GenerateBlocks <DumbAction>(15).ToImmutableArray(); // Blocks each block has: // A: 0, 4, 8, 12 // B: 1, 5, 9, 13 // C: 2, 6, 10, 14 // D: 3, 7, 11 char[] peers = { 'A', 'B', 'C', 'D' }; ImmutableDictionary <char, ImmutableDictionary <HashDigest <SHA256>, Block <DumbAction> > > peerBlocks = peers.ToImmutableDictionary( p => p, p => fixture.Skip(p - 'A').Where((_, i) => i % 4 < 1).ToImmutableDictionary( b => b.Hash, b => b ) ); const int initialHeight = 2; const int window = 5; var bc = new BlockCompletion <char, DumbAction>( fixture.Take(initialHeight).Select(b => b.Hash).ToImmutableHashSet().Contains, window ); ImmutableArray <HashDigest <SHA256> > initialDemands = fixture .Skip(initialHeight + 10) .Select(b => b.Hash) .ToImmutableArray(); bc.Demand(initialDemands); _logger.Verbose("Initial demands: {0}", initialDemands); IAsyncEnumerable <Tuple <Block <DumbAction>, char> > rv = bc.Complete( new[] { 'A', 'B', 'C', 'D' }, (peer, hashes, token) => new AsyncEnumerable <Block <DumbAction> >(async yield => { var blocksPeerHas = peerBlocks[peer]; var sent = new HashSet <HashDigest <SHA256> >(); foreach (HashDigest <SHA256> hash in hashes) { if (blocksPeerHas.ContainsKey(hash)) { Block <DumbAction> block = blocksPeerHas[hash]; await yield.ReturnAsync(block); sent.Add(block.Hash); } } _logger.Verbose("Peer {Peer} sent blocks: {SentBlockHashes}.", peer, sent); }) ); var downloadedBlocks = new HashSet <Block <DumbAction> >(); var sourcePeers = new HashSet <char>(); await AsyncEnumerable.ForEachAsync(rv, pair => { downloadedBlocks.Add(pair.Item1); sourcePeers.Add(pair.Item2); }); Assert.Equal(fixture.Skip(2).ToHashSet(), downloadedBlocks); Assert.Subset(peers.ToHashSet(), sourcePeers); }
public async void EnumerateChunks() { // 0, 1: Already existed blocks // 2, 3, 4, 5, 6: first chunk // 7, 8, 9, 10, 11: second chunk // 12, 13, 14: last chunk ImmutableArray <Block <DumbAction> > fixture = GenerateBlocks <DumbAction>(15).ToImmutableArray(); const int initialHeight = 2; const int window = 5; var bc = new BlockCompletion <int, DumbAction>( fixture.Take(initialHeight).Select(b => b.Hash).ToImmutableHashSet().Contains, window ); var logs = new ConcurrentBag <(int, ImmutableArray <HashDigest <SHA256> >)>(); var ev = new AsyncAutoResetEvent(false); var bg = Task.Run(async() => { await Task.Delay(100); int i = 0; await AsyncEnumerable.ForEachAsync(bc.EnumerateChunks(), hashes => { ImmutableArray <HashDigest <SHA256> > hashesArray = hashes.ToImmutableArray(); logs.Add((i, hashesArray)); i++; // To test dynamic demands if (hashesArray.Contains(fixture[7].Hash)) { bc.Demand(fixture[14].Hash); bc.Demand(fixture[0].Hash); // This should be ignored as it's existed. bc.Demand(fixture[3].Hash); // This should be ignored as it's satisfied. } ev.Set(); _logger.Verbose("Got a chunk of hashes: {0}", string.Join(", ", hashesArray)); }); }); // Demand: 2, 3, 4, 5, 6 bc.Demand(fixture.Skip(initialHeight).Take(5).Select(b => b.Hash)); // Chunk: 2, 3, 4, 5, 6 _logger.Verbose("Waiting demand #2-6..."); // TODO change waiting condition await Task.Delay(1000); _logger.Verbose("Demand #2-6 processed."); var actual = new List <HashDigest <SHA256> >(); while (logs.TryTake(out var log)) { actual.AddRange(log.Item2); } Assert.Equal(fixture.Skip(initialHeight).Take(window).Select(b => b.Hash), actual); // Complete: 2, 3, 4, 5 (and no 6) for (int i = initialHeight; i < initialHeight + window - 1; i++) { bc.Satisfy(fixture[i]); } // Demand: 7, 8, 9, 10, 11, 12, 13 (and 14 <- 7 will be added soon) bc.Demand(fixture.Skip(initialHeight + window).Select(b => b.Hash)); // Chunk: 7, 8, 9, 10, 11 _logger.Verbose("Waiting demand #7-11..."); // TODO change waiting condition await Task.Delay(1000); _logger.Verbose("Demand #7-11 processed."); actual = new List <HashDigest <SHA256> >(); while (logs.TryTake(out var log)) { actual.AddRange(log.Item2); } Assert.Equal( fixture.Skip(initialHeight + window).Take(window).Select(b => b.Hash), actual ); // Complete: 6, 7, 8, 9, 10, 11 for (int i = initialHeight + window - 1; i < initialHeight + window * 2; i++) { bc.Satisfy(fixture[i]); } // Chunk: 12, 13, 14 _logger.Verbose("Waiting demand #12-14..."); // TODO change waiting condition await Task.Delay(1000); _logger.Verbose("Demand #12-14 processed."); actual = new List <HashDigest <SHA256> >(); while (logs.TryTake(out var log)) { actual.AddRange(log.Item2); } Assert.Equal( fixture.Skip(initialHeight + window * 2).Select(b => b.Hash).ToImmutableHashSet(), actual.ToImmutableHashSet() ); // Complete: 12, 13, 14 for (int i = initialHeight + window * 2; i < fixture.Length; i++) { bc.Satisfy(fixture[i]); } await bg; }