From 2c23dbd59137e6f6763a4ad44c8f5ce8cd919b60 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Fri, 29 May 2020 04:09:10 +0900 Subject: [PATCH] Prevent GetDemandBlockHashes() from being hanged Fix https://github.com/planetarium/libplanet/issues/880 --- CHANGES.md | 7 ++++ Libplanet.Tests/ActionProgress.cs | 15 +++++++ Libplanet.Tests/Net/SwarmTest.Preload.cs | 52 ++++++++++++++++++++++++ Libplanet/Net/Swarm.cs | 12 ++++++ 4 files changed, 86 insertions(+) create mode 100644 Libplanet.Tests/ActionProgress.cs diff --git a/CHANGES.md b/CHANGES.md index 0df87ee2c78..5ad674e7145 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,13 @@ Version 0.9.3 To be released. + - Fixed a `Swarm.PreloadAsync()` method's bug that had hanged in a state + downloading block hashes and finally unexpectedly terminated when a peer's + chain had gotten reorged. [[#880], [#884]] + +[#880]: https://github.com/planetarium/libplanet/issues/880 +[#884]: https://github.com/planetarium/libplanet/pull/884 + Version 0.9.2 ------------- diff --git a/Libplanet.Tests/ActionProgress.cs b/Libplanet.Tests/ActionProgress.cs new file mode 100644 index 00000000000..69b1ee05ac8 --- /dev/null +++ b/Libplanet.Tests/ActionProgress.cs @@ -0,0 +1,15 @@ +using System; + +namespace Libplanet.Tests +{ + public sealed class ActionProgress : IProgress + { + private Action _action; + + public ActionProgress(Action action) => + _action = action ?? throw new ArgumentNullException(nameof(action)); + + public void Report(T value) => + _action(value); + } +} diff --git a/Libplanet.Tests/Net/SwarmTest.Preload.cs b/Libplanet.Tests/Net/SwarmTest.Preload.cs index 390e60e0b2d..33a0bb15ab8 100644 --- a/Libplanet.Tests/Net/SwarmTest.Preload.cs +++ b/Libplanet.Tests/Net/SwarmTest.Preload.cs @@ -1207,5 +1207,57 @@ public async void GetDemandBlockHashes() .Select(b => (b.Index, b.Hash)); Assert.Equal(expectedBlocks, demands); } + + [Fact(Timeout = Timeout)] + public async void GetDemandBlockHashesDuringReorg() + { + Swarm minerSwarm = _swarms[0]; + Swarm receiverSwarm = _swarms[1]; + Log.Logger.Information("Miner: {0}", minerSwarm.Address); + Log.Logger.Information("Receiver: {0}", receiverSwarm.Address); + + BlockChain minerChain = _blockchains[0]; + BlockChain receiverChain = _blockchains[1]; + + Block[] blocks = + (await MakeFixtureBlocksForPreloadAsyncCancellationTest()).Item2; + + foreach (Block block in blocks) + { + minerChain.Append(block); + } + + BlockChain forked = minerChain.Fork(minerChain.Genesis.Hash); + while (forked.Count <= minerChain.Count) + { + await forked.MineBlock(minerSwarm.Address); + } + + minerSwarm.FindNextHashesChunkSize = 2; + await StartAsync(minerSwarm); + + (BoundPeer, long?)[] peers = + { + ((BoundPeer)minerSwarm.AsPeer, minerChain.Count - 1), + }; + + long receivedCount = 0; + (long, HashDigest)[] demands = await receiverSwarm.GetDemandBlockHashes( + receiverChain, + peers, + progress: new ActionProgress(state => + { + if (state is BlockHashDownloadState s && + s.ReceivedBlockHashCount > minerChain.Count / 2) + { + receivedCount = s.ReceivedBlockHashCount; + minerChain.Swap(forked, render: false); + } + }), + cancellationToken: CancellationToken.None + ).ToArrayAsync(); + + Assert.Equal(receivedCount, demands.LongLength); + } } } diff --git a/Libplanet/Net/Swarm.cs b/Libplanet/Net/Swarm.cs index 2537ea4c8d5..2cab745e917 100644 --- a/Libplanet/Net/Swarm.cs +++ b/Libplanet/Net/Swarm.cs @@ -1055,6 +1055,8 @@ [EnumeratorCancellation] CancellationToken cancellationToken i++; long peerIndex = peerHeight ?? -1; + // FIXME: The following condition should be fixed together when the issue #459 is + // fixed. https://github.com/planetarium/libplanet/issues/459 if (peer is null || currentTipIndex >= peerIndex) { continue; @@ -1066,8 +1068,18 @@ [EnumeratorCancellation] CancellationToken cancellationToken try { var downloaded = new List>(); + int previousDownloadedCount = -1; + int stagnant = 0; + const int stagnationLimit = 3; while (downloaded.Count < totalBlocksToDownload) { + if (previousDownloadedCount == downloaded.Count && + ++stagnant > stagnationLimit) + { + break; + } + + previousDownloadedCount = downloaded.Count; _logger.Verbose( "Request block hashes to {Peer} (height: {PeerHeight}) using " + "the locator {@Locator}... ({CurrentIndex}/{EstimatedTotalCount})",