From 675736fe429a4ab3fca5a197ab2f0ee5551fff1e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 23 May 2021 01:04:15 +0100 Subject: [PATCH] Bootstrapping streaming client. Added all relevant models. Started unit test project. --- .../Resources/AccountSubscribe.json | 1 + Solnet.Rpc.Test/SolanaStreamingClientTest.cs | 45 ++++ Solnet.Rpc.Test/Solnet.Rpc.Test.csproj | 27 ++ Solnet.sln | 31 ++- src/Solnet.Examples/SolnetRpcTester.cs | 28 +- src/Solnet.Rpc/Core/Sockets/IWebSocket.cs | 17 ++ .../Core/Sockets/SolanaStreamingClient.cs | 255 ++++++++++++++++++ .../Core/Sockets/SubscriptionChannel.cs | 18 ++ .../Core/Sockets/SubscriptionEvent.cs | 24 ++ .../Core/Sockets/SubscriptionState.cs | 40 +++ .../Core/Sockets/SubscriptionStatus.cs | 15 ++ .../Core/Sockets/WebSocketWrapper.cs | 56 ++++ .../Messages/JsonRpcStreamResponse.cs | 15 ++ src/Solnet.Rpc/Models/Logs.cs | 21 ++ src/Solnet.Rpc/Models/ProgramInfo.cs | 15 ++ src/Solnet.Rpc/Models/SlotInfo.cs | 18 ++ src/Solnet.Rpc/Types/Commitment.cs | 16 ++ 17 files changed, 630 insertions(+), 12 deletions(-) create mode 100644 Solnet.Rpc.Test/Resources/AccountSubscribe.json create mode 100644 Solnet.Rpc.Test/SolanaStreamingClientTest.cs create mode 100644 Solnet.Rpc.Test/Solnet.Rpc.Test.csproj create mode 100644 src/Solnet.Rpc/Core/Sockets/IWebSocket.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/SolanaStreamingClient.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs create mode 100644 src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs create mode 100644 src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs create mode 100644 src/Solnet.Rpc/Models/Logs.cs create mode 100644 src/Solnet.Rpc/Models/ProgramInfo.cs create mode 100644 src/Solnet.Rpc/Models/SlotInfo.cs create mode 100644 src/Solnet.Rpc/Types/Commitment.cs diff --git a/Solnet.Rpc.Test/Resources/AccountSubscribe.json b/Solnet.Rpc.Test/Resources/AccountSubscribe.json new file mode 100644 index 00000000..53b1d5e8 --- /dev/null +++ b/Solnet.Rpc.Test/Resources/AccountSubscribe.json @@ -0,0 +1 @@ +{"method":"accountSubscribe","params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12",{"encoding":"base64"}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/Solnet.Rpc.Test/SolanaStreamingClientTest.cs b/Solnet.Rpc.Test/SolanaStreamingClientTest.cs new file mode 100644 index 00000000..d318ccc9 --- /dev/null +++ b/Solnet.Rpc.Test/SolanaStreamingClientTest.cs @@ -0,0 +1,45 @@ +using Solnet.Rpc.Core.Sockets; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using System; +using System.Threading; +using System.IO; +using System.Net.WebSockets; +using System.Text; + +namespace Solnet.Rpc.Test +{ + [TestClass] + public class SolanaStreamingClientTest + { + [TestMethod] + public void SubscribeAccountInfoTest() + { + var expected = File.ReadAllText("Resources/AccountSubscribe.json"); + ReadOnlyMemory result = new ReadOnlyMemory(); + var socket = new Mock(); + + SolanaStreamingClient sut = new SolanaStreamingClient("wss://api.mainnet-beta.solana.com/", socket.Object); + + var pubKey = "CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"; + + var action = new Mock>>(); + + socket.Setup(_ => _.ConnectAsync(It.IsAny(), It.IsAny())) + .Callback(() => socket.SetupGet(s => s.State).Returns(WebSocketState.Open)); + + socket.Setup(_ => _.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())) + .Callback, WebSocketMessageType, bool, CancellationToken>((mem, _, _, _) => result = mem); + + sut.Init().Wait(); + sut.SubscribeAccountInfo(pubKey, action.Object); + + socket.Verify(s => s.SendAsync(It.IsAny>(), WebSocketMessageType.Text, true, It.IsAny())); + var res = Encoding.UTF8.GetString(result.Span); + Assert.AreEqual(expected, res); + } + + } +} diff --git a/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj b/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj new file mode 100644 index 00000000..f9e08a06 --- /dev/null +++ b/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Solnet.sln b/Solnet.sln index f7570242..661102d6 100644 --- a/Solnet.sln +++ b/Solnet.sln @@ -1,16 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Rpc", "src\Solnet.Rpc\Solnet.Rpc.csproj", "{FE8D270F-30AE-4818-AC7B-030591820419}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31129.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.Rpc", "src\Solnet.Rpc\Solnet.Rpc.csproj", "{FE8D270F-30AE-4818-AC7B-030591820419}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Models", "src\Solnet.Models\Solnet.Models.csproj", "{8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.Models", "src\Solnet.Models\Solnet.Models.csproj", "{8B503BC5-F1C8-4F87-8C53-AD4D6CEFFCF4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.KeyStore", "src\Solnet.KeyStore\Solnet.KeyStore.csproj", "{BA5BFA52-8010-4438-BC87-153A34464CAA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.KeyStore", "src\Solnet.KeyStore\Solnet.KeyStore.csproj", "{BA5BFA52-8010-4438-BC87-153A34464CAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Wallet", "src\Solnet.Wallet\Solnet.Wallet.csproj", "{DA22B717-4FD7-4D08-A546-DFF3B21B7BAE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.Wallet", "src\Solnet.Wallet\Solnet.Wallet.csproj", "{DA22B717-4FD7-4D08-A546-DFF3B21B7BAE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Examples", "src\Solnet.Examples\Solnet.Examples.csproj", "{1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.Examples", "src\Solnet.Examples\Solnet.Examples.csproj", "{1198ED67-B1F1-4DE9-BDE8-AE6C96DA33A6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Util", "src\Solnet.Util\Solnet.Util.csproj", "{5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Solnet.Util", "src\Solnet.Util\Solnet.Util.csproj", "{5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Solnet.Rpc.Test", "Solnet.Rpc.Test\Solnet.Rpc.Test.csproj", "{E907F659-7FDE-4B98-85CD-1FBC6B1656CD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -42,9 +47,15 @@ Global {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {5B8896C9-4B06-41C9-B0F9-05B9F6190CA5}.Release|Any CPU.Build.0 = Release|Any CPU - {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3C534072-1F5F-4625-9831-ACBBA28E5C47}.Release|Any CPU.Build.0 = Release|Any CPU + {E907F659-7FDE-4B98-85CD-1FBC6B1656CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E907F659-7FDE-4B98-85CD-1FBC6B1656CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E907F659-7FDE-4B98-85CD-1FBC6B1656CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E907F659-7FDE-4B98-85CD-1FBC6B1656CD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BE3287F3-8CBC-4793-8CC9-A1AE4B8266ED} EndGlobalSection EndGlobal diff --git a/src/Solnet.Examples/SolnetRpcTester.cs b/src/Solnet.Examples/SolnetRpcTester.cs index 58f9d491..d0b8b4e7 100644 --- a/src/Solnet.Examples/SolnetRpcTester.cs +++ b/src/Solnet.Examples/SolnetRpcTester.cs @@ -2,15 +2,19 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.Json; using System.Threading.Tasks; +using Solnet.Rpc.Core.Sockets; using Solnet.Rpc.Http; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; namespace Solnet.Examples { class SolnetRpcTester { - static void Example(string[] args) + static void Main(string[] args) { SolanaJsonRpcClient c = new SolanaJsonRpcClient(); @@ -23,7 +27,27 @@ static void Example(string[] args) //var blockCommitment = c.GetBlockCommitment(78561320); - var blockTime = c.GetBlockTime(78561320); + //var blockTime = c.GetBlockTime(78561320); + + SolanaStreamingClient c2 = new SolanaStreamingClient("wss://api.mainnet-beta.solana.com/"); + + c2.Init().Wait(); + + + var sub = c2.SubscribeAccountInfo("9UGxCidmZtU1PM7Tbhv2twQ8ChsS6S3HdL1xo56fSVWn", (s, data) => + { + Console.WriteLine("received data " + data.Value.Lamports); + }); + + sub.SubscriptionChanged += Sub_SubscriptionChanged; + + Console.ReadKey(); + Console.ReadKey(); + } + + private static void Sub_SubscriptionChanged(object sender, SubscriptionEvent e) + { + Console.WriteLine("subcription changed to: " + e.Status); } } } diff --git a/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs b/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs new file mode 100644 index 00000000..b6ddc89a --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/IWebSocket.cs @@ -0,0 +1,17 @@ +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public interface IWebSocket : IDisposable + { + WebSocketState State { get; } + + Task ConnectAsync(Uri uri, CancellationToken cancellationToken); + ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken); + Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken); + ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken); + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/SolanaStreamingClient.cs b/src/Solnet.Rpc/Core/Sockets/SolanaStreamingClient.cs new file mode 100644 index 00000000..e22fd464 --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/SolanaStreamingClient.cs @@ -0,0 +1,255 @@ +using Solnet.Rpc.Core.Sockets; +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public class SolanaStreamingClient + { + IWebSocket _clientSocket; + + string _socketUri; + + private int _id; + + Dictionary unconfirmedSubscriptions = new Dictionary(); + + Dictionary confirmedSubscriptions = new Dictionary(); + private int GetNextId() + { + lock (this) + { + return _id++; + } + } + + public SolanaStreamingClient(string nodeUri, IWebSocket socket = default) + { + _clientSocket = socket ?? new WebSocketWrapper(new ClientWebSocket()); + _socketUri = nodeUri ?? "wss://"; + } + + public async Task Init() + { + await _clientSocket.ConnectAsync(new Uri(_socketUri), CancellationToken.None).ConfigureAwait(false); + _ = Task.Run(StartListening); + } + + private async Task StartListening() + { + try + { + while (_clientSocket.State == WebSocketState.Open) + { + await ReadNextMessage().ConfigureAwait(false); + } + } + catch (Exception) + { + throw; + } + finally + { + _clientSocket.Dispose(); + } + } + + private async Task ReadNextMessage(CancellationToken cancellationToken = default) + { + var buffer = new byte[32768]; + Memory mem = new Memory(buffer); + var messageParts = new StringBuilder(); + int count = 0; + + ValueWebSocketReceiveResult result = await _clientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); + count = result.Count; + if (result.MessageType == WebSocketMessageType.Close) + { + await _clientSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken); + } + else + { + if (!result.EndOfMessage) + { + MemoryStream ms = new MemoryStream(); + ms.Write(mem.Span); + + + while (!result.EndOfMessage) + { + result = await _clientSocket.ReceiveAsync(mem, cancellationToken).ConfigureAwait(false); + ms.Write(mem.Span); + count += result.Count; + } + + mem = new Memory(ms.ToArray()); + } + else + { + mem = mem.Slice(0, count); + } + //Console.WriteLine("\n\n[" + DateTime.UtcNow.ToLongTimeString() + "][Received]\n" + UTF8Encoding.UTF8.GetString(mem.ToArray(), 0, count)); + + + HandleNewMessage(mem); + } + } + + private void HandleNewMessage(Memory mem) + { + JsonReaderOptions opts = new JsonReaderOptions() { MaxDepth = 1 }; + Utf8JsonReader asd = new Utf8JsonReader(mem.Span, opts); + + asd.Read(); + + string prop = "", method = ""; + int id = -1, intResult = -1; + + while (asd.Read()) + { + switch (asd.TokenType) + { + case JsonTokenType.PropertyName: + prop = asd.GetString(); + break; + case JsonTokenType.StartObject: + if (prop == "params") + { + HandleDataMessage(ref asd, method); + // + // + } + break; + case JsonTokenType.String: + if (prop == "method") + { + method = asd.GetString(); + } + break; + case JsonTokenType.Number: + if (prop == "id") + { + id = asd.GetInt32(); + } else if (prop == "result") + { + intResult = asd.GetInt32(); + } + if(id != -1 && intResult != -1) + { + ConfirmSubscription(id, intResult); + } + break; + + } + } + } + + #region SubscriptionMapHandling + + private void ConfirmSubscription(int internalId, int resultId) + { + SubscriptionState sub = null; + lock(this) + { + if(unconfirmedSubscriptions.Remove(internalId, out sub)) + { + confirmedSubscriptions.Add(resultId, sub); + } + } + + sub.RaiseEvent(sub, new SubscriptionEvent(SubscriptionStatus.Subscribed)); + } + + private void AddSubscription(SubscriptionState subscription, int internalId) + { + lock(this) + { + unconfirmedSubscriptions.Add(internalId, subscription); + } + } + + private SubscriptionState RetrieveSubscription(int subscriptionId) + { + lock(this) + { + return confirmedSubscriptions[subscriptionId]; + } + } + #endregion + + private void HandleDataMessage(ref Utf8JsonReader reader, string method) + { + JsonSerializerOptions opts = new JsonSerializerOptions() { MaxDepth = 64, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + switch (method) + { + case "accountNotification": + var accNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(accNotif.Subscription, accNotif.Result); + break; + case "logsNotification": + var logsNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(logsNotif.Subscription, logsNotif.Result); + break; + case "programNotification": + var programNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(programNotif.Subscription, programNotif.Result); + break; + case "signatureNotification": + var signatureNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(signatureNotif.Subscription, signatureNotif.Result); + // remove subscription from map + break; + case "slotNotification": + var slotNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(slotNotif.Subscription, slotNotif.Result); + break; + case "rootNotification": + var rootNotif = JsonSerializer.Deserialize>(ref reader, opts); + NotifyData(rootNotif.Subscription, rootNotif.Result); + break; + } + } + + private void NotifyData(int subscription, object data) + { + var sub = RetrieveSubscription(subscription); + + sub.HandleData(data); + } + + private Type GetTypeFromMethod(string method) => method switch + { + "accountNotification" => typeof(JsonRpcStreamResponse>) + }; + + + public async Task SubscribeAccountInfoAsync(string pubkey, Action> callback) + { + var sub = new SubscriptionState>(SubscriptionChannel.Account, callback); + + var msg = new JsonRpcRequest(GetNextId(), "accountSubscribe", new List() { pubkey, new Dictionary() { { "encoding", "base64" } } }); + + var json = JsonSerializer.SerializeToUtf8Bytes(msg, new JsonSerializerOptions() { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + + + ReadOnlyMemory mem = new ReadOnlyMemory(json); + await _clientSocket.SendAsync(mem, WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false); + + AddSubscription(sub, msg.Id); + return sub; + } + + public SubscriptionState SubscribeAccountInfo(string pubkey, Action> callback) + => SubscribeAccountInfoAsync(pubkey, callback).Result; + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs new file mode 100644 index 00000000..6c9f1e15 --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionChannel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public enum SubscriptionChannel + { + Account, + Logs, + Program, + Signature, + Slot, + Root + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs new file mode 100644 index 00000000..199c14e9 --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionEvent.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public class SubscriptionEvent : EventArgs + { + public SubscriptionStatus Status { get; } + + public string Error { get; } + + public string Code { get; } + + internal SubscriptionEvent(SubscriptionStatus status, string error = default, string code = default) + { + Status = status; + Error = error; + Code = code; + } + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs new file mode 100644 index 00000000..fb74e531 --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionState.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public abstract class SubscriptionState + { + public SubscriptionChannel Channel { get; } + + public event EventHandler SubscriptionChanged; + + public ImmutableList AdditionalParameters { get; } + + internal void RaiseEvent(object sender, SubscriptionEvent e) => SubscriptionChanged?.Invoke(sender, e); + + internal abstract void HandleData(object data); + + internal SubscriptionState(SubscriptionChannel chan, IList aditionalParameters = default) + { + Channel = chan; + AdditionalParameters = aditionalParameters?.ToImmutableList(); + } + } + + internal class SubscriptionState : SubscriptionState + { + internal Action, T> DataHandler; + + internal SubscriptionState(SubscriptionChannel chan, Action handler) : base(chan) + { + DataHandler = handler; + } + + internal override void HandleData(object data) => DataHandler(this, (T)data); + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs b/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs new file mode 100644 index 00000000..736a30bf --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/SubscriptionStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + public enum SubscriptionStatus + { + WaitingResult, + Unsubscribed, + Subscribed + } +} diff --git a/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs b/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs new file mode 100644 index 00000000..114736a9 --- /dev/null +++ b/src/Solnet.Rpc/Core/Sockets/WebSocketWrapper.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Core.Sockets +{ + class WebSocketWrapper : IWebSocket + { + private readonly ClientWebSocket webSocket; + + internal WebSocketWrapper(ClientWebSocket webSocket) + { + this.webSocket = webSocket; + } + + public WebSocketState State => webSocket.State; + + public Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + => webSocket.CloseAsync(closeStatus, statusDescription, cancellationToken); + + public Task ConnectAsync(Uri uri, CancellationToken cancellationToken) + => webSocket.ConnectAsync(uri, cancellationToken); + + public ValueTask ReceiveAsync(Memory buffer, CancellationToken cancellationToken) + => webSocket.ReceiveAsync(buffer, cancellationToken); + + public ValueTask SendAsync(ReadOnlyMemory buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) + => webSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + webSocket.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs b/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs new file mode 100644 index 00000000..8db15a3d --- /dev/null +++ b/src/Solnet.Rpc/Messages/JsonRpcStreamResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Messages +{ + public class JsonRpcStreamResponse + { + public ResponseValue Result { get; set; } + + public int Subscription { get; set; } + } +} diff --git a/src/Solnet.Rpc/Models/Logs.cs b/src/Solnet.Rpc/Models/Logs.cs new file mode 100644 index 00000000..ca31b841 --- /dev/null +++ b/src/Solnet.Rpc/Models/Logs.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Models +{ + public class LogsInfo + { + public string Signature { get; set; } + + [JsonPropertyName("err")] + public string Error { get; set; } + + public string[] Logs { get; set; } + + } +} diff --git a/src/Solnet.Rpc/Models/ProgramInfo.cs b/src/Solnet.Rpc/Models/ProgramInfo.cs new file mode 100644 index 00000000..1d3ccd84 --- /dev/null +++ b/src/Solnet.Rpc/Models/ProgramInfo.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Models +{ + public class ProgramInfo + { + public string PubKey { get; set; } + + public AccountInfo Account { get; set; } + } +} diff --git a/src/Solnet.Rpc/Models/SlotInfo.cs b/src/Solnet.Rpc/Models/SlotInfo.cs new file mode 100644 index 00000000..2227df3d --- /dev/null +++ b/src/Solnet.Rpc/Models/SlotInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc.Models +{ + public class SlotInfo + { + public int Parent { get; set; } + + public int Root { get; set; } + + public int Slot { get; set; } + + } +} diff --git a/src/Solnet.Rpc/Types/Commitment.cs b/src/Solnet.Rpc/Types/Commitment.cs new file mode 100644 index 00000000..4ad95ca8 --- /dev/null +++ b/src/Solnet.Rpc/Types/Commitment.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.Json.Serialization; + +namespace Solnet.Rpc.Types +{ + public enum Commitment + { + Finalized, + Confirmed, + Processed + } +}