diff --git a/CHANGES.md b/CHANGES.md index 3cd939e874..0b2367b38c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -74,6 +74,8 @@ To be released. - Added `BlockChain.GetBalance()` method. [[#861], [#900]] - Added `Block.TotalDifficulty` property. [[#666], [#917]] - Added `SwarmOptions` class. [[#926]] + - Added `PeerChainState` struct. [[#936]] + - Added `Swarm.GetPeerChainStateAsync()` method. [[#936]] ### Behavioral changes @@ -154,6 +156,7 @@ To be released. [#930]: https://github.com/planetarium/libplanet/pull/930 [#932]: https://github.com/planetarium/libplanet/pull/932 [#933]: https://github.com/planetarium/libplanet/pull/933 +[#936]: https://github.com/planetarium/libplanet/pull/936 [sleep mode]: https://en.wikipedia.org/wiki/Sleep_mode diff --git a/Libplanet.Tests/Net/SwarmTest.cs b/Libplanet.Tests/Net/SwarmTest.cs index d62f7a0926..bed92db58a 100644 --- a/Libplanet.Tests/Net/SwarmTest.cs +++ b/Libplanet.Tests/Net/SwarmTest.cs @@ -2016,6 +2016,58 @@ public async Task DoNotFillMultipleTimes() } } + [Fact(Timeout = Timeout)] + public async Task GetPeerChainStateAsync() + { + Swarm swarm1 = _swarms[0]; + Swarm swarm2 = _swarms[1]; + Swarm swarm3 = _swarms[2]; + + var peerChainState = await swarm1.GetPeerChainStateAsync( + TimeSpan.FromSeconds(1), default); + Assert.Empty(peerChainState); + + try + { + await StartAsync(swarm2); + await StartAsync(swarm3); + + await BootstrapAsync(swarm1, swarm2.AsPeer); + + peerChainState = await swarm1.GetPeerChainStateAsync( + TimeSpan.FromSeconds(1), default); + Assert.Equal( + new PeerChainState((BoundPeer)swarm2.AsPeer, 0, 0), + peerChainState.First() + ); + + await swarm2.BlockChain.MineBlock(_fx1.Address1); + peerChainState = await swarm1.GetPeerChainStateAsync( + TimeSpan.FromSeconds(1), default); + Assert.Equal( + new PeerChainState((BoundPeer)swarm2.AsPeer, 1, 1024), + peerChainState.First() + ); + + await BootstrapAsync(swarm1, swarm3.AsPeer); + peerChainState = await swarm1.GetPeerChainStateAsync( + TimeSpan.FromSeconds(1), default); + Assert.Equal( + new[] + { + new PeerChainState((BoundPeer)swarm2.AsPeer, 1, 1024), + new PeerChainState((BoundPeer)swarm3.AsPeer, 0, 0), + }.ToHashSet(), + peerChainState.ToHashSet() + ); + } + finally + { + await StopAsync(swarm2); + await StopAsync(swarm3); + } + } + private async Task StartAsync( Swarm swarm, CancellationToken cancellationToken = default diff --git a/Libplanet/Net/PeerChainState.cs b/Libplanet/Net/PeerChainState.cs new file mode 100644 index 0000000000..a336ab4f4c --- /dev/null +++ b/Libplanet/Net/PeerChainState.cs @@ -0,0 +1,38 @@ +using System.Numerics; + +namespace Libplanet.Net +{ + /// + /// The blockchain state of . + /// + public readonly struct PeerChainState + { + public PeerChainState(BoundPeer peer, long tipIndex, BigInteger totalDifficulty) + { + Peer = peer; + TipIndex = tipIndex; + TotalDifficulty = totalDifficulty; + } + + /// + /// The peer with chain. + /// + public BoundPeer Peer { get; } + + /// + /// The blockchain tip of the . + /// + public long TipIndex { get; } + + /// + /// The total difficulty of the blockchain of the . + /// + public BigInteger TotalDifficulty { get; } + + /// + public override string ToString() + { + return $"{Peer}, {TipIndex}, {TotalDifficulty}"; + } + } +} diff --git a/Libplanet/Net/Swarm.cs b/Libplanet/Net/Swarm.cs index c34ef0f852..89befe2dc4 100644 --- a/Libplanet/Net/Swarm.cs +++ b/Libplanet/Net/Swarm.cs @@ -404,6 +404,26 @@ public string TraceTable() return Transport is null ? string.Empty : (Transport as NetMQTransport)?.Trace(); } + /// + /// Gets the of the connected . + /// + /// A timeout value for dialing. + /// + /// A cancellation token used to propagate notification that this + /// operation should be canceled. + /// + /// of the connected . + public async Task> GetPeerChainStateAsync( + TimeSpan? dialTimeout, + CancellationToken cancellationToken + ) + { + return (await DialToExistingPeers(dialTimeout, cancellationToken)) + .Where(pp => !(pp.Item1 is null || pp.Item2 is null)) + .Select(pp => + new PeerChainState(pp.Item1, pp.Item2.TipIndex, pp.Item2.TotalDifficulty)); + } + /// /// Preemptively downloads blocks from registered s. ///