Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Substrate.NetApi.Test/Substrate.NetApi.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
Expand Down
74 changes: 74 additions & 0 deletions Substrate.NetApi.TestNode/ClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;
using Substrate.NetApi.Model.Extrinsics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Substrate.NetApi.TestNode
{
public class ClientTests
{
private SubstrateClient _client;

[SetUp]
public void Setup()
{
_client = new SubstrateClient(new Uri("ws://rpc-parachain.bajun.network"), ChargeTransactionPayment.Default());
}

[TearDown]
public async Task TeardownAsync()
{
await _client.CloseAsync();
}

[Test]
public async Task Connect_ShouldConnectSuccessfullyAsync()
{
Assert.That(_client.IsConnected, Is.False);

await _client.ConnectAsync();
Assert.That(_client.IsConnected, Is.True);
}

[Test]
public async Task Connect_ShouldDisconnectSuccessfullyAsync()
{
await _client.ConnectAsync();
Assert.That(_client.IsConnected, Is.True);

await _client.CloseAsync();
Assert.That(_client.IsConnected, Is.False);
}

[Test]
public async Task Connect_ShouldTriggerEventAsync()
{
var onConnectionSetTriggered = new TaskCompletionSource<bool>();
_client.ConnectionSet += (sender, e) => onConnectionSetTriggered.SetResult(true);

await _client.ConnectAsync();

await Task.WhenAny(onConnectionSetTriggered.Task, Task.Delay(TimeSpan.FromMinutes(1)));
Assert.That(onConnectionSetTriggered.Task.IsCompleted, Is.True);
}

[Test]
public async Task OnConnectionLost_ShouldThrowDisconnectedEventAsync()
{
var onConnectionLostTriggered = new TaskCompletionSource<bool>();
_client.ConnectionLost += (sender, e) => onConnectionLostTriggered.SetResult(true);

await _client.ConnectAsync();
await _client.CloseAsync();

await Task.WhenAny(onConnectionLostTriggered.Task, Task.Delay(TimeSpan.FromMinutes(1)));
Assert.That(onConnectionLostTriggered.Task.IsCompleted, Is.True);
}
}
}
68 changes: 59 additions & 9 deletions Substrate.NetApi/SubstrateClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,22 @@ public class SubstrateClient : IDisposable
private ClientWebSocket _socket;

/// <summary>
/// Bypass Remote Certificate Validation. Useful when testing with self-signed SSL certificates.
/// The "ping" to check the connection status
/// </summary>
private int _connectionCheckDelay = 500;

/// <summary>
/// The connexion lost event trigger when the websocket change state to disconnected
/// </summary>
public event EventHandler ConnectionLost;

/// <summary>
/// Event triggered when the connection is set
/// </summary>
public event EventHandler ConnectionSet;

/// <summary>
/// Bypass Remote Certificate Validation. Useful when testing with self-signed SSL certificates.
/// </summary>
private readonly bool _bypassRemoteCertificateValidation;

Expand Down Expand Up @@ -88,7 +103,7 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific
/// <value> Information describing the meta. </value>
public MetaData MetaData { get; private set; }

/// <summary>
/// <summary>
/// Network runtime version
/// </summary>
public RuntimeVersion RuntimeVersion { get; private set; }
Expand Down Expand Up @@ -205,17 +220,21 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell
#if NETSTANDARD2_0
throw new NotSupportedException("Bypass remote certification validation not supported in NETStandard2.0");
#elif NETSTANDARD2_1_OR_GREATER
_socket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true;
_socket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true;
#endif
}
}

_connectTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, _connectTokenSource.Token);
await _socket.ConnectAsync(_uri, linkedTokenSource.Token);

// Triger the event
OnConnectionSet();

linkedTokenSource.Dispose();
_connectTokenSource.Dispose();
_connectTokenSource = null;
//_connectTokenSource.Dispose();
//_connectTokenSource = null;
Logger.Debug("Connected to Websocket.");

var formatter = new JsonMessageFormatter();
Expand Down Expand Up @@ -270,7 +289,38 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell
}
}

//_jsonRpc.TraceSource.Switch.Level = SourceLevels.All;
// Start a background task to monitor the WebSocket connection
_ = Task.Run(async () =>
{
while (IsConnected)
{
await Task.Delay(_connectionCheckDelay);

if (!IsConnected)
{
// Raise the ConnectionLost event on a separate thread
ThreadPool.QueueUserWorkItem(state =>
{
OnConnectionLost();
});
}
}
});
}

/// <summary>
/// Raises the event when the connection to the server is lost.
/// </summary>
protected virtual void OnConnectionLost() {
ConnectionLost?.Invoke(this, EventArgs.Empty);
}

/// <summary>
/// Raises the event when the connection to the server is set.
/// </summary>
protected virtual void OnConnectionSet()
{
ConnectionSet?.Invoke(this, EventArgs.Empty);
}

/// <summary>
Expand Down Expand Up @@ -446,16 +496,16 @@ public async Task CloseAsync()
/// <returns> An asynchronous result. </returns>
public async Task CloseAsync(CancellationToken token)
{
_connectTokenSource?.Cancel();

await Task.Run(() =>
await Task.Run(async () =>
{
// cancel remaining request tokens
foreach (var key in _requestTokenSourceDict.Keys) key?.Cancel();
_requestTokenSourceDict.Clear();

if (_socket != null && _socket.State == WebSocketState.Open)
{
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
_connectTokenSource?.Cancel();
_jsonRpc?.Dispose();
Logger.Debug("Client closed.");
}
Expand Down