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)
{