diff --git a/Libplanet.Net/Protocols/IProtocol.cs b/Libplanet.Net/Protocols/IProtocol.cs index b5a8cabfb06..ea62854ee1b 100644 --- a/Libplanet.Net/Protocols/IProtocol.cs +++ b/Libplanet.Net/Protocols/IProtocol.cs @@ -68,6 +68,19 @@ Task AddPeersAsync( /// An awaitable task without value. Task RebuildConnectionAsync(int depth, CancellationToken cancellationToken); + /// + /// Reconstructs network connection between peers on network using seed peers. + /// + /// The list of the see peers. + /// Recursive operation depth to search peers from network. + /// A cancellation token used to propagate notification + /// that this operation should be canceled. + /// An awaitable task without value. + Task RebuildConnectionAsync( + IEnumerable seedPeers, + int depth, + CancellationToken cancellationToken); + /// /// Checks the in the and if /// there is an empty , fill it with s diff --git a/Libplanet.Net/Protocols/KademliaProtocol.cs b/Libplanet.Net/Protocols/KademliaProtocol.cs index 1511ea121fa..e5f00e321ef 100644 --- a/Libplanet.Net/Protocols/KademliaProtocol.cs +++ b/Libplanet.Net/Protocols/KademliaProtocol.cs @@ -268,6 +268,34 @@ public async Task RebuildConnectionAsync(int depth, CancellationToken cancellati } } + /// + public async Task RebuildConnectionAsync( + IEnumerable seedPeers, + int depth, + CancellationToken cancellationToken) + { + _logger.Verbose("Rebuilding connection using seed peers..."); + var history = new ConcurrentBag(); + var dialHistory = new ConcurrentBag(); + var tasks = seedPeers.Select(seed => + FindPeerAsync( + history, + dialHistory, + _address, + seed, + depth, + _requestTimeout, + cancellationToken)).ToList(); + + try + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + catch (TimeoutException) + { + } + } + /// public async Task CheckReplacementCacheAsync(CancellationToken cancellationToken) { @@ -722,6 +750,12 @@ private async Task ProcessFoundAsync( "Some responses from neighbors found unexpectedly terminated"); } + if (depth == 1) + { + // depth 1 means spawn FindPeerAsync task of depth 0, and it does nothing. + return; + } + var findPeerTasks = new List(); BoundPeer closestKnownPeer = closestCandidate.FirstOrDefault(); var count = 0; diff --git a/Libplanet.Net/Protocols/RoutingTable.cs b/Libplanet.Net/Protocols/RoutingTable.cs index 7010042d4e3..bbc13ed80b8 100644 --- a/Libplanet.Net/Protocols/RoutingTable.cs +++ b/Libplanet.Net/Protocols/RoutingTable.cs @@ -23,13 +23,16 @@ public class RoutingTable : IRoutingTable /// of this peer. /// The number of buckets in the table. /// The size of a single bucket. + /// The list of the seed peers. + /// If null is given, is set to an empty list. /// /// Thrown when or is /// less then or equal to 0. public RoutingTable( Address address, int tableSize = Kademlia.TableSize, - int bucketSize = Kademlia.BucketSize) + int bucketSize = Kademlia.BucketSize, + IEnumerable? seedPeers = null) { if (tableSize <= 0) { @@ -45,6 +48,7 @@ public RoutingTable( _address = address; TableSize = tableSize; BucketSize = bucketSize; + SeedPeers = seedPeers?.ToList() ?? new List(); _logger = Log .ForContext() .ForContext("Source", nameof(RoutingTable)); @@ -67,6 +71,13 @@ public RoutingTable( /// public int BucketSize { get; } + /// + /// The list of the seed peers. + /// Seed peers are excluded from bound peer selection, and neighbor. + /// + /// + public IReadOnlyList SeedPeers { get; } + /// public int Count => _buckets.Sum(bucket => bucket.Count); @@ -144,7 +155,7 @@ public bool Contains(BoundPeer peer) Peers.FirstOrDefault(peer => peer.Address.Equals(addr)); /// - /// Removes all peers in the table. This method does not affect static peers. + /// Removes all peers in the table. /// public void Clear() { @@ -179,7 +190,6 @@ public IReadOnlyList Neighbors(BoundPeer target, int k, bool includeT /// An enumerable of . public IReadOnlyList Neighbors(Address target, int k, bool includeTarget) { - // TODO: Should include static peers? var sorted = _buckets .Where(b => !b.IsEmpty) .SelectMany(b => b.Peers) @@ -200,12 +210,17 @@ public IReadOnlyList Neighbors(Address target, int k, bool includeTar /// /// Marks checked and refreshes last checked time of the peer. + /// If the given is one of the , + /// it is not added. /// /// The to check. /// at the beginning of the check. /// at the end of the check. /// /// Thrown when is . + /// + /// Thrown when given 's is equal to + /// 's . public void Check(BoundPeer peer, DateTimeOffset start, DateTimeOffset end) => BucketOf(peer).Check(peer, start, end); @@ -218,6 +233,12 @@ internal void AddPeer(BoundPeer peer, DateTimeOffset updated) nameof(peer)); } + if (SeedPeers.Any(seed => peer.Address.Equals(seed.Address))) + { + _logger.Verbose("A seed peer is disallowed to add in the routing table."); + return; + } + _logger.Debug("Adding peer {Peer} to the routing table...", peer); BucketOf(peer).AddPeer(peer, updated); } diff --git a/Libplanet.Net/Swarm.cs b/Libplanet.Net/Swarm.cs index 9d4d8317cff..5664186c0dd 100644 --- a/Libplanet.Net/Swarm.cs +++ b/Libplanet.Net/Swarm.cs @@ -87,7 +87,11 @@ public Swarm( Options = options ?? new SwarmOptions(); TxCompletion = new TxCompletion(BlockChain, GetTxsAsync, BroadcastTxs); - RoutingTable = new RoutingTable(Address, Options.TableSize, Options.BucketSize); + RoutingTable = new RoutingTable( + Address, + Options.TableSize, + Options.BucketSize, + Options.StaticPeers); // FIXME: after the initialization of NetMQTransport is fully converted to asynchronous // code, the portion initializing the swarm in Agent.cs in NineChronicles should be @@ -1432,9 +1436,19 @@ private async Task RebuildConnectionAsync( try { await Task.Delay(period, cancellationToken); - await PeerDiscovery.RebuildConnectionAsync( - Kademlia.MaxDepth, - cancellationToken); + if (RoutingTable.SeedPeers.Any()) + { + await PeerDiscovery.RebuildConnectionAsync( + RoutingTable.SeedPeers, + 1, + cancellationToken); + } + else + { + await PeerDiscovery.RebuildConnectionAsync( + Kademlia.MaxDepth, + cancellationToken); + } } catch (OperationCanceledException e) {