From 6e7249f8e0feee76292acfbd256a6645bf1c00f5 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Thu, 29 Aug 2019 15:19:07 +0900 Subject: [PATCH] Download trusted states from a likely branchpoint --- CHANGES.md | 10 ++++ Libplanet.Tests/Net/SwarmTest.cs | 64 +++++++++++++++++++++-- Libplanet/Net/Messages/GetRecentStates.cs | 16 +++--- Libplanet/Net/Swarm.cs | 10 ++-- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6e6050ba192..fe4eec67978 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -33,6 +33,15 @@ To be released. ### Behavioral changes + - `Swarm.PreloadAsync()` method became to download precalculated states + of blocks from a likely branchpoint instead of a genesis block from + a trusted peer (i.e., `trustedStateValidators`) where there are branches + between peers. [[#465], [#481]] + - `Swarm`'s internal `GetRecentStates` message became to take + `BlockLocator`, an internal data type to approximates a path of + a chain of blocks for heuristics to search a likely branchpoint, + instead of `HashDigest`. [[#465], [#481]] + ### Bug fixes - Fixed a bug that `Swarm` hadn't released its TURN related resources on @@ -41,6 +50,7 @@ To be released. [#420]: https://github.com/planetarium/libplanet/pull/420 [#450]: https://github.com/planetarium/libplanet/pull/450 [#470]: https://github.com/planetarium/libplanet/pull/470 +[#481]: https://github.com/planetarium/libplanet/pull/481 Version 0.5.2 diff --git a/Libplanet.Tests/Net/SwarmTest.cs b/Libplanet.Tests/Net/SwarmTest.cs index 60c3d3e09e4..d021aeb6d72 100644 --- a/Libplanet.Tests/Net/SwarmTest.cs +++ b/Libplanet.Tests/Net/SwarmTest.cs @@ -1092,7 +1092,7 @@ public async Task PreloadWithTrustedPeers(bool trust) HashDigest? deepBlockHash = null; - for (int i = 0; i < 2; i++) + for (int i = 0; i < 3; i++) { int j = 0; Block block = null; @@ -1106,7 +1106,7 @@ public async Task PreloadWithTrustedPeers(bool trust) j++; } - if (i < 1) + if (i < 2) { deepBlockHash = block?.Hash; } @@ -1142,7 +1142,7 @@ public async Task PreloadWithTrustedPeers(bool trust) ); Assert.Single(states); Assert.Equal( - $"({chainType}) Item0.{i},Item1.{i}", + $"({chainType}) Item0.{i},Item1.{i},Item2.{i}", $"({chainType}) {states[target]}" ); } @@ -1163,7 +1163,7 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetStates( { var deepStates = TryToGetDeepStates(); Assert.Single(deepStates); - Assert.Equal($"Item0.{i}", deepStates[target]); + Assert.Equal($"Item0.{i},Item1.{i}", deepStates[target]); } i++; @@ -1175,7 +1175,7 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetStates( new[] { minerSwarm.Address }, completeStates: false); Assert.Single(minerState); - Assert.Equal(20, minerState[minerSwarm.Address]); + Assert.Equal(30, minerState[minerSwarm.Address]); } } finally @@ -1186,6 +1186,60 @@ AddressStateMap TryToGetDeepStates() => receiverChain.GetStates( } } + [Fact] + public async Task PreloadWithBranchesAndTrustedPeers() + { + // Two miners; one trusts other one. Test if it downloads the minimum range of blocks + // (instead of the entire chain from the genesis to the tip) when there are branches. + // See also: https://github.com/planetarium/libplanet/issues/465#issuecomment-525682219 + Swarm senderSwarm = _swarms[0]; + Swarm receiverSwarm = _swarms[1]; + + BlockChain senderChain = _blockchains[0]; + BlockChain receiverChain = _blockchains[1]; + + Block g = TestUtils.MineGenesis(senderSwarm.Address), + bp = TestUtils.MineNext(g, difficulty: 1024), + b2send = TestUtils.MineNext(bp, difficulty: 1024), + b2recv = TestUtils.MineNext(bp, difficulty: 1024), + b3 = TestUtils.MineNext(b2send, difficulty: 1024); + + senderChain.Append(g); + senderChain.Append(bp); + senderChain.Append(b2send); + senderChain.Append(b3); + + receiverChain.Append(g); + receiverChain.Append(bp); + receiverChain.Append(b2recv); + + var receivedBlockStates = new HashSet>(); + + try + { + await StartAsync(senderSwarm); + await receiverSwarm.AddPeersAsync(new[] { senderSwarm.AsPeer }); + await receiverSwarm.PreloadAsync( + progress: new Progress(state => + { + if (state is BlockStateDownloadState s) + { + receivedBlockStates.Add(s.ReceivedBlockHash); + } + }), + trustedStateValidators: ImmutableHashSet
.Empty.Add(senderSwarm.Address) + ); + } + finally + { + await senderSwarm.StopAsync(); + } + + Assert.Equal(senderChain, receiverChain); + Assert.DoesNotContain(g.Hash, receivedBlockStates); + Assert.DoesNotContain(bp.Hash, receivedBlockStates); + } + [Theory] [InlineData(0)] [InlineData(50)] diff --git a/Libplanet/Net/Messages/GetRecentStates.cs b/Libplanet/Net/Messages/GetRecentStates.cs index 4d6bc12837d..9076edf34e5 100644 --- a/Libplanet/Net/Messages/GetRecentStates.cs +++ b/Libplanet/Net/Messages/GetRecentStates.cs @@ -1,28 +1,28 @@ using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography; +using Libplanet.Blockchain; using NetMQ; namespace Libplanet.Net.Messages { internal class GetRecentStates : Message { - public GetRecentStates(HashDigest? @base, HashDigest target) + public GetRecentStates(BlockLocator baseLocator, HashDigest target) { - BaseBlockHash = @base; + BaseLocator = baseLocator; TargetBlockHash = target; } public GetRecentStates(NetMQFrame[] frames) : this( - frames.Length > 1 - ? new HashDigest(frames[1].Buffer) - : (HashDigest?)null, + new BlockLocator(frames.Skip(1).Select(f => new HashDigest(f.Buffer))), new HashDigest(frames[0].Buffer) ) { } - public HashDigest? BaseBlockHash { get; } + public BlockLocator BaseLocator { get; } public HashDigest TargetBlockHash { get; } @@ -33,9 +33,9 @@ protected override IEnumerable DataFrames get { yield return new NetMQFrame(TargetBlockHash.ToByteArray()); - if (BaseBlockHash is HashDigest @base) + foreach (HashDigest hash in BaseLocator) { - yield return new NetMQFrame(@base.ToByteArray()); + yield return new NetMQFrame(hash.ToByteArray()); } } } diff --git a/Libplanet/Net/Swarm.cs b/Libplanet/Net/Swarm.cs index cf7431f1e87..fd10f0e246f 100644 --- a/Libplanet/Net/Swarm.cs +++ b/Libplanet/Net/Swarm.cs @@ -586,6 +586,7 @@ await DialToExistingPeers(cancellationToken).Select(pp => } Block initialTip = _blockChain.Tip; + BlockLocator initialLocator = _blockChain.GetBlockLocator(); // As preloading takes long, the blockchain data can corrupt if a program suddenly // terminates during preloading is going on. In order to make preloading done @@ -639,7 +640,7 @@ await SyncBehindsBlocksFromPeersAsync( workspace, progress, trustedPeersWithTip.ToImmutableList(), - initialTip?.Hash, + initialLocator, cancellationToken ); @@ -991,7 +992,7 @@ private async Task SyncRecentStatesFromTrustedPeersAsync( BlockChain blockChain, IProgress progress, IReadOnlyList<(Peer, HashDigest)> trustedPeersWithTip, - HashDigest? baseBlockHash, + BlockLocator baseLocator, CancellationToken cancellationToken) { _logger.Debug( @@ -1001,7 +1002,7 @@ private async Task SyncRecentStatesFromTrustedPeersAsync( foreach ((Peer peer, var blockHash) in trustedPeersWithTip) { cancellationToken.ThrowIfCancellationRequested(); - var request = new GetRecentStates(baseBlockHash, blockHash); + var request = new GetRecentStates(baseLocator, blockHash); _logger.Debug("Makes a dealer socket to a trusted peer ({0}).", peer); using (var socket = new DealerSocket(ToNetMQAddress(peer))) { @@ -1663,7 +1664,8 @@ private void TransferBlocks(GetBlocks getData) private void TransferRecentStates(GetRecentStates getRecentStates) { - HashDigest? @base = getRecentStates.BaseBlockHash; + BlockLocator baseLocator = getRecentStates.BaseLocator; + HashDigest @base = _blockChain.FindBranchPoint(baseLocator); HashDigest target = getRecentStates.TargetBlockHash; IImmutableDictionary, IImmutableDictionary