Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid infinite loop when a peer is evicted from routing table and add cached peer #566

Merged
merged 9 commits into from Oct 15, 2019
9 changes: 7 additions & 2 deletions CHANGES.md
Expand Up @@ -31,6 +31,8 @@ To be released.

### Behavioral changes

- Changed to send `Pong` before updating the message sender to the routing
table when `Ping` is received. [[#566]]
- Improved performance of `StoreExtension.LookupStateReference<T>()` method.
[[#447], [#545]]
- Added .NET Core 2.2 as a targeted framework. [[#209], [#561]]
Expand All @@ -39,6 +41,8 @@ To be released.

### Bug fixes

- Fixed a bug where `KademliaProtocol<T>.UpdateAsync()` runs infinitely when
a peer is evicted from `Bucket` and replacement cache is used. [[#566]]
- Fixed a bug where `Swarm<T>.AppendBlocksAsync()` re-requests blocks that
already received when blockchain is empty. [[#550], [#562]]
- Fixed a bug that `Swarm<T>` had thrown `SocketException` with a message
Expand All @@ -48,6 +52,7 @@ To be released.
- Fixed a bug that `Swarm<T>` had thrown `InvalidBlockIndexException` during
synchronizing with other reorganized peer. [[#528], [#576]]

[#209]: https://github.com/planetarium/libplanet/issues/209
[#405]: https://github.com/planetarium/libplanet/issues/405
[#447]: https://github.com/planetarium/libplanet/issues/447
[#462]: https://github.com/planetarium/libplanet/issues/462
Expand All @@ -62,10 +67,10 @@ To be released.
[#555]: https://github.com/planetarium/libplanet/issues/555
[#558]: https://github.com/planetarium/libplanet/pull/558
[#560]: https://github.com/planetarium/libplanet/pull/560
[#561]: https://github.com/planetarium/libplanet/pull/561
[#562]: https://github.com/planetarium/libplanet/pull/562
[#563]: https://github.com/planetarium/libplanet/pull/563
[#209]: https://github.com/planetarium/libplanet/issues/209
[#561]: https://github.com/planetarium/libplanet/pull/561
[#566]: https://github.com/planetarium/libplanet/pull/566
[#575]: https://github.com/planetarium/libplanet/pull/575
[#576]: https://github.com/planetarium/libplanet/pull/576

Expand Down
166 changes: 161 additions & 5 deletions Libplanet.Tests/Net/SwarmTest.cs
Expand Up @@ -15,6 +15,7 @@
using Libplanet.Crypto;
using Libplanet.Net;
using Libplanet.Net.Messages;
using Libplanet.Net.Protocols;
using Libplanet.Tests.Blockchain;
using Libplanet.Tests.Common.Action;
using Libplanet.Tests.Store;
Expand Down Expand Up @@ -385,19 +386,17 @@ public async Task PingToClosedPeer()
await StartAsync(swarmB);
await StartAsync(swarmC);

Peer peer = swarmC.AsPeer;
await swarmA.AddPeersAsync(new[] { swarmB.AsPeer, peer }, null);
await swarmA.AddPeersAsync(new[] { swarmB.AsPeer, swarmC.AsPeer }, null);

Assert.Contains(swarmB.AsPeer, swarmA.Peers);
Assert.Contains(peer, swarmA.Peers);
Assert.Contains(swarmC.AsPeer, swarmA.Peers);

await swarmC.StopAsync();
await Assert.ThrowsAsync<TimeoutException>(
() => swarmA.AddPeersAsync(new[] { peer }, TimeSpan.FromSeconds(3)));
() => swarmA.AddPeersAsync(new[] { swarmC.AsPeer }, TimeSpan.FromSeconds(3)));
await swarmA.AddPeersAsync(new[] { swarmB.AsPeer }, null);

Assert.Contains(swarmB.AsPeer, swarmA.Peers);
Assert.DoesNotContain(peer, swarmA.Peers);
}
finally
{
Expand All @@ -414,6 +413,163 @@ public async Task PingToClosedPeer()
}
}

[Fact(Timeout = Timeout)]
public async Task RoutingTableFull()
{
var policy = new BlockPolicy<DumbAction>(new MinerReward(1));
var fx = new LiteDBStoreFixture(memory: true);

var blockchain = new BlockChain<DumbAction>(policy, fx.Store);

var swarm = new Swarm<DumbAction>(
blockchain,
new PrivateKey(),
appProtocolVersion: 1,
dialTimeout: TimeSpan.FromMilliseconds(15000),
linger: TimeSpan.FromMilliseconds(1000),
tableSize: 1,
bucketSize: 1,
host: IPAddress.Loopback.ToString());
var swarmA = _swarms[0];
var swarmB = _swarms[1];
var swarmC = _swarms[2];

try
{
await StartAsync(swarm);
await StartAsync(swarmA);
await StartAsync(swarmB);
await StartAsync(swarmC);

await swarmA.AddPeersAsync(new[] { swarm.AsPeer }, null);
await swarmB.AddPeersAsync(new[] { swarm.AsPeer }, null);
await swarmC.AddPeersAsync(new[] { swarm.AsPeer }, null);

Assert.Equal(1, swarmA.Peers.Count);
Assert.Contains(swarmA.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmB.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmC.AsPeer, swarm.Peers);
}
finally
{
await swarm.StopAsync();
await swarmA.StopAsync();
await swarmB.StopAsync();
await swarmC.StopAsync();
fx.Dispose();
swarm.Dispose();
}
}

[Fact(Timeout = Timeout)]
public async Task ReplacementCache()
{
var policy = new BlockPolicy<DumbAction>(new MinerReward(1));
var fx = new LiteDBStoreFixture(memory: true);

var blockchain = new BlockChain<DumbAction>(policy, fx.Store);

var swarm = new Swarm<DumbAction>(
blockchain,
new PrivateKey(),
appProtocolVersion: 1,
dialTimeout: TimeSpan.FromMilliseconds(15000),
linger: TimeSpan.FromMilliseconds(1000),
tableSize: 1,
bucketSize: 1,
host: IPAddress.Loopback.ToString());
var swarmA = _swarms[0];
var swarmB = _swarms[1];
var swarmC = _swarms[2];

try
{
await StartAsync(swarm);
await StartAsync(swarmA);
await StartAsync(swarmB);
await StartAsync(swarmC);

await swarmA.AddPeersAsync(new[] { swarm.AsPeer }, null);
await swarmB.AddPeersAsync(new[] { swarm.AsPeer }, null);

Assert.Equal(1, swarmA.Peers.Count);
Assert.Contains(swarmA.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmB.AsPeer, swarm.Peers);

await swarmA.StopAsync();
await swarmC.AddPeersAsync(new[] { swarm.AsPeer }, null);
await Task.Delay(Kademlia.IdleRequestTimeout + 1000);

Assert.Equal(1, swarm.Peers.Count);
Assert.DoesNotContain(swarmA.AsPeer, swarm.Peers);
Assert.Contains(swarmB.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmC.AsPeer, swarm.Peers);
}
finally
{
await swarm.StopAsync();
await swarmB.StopAsync();
await swarmC.StopAsync();
fx.Dispose();
swarm.Dispose();
}
}

[Fact(Timeout = Timeout)]
public async Task RemoveDeadReplacementCache()
{
var policy = new BlockPolicy<DumbAction>(new MinerReward(1));
var fx = new LiteDBStoreFixture(memory: true);

var blockchain = new BlockChain<DumbAction>(policy, fx.Store);

var swarm = new Swarm<DumbAction>(
blockchain,
new PrivateKey(),
appProtocolVersion: 1,
dialTimeout: TimeSpan.FromMilliseconds(15000),
linger: TimeSpan.FromMilliseconds(1000),
tableSize: 1,
bucketSize: 1,
host: IPAddress.Loopback.ToString());
var swarmA = _swarms[0];
var swarmB = _swarms[1];
var swarmC = _swarms[2];

try
{
await StartAsync(swarm);
await StartAsync(swarmA);
await StartAsync(swarmB);
await StartAsync(swarmC);

await swarmA.AddPeersAsync(new[] { swarm.AsPeer }, null);
await swarmB.AddPeersAsync(new[] { swarm.AsPeer }, null);

Assert.Equal(1, swarmA.Peers.Count);
Assert.Contains(swarmA.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmB.AsPeer, swarm.Peers);

await swarmA.StopAsync();
await swarmB.StopAsync();

await swarmC.AddPeersAsync(new[] { swarm.AsPeer }, null);
await Task.Delay(Kademlia.IdleRequestTimeout * 2 + 1000);

Assert.Equal(1, swarmA.Peers.Count);
Assert.DoesNotContain(swarmA.AsPeer, swarm.Peers);
Assert.DoesNotContain(swarmB.AsPeer, swarm.Peers);
Assert.Contains(swarmC.AsPeer, swarm.Peers);
}
finally
{
await swarm.StopAsync();
await swarmC.StopAsync();
fx.Dispose();
swarm.Dispose();
}
}

[Fact(Timeout = Timeout)]
public async Task BootstrapException()
{
Expand Down
4 changes: 2 additions & 2 deletions Libplanet/Net/Protocols/IProtocol.cs
Expand Up @@ -21,9 +21,9 @@ internal interface IProtocol
TimeSpan? findPeerTimeout,
CancellationToken cancellationToken);

Task RefreshTableAsync(TimeSpan? period, CancellationToken cancellationToken);
Task RefreshTableAsync(TimeSpan period, CancellationToken cancellationToken);

Task RebuildConnectionAsync(TimeSpan? period, CancellationToken cancellationToken);
Task RebuildConnectionAsync(TimeSpan period, CancellationToken cancellationToken);

void ReceiveMessage(object sender, Message message);

Expand Down
6 changes: 6 additions & 0 deletions Libplanet/Net/Protocols/Kademlia.cs
Expand Up @@ -6,6 +6,12 @@ namespace Libplanet.Net.Protocols
{
internal static class Kademlia
{
public const int BucketSize = 16;
public const int TableSize = Address.Size * sizeof(byte) * 8;
public const int FindConcurrency = 3;
public const int MaxDepth = 3;
public const int IdleRequestTimeout = 5000;

public static Address CalculateDistance(Address a, Address b)
{
var dba = new byte[Address.Size];
Expand Down