From ce0099688ad93f82350c9eff469501d3a07fa0f0 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sun, 12 Nov 2023 13:06:37 -0500 Subject: [PATCH 01/15] feat: default session saving completed & tested --- .../WalletConnectSharp.Crypto/KeyChain.cs | 4 +- .../FileSystemStorage.cs | 67 ++++++--- .../InMemoryStorage.cs | 40 ++++- .../SignClientFixture.cs | 14 +- .../WalletConnectSharp.Sign.Test/SignTests.cs | 90 ++++++++---- .../TwoClientsFixture.cs | 19 ++- .../Controllers/MessageTracker.cs | 4 +- .../Controllers/Relayer.cs | 62 +++++--- .../Controllers/Subscriber.cs | 3 +- .../Controllers/TypedMessageHandler.cs | 10 ++ WalletConnectSharp.Core/Interfaces/ICore.cs | 2 +- .../MessageHandler/TypedEventHandler.cs | 8 +- .../Controllers/AddressProvider.cs | 137 ++++++++++++++---- WalletConnectSharp.Sign/Engine.cs | 13 +- .../Interfaces/IAddressProvider.cs | 4 + WalletConnectSharp.Sign/Interfaces/IEngine.cs | 2 +- .../Models/DefaultsLoadingEventArgs.cs | 13 ++ .../Models/SessionRequestEventHandler.cs | 29 +++- .../WalletConnectSignClient.cs | 2 + 19 files changed, 398 insertions(+), 125 deletions(-) create mode 100644 WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs diff --git a/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs b/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs index 7c83a88..d58720e 100644 --- a/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs +++ b/Core Modules/WalletConnectSharp.Crypto/KeyChain.cs @@ -193,7 +193,9 @@ private async Task> GetKeyChain() private async Task SaveKeyChain() { - await Storage.SetItem(StorageKey, this._keyChain); + // We need to copy the contents, otherwise Dispose() + // may clear the reference stored inside InMemoryStorage + await Storage.SetItem(StorageKey, new Dictionary(this._keyChain)); } public void Dispose() diff --git a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs index 5414eb9..69338ce 100644 --- a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs @@ -88,12 +88,42 @@ private async Task Save() Directory.CreateDirectory(path); } - var json = JsonConvert.SerializeObject(Entries, - new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); + string json; + lock (entriesLock) + { + json = JsonConvert.SerializeObject(Entries, + new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); + } - await _semaphoreSlim.WaitAsync(); - await File.WriteAllTextAsync(FilePath, json, Encoding.UTF8); - _semaphoreSlim.Release(); + try + { + if (!Disposed) + await _semaphoreSlim.WaitAsync(); + int count = 5; + IOException lastException; + do + { + try + { + await File.WriteAllTextAsync(FilePath, json, Encoding.UTF8); + return; + } + catch (IOException e) + { + WCLogger.LogError($"Got error saving storage file: retries left {count}"); + await Task.Delay(100); + count--; + lastException = e; + } + } while (count > 0); + + throw lastException; + } + finally + { + if (!Disposed) + _semaphoreSlim.Release(); + } } private async Task Load() @@ -101,25 +131,22 @@ private async Task Load() if (!File.Exists(FilePath)) return; - await _semaphoreSlim.WaitAsync(); - var json = await File.ReadAllTextAsync(FilePath, Encoding.UTF8); - _semaphoreSlim.Release(); - + string json; try { - Entries = JsonConvert.DeserializeObject>(json, - new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }); + await _semaphoreSlim.WaitAsync(); + json = await File.ReadAllTextAsync(FilePath, Encoding.UTF8); } - catch (JsonSerializationException e) + finally { - // Move the file to a .unsupported file - // and start fresh - WCLogger.LogError(e); - WCLogger.LogError("Cannot load JSON file, moving data to .unsupported file to force continue"); - if (File.Exists(FilePath + ".unsupported")) - File.Move(FilePath + ".unsupported", FilePath + "." + Guid.NewGuid() + ".unsupported"); - File.Move(FilePath, FilePath + ".unsupported"); - Entries = new Dictionary(); + _semaphoreSlim.Release(); + } + + // Hard fail here if the storage file is bad + lock (entriesLock) + { + Entries = JsonConvert.DeserializeObject>(json, + new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }); } } diff --git a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs index 8ff6bde..ba8ac5d 100644 --- a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs @@ -5,6 +5,7 @@ namespace WalletConnectSharp.Storage { public class InMemoryStorage : IKeyValueStorage { + protected readonly object entriesLock = new object(); protected Dictionary Entries = new Dictionary(); private bool _initialized = false; protected bool Disposed; @@ -18,51 +19,76 @@ public virtual Task Init() public virtual Task GetKeys() { IsInitialized(); - return Task.FromResult(Entries.Keys.ToArray()); + lock (entriesLock) + { + return Task.FromResult(Entries.Keys.ToArray()); + } } public virtual async Task GetEntriesOfType() { IsInitialized(); + // GetEntries is thread-safe return (await GetEntries()).OfType().ToArray(); } public virtual Task GetEntries() { IsInitialized(); - return Task.FromResult(Entries.Values.ToArray()); + lock (entriesLock) + { + return Task.FromResult(Entries.Values.ToArray()); + } } public virtual Task GetItem(string key) { IsInitialized(); - return Task.FromResult(Entries[key] is T ? (T)Entries[key] : default); + lock (entriesLock) + { + return Task.FromResult(Entries[key] is T ? (T)Entries[key] : default); + } } public virtual Task SetItem(string key, T value) { IsInitialized(); - Entries[key] = value; + lock (entriesLock) + { + Entries[key] = value; + } + return Task.CompletedTask; } public virtual Task RemoveItem(string key) { IsInitialized(); - Entries.Remove(key); + lock (entriesLock) + { + Entries.Remove(key); + } + return Task.CompletedTask; } public virtual Task HasItem(string key) { IsInitialized(); - return Task.FromResult(Entries.ContainsKey(key)); + lock (entriesLock) + { + return Task.FromResult(Entries.ContainsKey(key)); + } } public virtual Task Clear() { IsInitialized(); - Entries.Clear(); + lock (entriesLock) + { + Entries.Clear(); + } + return Task.CompletedTask; } diff --git a/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs b/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs index 594f397..4460965 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs @@ -1,12 +1,16 @@ using WalletConnectSharp.Core; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Storage; +using WalletConnectSharp.Storage.Interfaces; using WalletConnectSharp.Tests.Common; namespace WalletConnectSharp.Sign.Test; public class SignClientFixture : TwoClientsFixture { + public IKeyValueStorage StorageOverrideA; + public IKeyValueStorage StorageOverrideB; + public SignClientOptions OptionsA { get; protected set; } public SignClientOptions OptionsB { get; protected set; } @@ -26,11 +30,11 @@ public override async Task Init() { Description = "An example dapp to showcase WalletConnectSharpv2", Icons = new[] { "https://walletconnect.com/meta/favicon.ico" }, - Name = $"WalletConnectSharpv2 Dapp Example - {Guid.NewGuid().ToString()}", + Name = $"WalletConnectSharpv2 Dapp Example", Url = "https://walletconnect.com" }, // Omit if you want persistant storage - Storage = new InMemoryStorage() + Storage = StorageOverrideA ?? new InMemoryStorage() }; OptionsB = new SignClientOptions() @@ -41,14 +45,16 @@ public override async Task Init() { Description = "An example wallet to showcase WalletConnectSharpv2", Icons = new[] { "https://walletconnect.com/meta/favicon.ico" }, - Name = $"WalletConnectSharpv2 Wallet Example - {Guid.NewGuid().ToString()}", + Name = $"WalletConnectSharpv2 Wallet Example", Url = "https://walletconnect.com" }, // Omit if you want persistant storage - Storage = new InMemoryStorage() + Storage = StorageOverrideB ?? new InMemoryStorage() }; ClientA = await WalletConnectSignClient.Init(OptionsA); ClientB = await WalletConnectSignClient.Init(OptionsB); } + + } diff --git a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs index c3cc145..5a5f8b0 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs @@ -6,12 +6,14 @@ using WalletConnectSharp.Sign.Models.Engine; using WalletConnectSharp.Tests.Common; using Xunit; +using Xunit.Abstractions; namespace WalletConnectSharp.Sign.Test { public class SignTests : IClassFixture { private SignClientFixture _cryptoFixture; + private readonly ITestOutputHelper _testOutputHelper; private const string AllowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; [RpcMethod("test_method"), RpcRequestOptions(Clock.ONE_MINUTE, 99998)] @@ -52,9 +54,10 @@ public WalletConnectSignClient ClientB } } - public SignTests(SignClientFixture cryptoFixture) + public SignTests(SignClientFixture cryptoFixture, ITestOutputHelper testOutputHelper) { this._cryptoFixture = cryptoFixture; + _testOutputHelper = testOutputHelper; } public static async Task TestConnectMethod(ISignClient clientA, ISignClient clientB) @@ -384,50 +387,50 @@ public async void TestTwoUniqueSessionRequestResponse() } [Fact, Trait("Category", "integration")] - public async void TestTwoUniqueSessionRequestResponseUsingAddressProviderDefaults() + public async Task TestTwoUniqueSessionRequestResponseUsingAddressProviderDefaults() { await _cryptoFixture.WaitForClientsReady(); - var testAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; - var testMethod = "test_method"; - var testMethod2 = "test_method_2"; - - var dappConnectOptions = new ConnectOptions() + var dappClient = ClientA; + var walletClient = ClientB; + if (!dappClient.AddressProvider.HasDefaultSession && !walletClient.AddressProvider.HasDefaultSession) { - RequiredNamespaces = new RequiredNamespaces() + var testAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; + var testMethod = "test_method"; + var testMethod2 = "test_method_2"; + + var dappConnectOptions = new ConnectOptions() { + RequiredNamespaces = new RequiredNamespaces() { - "eip155", new ProposedNamespace() { - Methods = new[] + "eip155", + new ProposedNamespace() { - testMethod, - testMethod2 - }, - Chains = new[] - { - "eip155:1" - }, - Events = new[] - { - "chainChanged", "accountsChanged" + Methods = new[] { testMethod, testMethod2 }, + Chains = new[] { "eip155:1" }, + Events = new[] { "chainChanged", "accountsChanged" } } } } - } - }; + }; - var dappClient = ClientA; - var connectData = await dappClient.Connect(dappConnectOptions); + var connectData = await dappClient.Connect(dappConnectOptions); - var walletClient = ClientB; - var proposal = await walletClient.Pair(connectData.Uri); + var proposal = await walletClient.Pair(connectData.Uri); - var approveData = await walletClient.Approve(proposal, testAddress); + var approveData = await walletClient.Approve(proposal, testAddress); - var sessionData = await connectData.Approval; - await approveData.Acknowledged(); + await connectData.Approval; + await approveData.Acknowledged(); + } + else + { + Assert.True(dappClient.AddressProvider.HasDefaultSession); + Assert.True(walletClient.AddressProvider.HasDefaultSession); + } + var defaultSessionTopic = dappClient.AddressProvider.DefaultSession.Topic; var rnd = new Random(); var a = rnd.Next(100); var b = rnd.Next(100); @@ -474,9 +477,11 @@ public async void TestTwoUniqueSessionRequestResponseUsingAddressProviderDefault // from the dappClient.Engine.Request function call (the response Result or throws an Exception) // We do it here for the sake of testing dappClient.Engine.SessionRequestEvents() - .FilterResponses((r) => r.Topic == sessionData.Topic) + .FilterResponses((r) => r.Topic == defaultSessionTopic) .OnResponse += (responseData) => { + Assert.True(responseData.Topic == defaultSessionTopic); + var response = responseData.Response; var data = response.Result; @@ -557,5 +562,30 @@ public async void TestAddressProviderDefaults() Assert.Equal("eip155:1", dappClient.AddressProvider.DefaultChain); Assert.Equal("eip155", dappClient.AddressProvider.DefaultNamespace); } + + [Fact, Trait("Category", "integration")] + public async void TestAddressProviderDefaultsSaving() + { + await _cryptoFixture.WaitForClientsReady(); + + await TestTwoUniqueSessionRequestResponseUsingAddressProviderDefaults(); + + var defaultSessionTopic = _cryptoFixture.ClientA.AddressProvider.DefaultSession.Topic; + + _cryptoFixture.StorageOverrideA = _cryptoFixture.ClientA.Core.Storage; + _cryptoFixture.StorageOverrideB = _cryptoFixture.ClientB.Core.Storage; + + await Task.Delay(100); + + await _cryptoFixture.DisposeAndReset(); + + await Task.Delay(100); + + var reloadedDefaultSessionTopic = _cryptoFixture.ClientA.AddressProvider.DefaultSession.Topic; + + Assert.Equal(defaultSessionTopic, reloadedDefaultSessionTopic); + + await TestTwoUniqueSessionRequestResponseUsingAddressProviderDefaults(); + } } } diff --git a/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs b/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs index e630033..7cec984 100644 --- a/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs +++ b/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs @@ -1,6 +1,6 @@ namespace WalletConnectSharp.Tests.Common; -public abstract class TwoClientsFixture +public abstract class TwoClientsFixture where TClient : IDisposable { public TClient ClientA { get; protected set; } public TClient ClientB { get; protected set; } @@ -19,4 +19,21 @@ public async Task WaitForClientsReady() while (ClientA == null || ClientB == null) await Task.Delay(10); } + + public async Task DisposeAndReset() + { + if (ClientA != null) + { + ClientA.Dispose(); + ClientA = default; + } + + if (ClientB != null) + { + ClientB.Dispose(); + ClientB = default; + } + + await Init(); + } } diff --git a/WalletConnectSharp.Core/Controllers/MessageTracker.cs b/WalletConnectSharp.Core/Controllers/MessageTracker.cs index 2b629e3..242ce48 100644 --- a/WalletConnectSharp.Core/Controllers/MessageTracker.cs +++ b/WalletConnectSharp.Core/Controllers/MessageTracker.cs @@ -177,7 +177,9 @@ public async Task Delete(string topic) private async Task SetRelayerMessages(Dictionary messages) { - await _core.Storage.SetItem(StorageKey, messages); + // Clone dictionary for Storage, otherwise we'll be saving + // the reference + await _core.Storage.SetItem(StorageKey, new Dictionary(messages)); } private async Task> GetRelayerMessages() diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 62e6aa7..6b81c37 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -199,33 +199,46 @@ protected virtual Task BuildConnection(string url) protected virtual void RegisterProviderEventListeners() { - Provider.RawMessageReceived += (sender, s) => - { - OnProviderPayload(s); - }; + Provider.RawMessageReceived += ProviderOnRawMessageReceived; + Provider.Connected += ProviderOnConnected; + Provider.Disconnected += ProviderOnDisconnected; + Provider.ErrorReceived += ProviderOnErrorReceived; + } - Provider.Connected += (sender, connection) => - { - this.OnConnected?.Invoke(this, EventArgs.Empty); - }; + private void ProviderOnErrorReceived(object sender, Exception e) + { + if (Disposed) return; - Provider.Disconnected += async (sender, args) => - { - this.OnDisconnected?.Invoke(this, EventArgs.Empty); + this.OnErrored?.Invoke(this, e); + } - if (this._transportExplicitlyClosed) - return; + private async void ProviderOnDisconnected(object sender, EventArgs e) + { + if (Disposed) return; - // Attempt to reconnect after one second - await Task.Delay(1000); + this.OnDisconnected?.Invoke(this, EventArgs.Empty); - await RestartTransport(); - }; + if (this._transportExplicitlyClosed) + return; - Provider.ErrorReceived += (sender, args) => - { - this.OnErrored?.Invoke(this, args); - }; + // Attempt to reconnect after one second + await Task.Delay(1000); + + await RestartTransport(); + } + + private void ProviderOnConnected(object sender, IJsonRpcConnection e) + { + if (Disposed) return; + + this.OnConnected?.Invoke(sender, EventArgs.Empty); + } + + private void ProviderOnRawMessageReceived(object sender, string e) + { + if (Disposed) return; + + OnProviderPayload(e); } protected virtual void RegisterEventListeners() @@ -507,6 +520,13 @@ protected virtual void Dispose(bool disposing) Subscriber?.Dispose(); Publisher?.Dispose(); Messages?.Dispose(); + + // Un-listen to events + Provider.Connected -= ProviderOnConnected; + Provider.Disconnected -= ProviderOnDisconnected; + Provider.RawMessageReceived -= ProviderOnRawMessageReceived; + Provider.ErrorReceived -= ProviderOnErrorReceived; + Provider?.Dispose(); } diff --git a/WalletConnectSharp.Core/Controllers/Subscriber.cs b/WalletConnectSharp.Core/Controllers/Subscriber.cs index 2b8e944..671853a 100644 --- a/WalletConnectSharp.Core/Controllers/Subscriber.cs +++ b/WalletConnectSharp.Core/Controllers/Subscriber.cs @@ -582,8 +582,7 @@ private void OnBatchSubscribe(ActiveSubscription[] subscriptions) if (subscriptions.Length == 0) return; foreach (var sub in subscriptions) { - SetSubscription(sub.Id, sub); - this.pending.Remove(sub.Topic); + OnSubscribe(sub.Id, sub); } } diff --git a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs index fbaa626..7410674 100644 --- a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs @@ -66,6 +66,12 @@ async void RelayerMessageCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; + if (!await this.Core.Crypto.KeyChain.Has(topic)) + { + WCLogger.LogError($"Received message for topic we don't have: {topic}"); + return; + } + var options = DecodeOptionForTopic(topic); var payload = await this.Core.Crypto.Decode(topic, message, options); @@ -101,6 +107,8 @@ async void RequestCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; + if (!await this.Core.Crypto.HasKeys(topic)) return; + var options = DecodeOptionForTopic(topic); var payload = await this.Core.Crypto.Decode>(topic, message, options); @@ -116,6 +124,8 @@ async void ResponseCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; + + if (!await this.Core.Crypto.HasKeys(topic)) return; var options = DecodeOptionForTopic(topic); diff --git a/WalletConnectSharp.Core/Interfaces/ICore.cs b/WalletConnectSharp.Core/Interfaces/ICore.cs index 5ba2545..e71f15c 100644 --- a/WalletConnectSharp.Core/Interfaces/ICore.cs +++ b/WalletConnectSharp.Core/Interfaces/ICore.cs @@ -59,7 +59,7 @@ public interface ICore : IModule /// SDK executions /// public IKeyValueStorage Storage { get; } - + /// /// The module this Core module is using. Use this for handling /// custom message types (request or response) and for sending messages (request, responses or errors) diff --git a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs index 2f0cb34..17c3526 100644 --- a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs +++ b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs @@ -15,7 +15,7 @@ namespace WalletConnectSharp.Sign.Models /// /// The request type to filter for /// The response typ to filter for - public class TypedEventHandler + public class TypedEventHandler : IDisposable { protected static Dictionary> _instances = new Dictionary>(); @@ -258,5 +258,11 @@ async Task VerifyContext(string hash, Metadata metadata) return context; } + + public virtual void Dispose() + { + var context = _ref.Context; + _instances.Remove(context); + } } } diff --git a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs index 47b7383..eed978e 100644 --- a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs +++ b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs @@ -1,4 +1,6 @@ -using WalletConnectSharp.Sign.Interfaces; +using Newtonsoft.Json; +using WalletConnectSharp.Common.Logging; +using WalletConnectSharp.Sign.Interfaces; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Sign.Models.Engine.Events; @@ -6,6 +8,15 @@ namespace WalletConnectSharp.Sign.Controllers; public class AddressProvider : IAddressProvider { + public struct DefaultData + { + public SessionStruct Session; + public string Namespace; + public string ChainId; + } + + public event EventHandler DefaultsLoading; + public bool HasDefaultSession { get @@ -30,9 +41,43 @@ public string Context } } - public SessionStruct DefaultSession { get; set; } - public string DefaultNamespace { get; set; } - public string DefaultChain { get; set; } + private DefaultData _state; + + public SessionStruct DefaultSession + { + get + { + return _state.Session; + } + set + { + _state.Session = value; + } + } + + public string DefaultNamespace + { + get + { + return _state.Namespace; + } + set + { + _state.Namespace = value; + } + } + + public string DefaultChain + { + get + { + return _state.ChainId; + } + set + { + _state.ChainId = value; + } + } public ISession Sessions { get; private set; } private ISignClient _client; @@ -46,7 +91,27 @@ public AddressProvider(ISignClient client) client.SessionConnected += ClientOnSessionConnected; client.SessionDeleted += ClientOnSessionDeleted; client.SessionUpdated += ClientOnSessionUpdated; - client.SessionApproved += ClientOnSessionConnected; + client.SessionApproved += ClientOnSessionConnected; + } + + public virtual async Task SaveDefaults() + { + await _client.Core.Storage.SetItem($"{Context}-default-session", _state); + } + + public virtual async Task LoadDefaults() + { + var key = $"{Context}-default-session"; + if (await _client.Core.Storage.HasItem(key)) + { + _state = await _client.Core.Storage.GetItem($"{Context}-default-session"); + } + else + { + _state = new DefaultData(); + } + + DefaultsLoading?.Invoke(this, new DefaultsLoadingEventArgs(ref _state)); } private void ClientOnSessionUpdated(object sender, SessionEvent e) @@ -75,45 +140,52 @@ private void ClientOnSessionConnected(object sender, SessionStruct e) } } - private void UpdateDefaultChainAndNamespace() + private async void UpdateDefaultChainAndNamespace() { - if (HasDefaultSession) + try { - var currentDefault = DefaultNamespace; - if (currentDefault != null && DefaultSession.RequiredNamespaces.ContainsKey(currentDefault)) + if (HasDefaultSession) { - // DefaultNamespace is still valid - var currentChain = DefaultChain; - if (currentChain == null || - DefaultSession.RequiredNamespaces[DefaultNamespace].Chains.Contains(currentChain)) + var currentDefault = DefaultNamespace; + if (currentDefault != null && DefaultSession.RequiredNamespaces.ContainsKey(currentDefault)) { - // DefaultChain is still valid + // DefaultNamespace is still valid + var currentChain = DefaultChain; + if (currentChain == null || + DefaultSession.RequiredNamespaces[DefaultNamespace].Chains.Contains(currentChain)) + { + // DefaultChain is still valid + return; + } + + DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; return; } - DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; - return; - } - - // DefaultNamespace is null or not found in RequiredNamespaces, update it - DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault(); - if (DefaultNamespace != null) - { - DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; - } - else - { - // TODO The Keys property is unordered! Maybe this needs to be updated - DefaultNamespace = DefaultSession.RequiredNamespaces.Keys.FirstOrDefault(); + // DefaultNamespace is null or not found in RequiredNamespaces, update it + DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault(); if (DefaultNamespace != null) { DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; } + else + { + // TODO The Keys property is unordered! Maybe this needs to be updated + DefaultNamespace = DefaultSession.RequiredNamespaces.Keys.FirstOrDefault(); + if (DefaultNamespace != null) + { + DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + } + } + } + else + { + DefaultNamespace = null; } } - else + finally { - DefaultNamespace = null; + await SaveDefaults(); } } @@ -126,6 +198,11 @@ public Caip25Address CurrentAddress(string @namespace = null, SessionStruct sess return session.CurrentAddress(@namespace); } + public async Task Init() + { + await this.LoadDefaults(); + } + public Caip25Address[] AllAddresses(string @namespace = null, SessionStruct session = default) { @namespace ??= DefaultNamespace; diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index 7bdda1b..846aa3e 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -31,6 +31,7 @@ public partial class Engine : IEnginePrivate, IEngine, IModule private const int KeyLength = 32; private bool _initialized = false; + private Dictionary _disposeActions = new Dictionary(); /// /// The using this Engine @@ -224,7 +225,11 @@ private void RegisterRelayerEvents() /// The managing events for the given types T, TR public TypedEventHandler SessionRequestEvents() { - return SessionRequestEventHandler.GetInstance(Client.Core, PrivateThis); + var uniqueKey = typeof(T).FullName + "--" + typeof(TR).FullName; + var instance = SessionRequestEventHandler.GetInstance(Client.Core, PrivateThis); + if (!_disposeActions.ContainsKey(uniqueKey)) + _disposeActions.Add(uniqueKey, () => instance.Dispose()); + return instance; } /// @@ -864,7 +869,11 @@ protected virtual void Dispose(bool disposing) if (disposing) { - Client?.Dispose(); + foreach (var action in _disposeActions.Values) + { + action(); + } + _disposeActions.Clear(); } Disposed = true; diff --git a/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs b/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs index c5d107f..c16bbf2 100644 --- a/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs +++ b/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs @@ -5,6 +5,8 @@ namespace WalletConnectSharp.Sign.Interfaces; public interface IAddressProvider : IModule { + event EventHandler DefaultsLoading; + bool HasDefaultSession { get; } SessionStruct DefaultSession { get; set; } @@ -17,5 +19,7 @@ public interface IAddressProvider : IModule Caip25Address CurrentAddress( string chain = null, SessionStruct session = default); + Task Init(); + Caip25Address[] AllAddresses(string chain = null, SessionStruct session = default); } diff --git a/WalletConnectSharp.Sign/Interfaces/IEngine.cs b/WalletConnectSharp.Sign/Interfaces/IEngine.cs index 7f9ae03..35c3977 100644 --- a/WalletConnectSharp.Sign/Interfaces/IEngine.cs +++ b/WalletConnectSharp.Sign/Interfaces/IEngine.cs @@ -15,7 +15,7 @@ namespace WalletConnectSharp.Sign.Interfaces /// is an sub-type of and represents the actual Engine. This is /// different than the Sign client. /// - public interface IEngine : IEngineAPI + public interface IEngine : IEngineAPI, IDisposable { /// /// The this Engine is using diff --git a/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs b/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs new file mode 100644 index 0000000..365449e --- /dev/null +++ b/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs @@ -0,0 +1,13 @@ +using WalletConnectSharp.Sign.Controllers; + +namespace WalletConnectSharp.Sign.Models; + +public class DefaultsLoadingEventArgs : EventArgs +{ + public DefaultsLoadingEventArgs(ref AddressProvider.DefaultData defaults) + { + Defaults = defaults; + } + + public AddressProvider.DefaultData Defaults { get; } +} diff --git a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs index 7fa28fa..01c365a 100644 --- a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs +++ b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs @@ -14,6 +14,7 @@ namespace WalletConnectSharp.Sign.Models public class SessionRequestEventHandler : TypedEventHandler { private IEnginePrivate _enginePrivate; + private List _disposeActions = new List(); /// /// Get a singleton instance of this class for the given context. The context @@ -44,20 +45,32 @@ protected SessionRequestEventHandler(ICore engine, IEnginePrivate enginePrivate) protected override TypedEventHandler BuildNew(ICore _ref, Func, bool> requestPredicate, Func, bool> responsePredicate) { - return new SessionRequestEventHandler(_ref, _enginePrivate) + var instance = new SessionRequestEventHandler(_ref, _enginePrivate) { requestPredicate = requestPredicate, responsePredicate = responsePredicate }; + + _disposeActions.Add(() => + { + instance.Dispose(); + }); + + return instance; } protected override void Setup() { var wrappedRef = TypedEventHandler, TR>.GetInstance(_ref); - + wrappedRef.OnRequest += WrappedRefOnOnRequest; wrappedRef.OnResponse += WrappedRefOnOnResponse; - } + + _disposeActions.Add(() => + { + wrappedRef.Dispose(); + }); + } private Task WrappedRefOnOnResponse(ResponseEventArgs e) { @@ -94,5 +107,15 @@ await _enginePrivate.SetPendingSessionRequest(new PendingRequestStruct() await base.RequestCallback(e.Topic, sessionRequest); } + + public override void Dispose() + { + foreach (var action in _disposeActions) + { + action(); + } + + base.Dispose(); + } } } diff --git a/WalletConnectSharp.Sign/WalletConnectSignClient.cs b/WalletConnectSharp.Sign/WalletConnectSignClient.cs index 6a793ee..659bc9f 100644 --- a/WalletConnectSharp.Sign/WalletConnectSignClient.cs +++ b/WalletConnectSharp.Sign/WalletConnectSignClient.cs @@ -476,6 +476,7 @@ private async Task Initialize() await Session.Init(); await Proposal.Init(); await Engine.Init(); + await AddressProvider.Init(); } public void Dispose() @@ -495,6 +496,7 @@ protected virtual void Dispose(bool disposing) Session?.Dispose(); Proposal?.Dispose(); PendingRequests?.Dispose(); + Engine?.Dispose(); } Disposed = true; From 57d064d54c69a34d8b93ef40847b321add19fcfe Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sun, 12 Nov 2023 13:35:53 -0500 Subject: [PATCH 02/15] fix: auth client regression --- .../AuthClientTest.cs | 21 +++++++++++-------- .../Controllers/TypedMessageHandler.cs | 14 ++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs b/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs index 504ff77..bf4862f 100644 --- a/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs +++ b/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs @@ -11,6 +11,7 @@ using WalletConnectSharp.Storage; using WalletConnectSharp.Tests.Common; using Xunit; +using Xunit.Abstractions; using ErrorResponse = WalletConnectSharp.Auth.Models.ErrorResponse; namespace WalletConnectSharp.Auth.Tests @@ -26,6 +27,7 @@ public class AuthClientTests : IClassFixture, IAsyncLifetim }; private readonly CryptoWalletFixture _cryptoWalletFixture; + private readonly ITestOutputHelper _testOutputHelper; private IAuthClient PeerA; public IAuthClient PeerB; @@ -54,9 +56,10 @@ public string WalletAddress } } - public AuthClientTests(CryptoWalletFixture cryptoFixture) + public AuthClientTests(CryptoWalletFixture cryptoFixture, ITestOutputHelper testOutputHelper) { this._cryptoWalletFixture = cryptoFixture; + _testOutputHelper = testOutputHelper; } [Fact, Trait("Category", "unit")] @@ -120,7 +123,7 @@ public async void TestKnownPairings() var ogHistorySizeB = historyB.Keys.Length; List responses = new List(); - TaskCompletionSource responseTask = new TaskCompletionSource(); + TaskCompletionSource knownPairingTask = new TaskCompletionSource(); async void OnPeerBOnAuthRequested(object sender, AuthRequest request) { @@ -139,9 +142,9 @@ void OnPeerAOnAuthResponded(object sender, AuthResponse args) var sessionTopic = args.Topic; var cacao = args.Response.Result; var signature = cacao.Signature; - Console.WriteLine($"{sessionTopic}: {signature}"); + _testOutputHelper.WriteLine($"{sessionTopic}: {signature}"); responses.Add(args); - responseTask.SetResult(args); + knownPairingTask.SetResult(args); } PeerA.AuthResponded += OnPeerAOnAuthResponded; @@ -150,9 +153,9 @@ void OnPeerAOnAuthError(object sender, AuthErrorResponse args) { var sessionTopic = args.Topic; var error = args.Error; - Console.WriteLine($"{sessionTopic}: {error}"); + _testOutputHelper.WriteLine($"{sessionTopic}: {error}"); responses.Add(args); - responseTask.SetResult(args); + knownPairingTask.SetResult(args); } PeerA.AuthError += OnPeerAOnAuthError; @@ -161,17 +164,17 @@ void OnPeerAOnAuthError(object sender, AuthErrorResponse args) await PeerB.Core.Pairing.Pair(requestData.Uri); - await responseTask.Task; + await knownPairingTask.Task; // Reset - responseTask = new TaskCompletionSource(); + knownPairingTask = new TaskCompletionSource(); // Get last pairing, that is the one we just made var knownPairing = PeerA.Core.Pairing.Pairings[^1]; var requestData2 = await PeerA.Request(DefaultRequestParams, knownPairing.Topic); - await responseTask.Task; + await knownPairingTask.Task; Assert.Null(requestData2.Uri); diff --git a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs index 7410674..57cfaf2 100644 --- a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs @@ -66,12 +66,6 @@ async void RelayerMessageCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; - if (!await this.Core.Crypto.KeyChain.Has(topic)) - { - WCLogger.LogError($"Received message for topic we don't have: {topic}"); - return; - } - var options = DecodeOptionForTopic(topic); var payload = await this.Core.Crypto.Decode(topic, message, options); @@ -107,9 +101,9 @@ async void RequestCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; - if (!await this.Core.Crypto.HasKeys(topic)) return; - var options = DecodeOptionForTopic(topic); + + if (options == null && !await this.Core.Crypto.HasKeys(topic)) return; var payload = await this.Core.Crypto.Decode>(topic, message, options); @@ -124,10 +118,10 @@ async void ResponseCallback(object sender, MessageEvent e) var topic = e.Topic; var message = e.Message; - - if (!await this.Core.Crypto.HasKeys(topic)) return; var options = DecodeOptionForTopic(topic); + + if (options == null && !await this.Core.Crypto.HasKeys(topic)) return; var rawResultPayload = await this.Core.Crypto.Decode(topic, message, options); From 45da07d18157fa68d262fc916879a8f53bb43633 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sun, 12 Nov 2023 13:41:19 -0500 Subject: [PATCH 03/15] fix: print stored keys, passes test --- Tests/WalletConnectSharp.Sign.Test/SignTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs index 5a5f8b0..8d6bcaa 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs @@ -1,9 +1,11 @@ using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; +using WalletConnectSharp.Crypto; using WalletConnectSharp.Network.Models; using WalletConnectSharp.Sign.Interfaces; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Sign.Models.Engine; +using WalletConnectSharp.Storage; using WalletConnectSharp.Tests.Common; using Xunit; using Xunit.Abstractions; @@ -579,6 +581,8 @@ public async void TestAddressProviderDefaultsSaving() await _cryptoFixture.DisposeAndReset(); + _testOutputHelper.WriteLine(string.Join(",", _cryptoFixture.ClientB.Core.Crypto.KeyChain.Keychain.Values)); + await Task.Delay(100); var reloadedDefaultSessionTopic = _cryptoFixture.ClientA.AddressProvider.DefaultSession.Topic; From 4d65e41bae87bcc627c0a782add14e40a331db7d Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sat, 25 Nov 2023 16:32:39 -0500 Subject: [PATCH 04/15] fix: dispose pattern, test return type and other --- .../FileSystemStorage.cs | 15 +++---- .../InMemoryStorage.cs | 39 +++++-------------- .../AuthClientTest.cs | 24 ++++++------ .../SignatureTest.cs | 4 +- .../CryptoTests.cs | 2 +- .../RelayTests.cs | 12 +++--- .../SignClientConcurrency.cs | 2 +- .../WalletConnectSharp.Sign.Test/SignTests.cs | 12 +++--- .../FileSystemStorageTest.cs | 8 ++-- .../AuthTests.cs | 6 +-- .../SignTests.cs | 22 +++++------ .../MessageHandler/TypedEventHandler.cs | 21 ++++++++-- .../Controllers/AddressProvider.cs | 16 +++++--- .../Interfaces/IAddressProvider.cs | 2 +- .../Models/DefaultsLoadingEventArgs.cs | 2 +- .../Models/SessionRequestEventHandler.cs | 21 +++++----- 16 files changed, 100 insertions(+), 108 deletions(-) diff --git a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs index 69338ce..2e44f8b 100644 --- a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Text; using Newtonsoft.Json; using WalletConnectSharp.Common.Logging; @@ -89,11 +90,8 @@ private async Task Save() } string json; - lock (entriesLock) - { - json = JsonConvert.SerializeObject(Entries, - new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); - } + json = JsonConvert.SerializeObject(Entries, + new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }); try { @@ -143,11 +141,8 @@ private async Task Load() } // Hard fail here if the storage file is bad - lock (entriesLock) - { - Entries = JsonConvert.DeserializeObject>(json, - new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }); - } + Entries = JsonConvert.DeserializeObject>(json, + new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }); } protected override void Dispose(bool disposing) diff --git a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs index ba8ac5d..77243b8 100644 --- a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Storage.Interfaces; @@ -5,8 +6,7 @@ namespace WalletConnectSharp.Storage { public class InMemoryStorage : IKeyValueStorage { - protected readonly object entriesLock = new object(); - protected Dictionary Entries = new Dictionary(); + protected ConcurrentDictionary Entries = new ConcurrentDictionary(); private bool _initialized = false; protected bool Disposed; @@ -19,10 +19,7 @@ public virtual Task Init() public virtual Task GetKeys() { IsInitialized(); - lock (entriesLock) - { - return Task.FromResult(Entries.Keys.ToArray()); - } + return Task.FromResult(Entries.Keys.ToArray()); } public virtual async Task GetEntriesOfType() @@ -35,28 +32,19 @@ public virtual async Task GetEntriesOfType() public virtual Task GetEntries() { IsInitialized(); - lock (entriesLock) - { - return Task.FromResult(Entries.Values.ToArray()); - } + return Task.FromResult(Entries.Values.ToArray()); } public virtual Task GetItem(string key) { IsInitialized(); - lock (entriesLock) - { - return Task.FromResult(Entries[key] is T ? (T)Entries[key] : default); - } + return Task.FromResult(Entries[key] is T ? (T)Entries[key] : default); } public virtual Task SetItem(string key, T value) { IsInitialized(); - lock (entriesLock) - { - Entries[key] = value; - } + Entries[key] = value; return Task.CompletedTask; } @@ -64,10 +52,7 @@ public virtual Task SetItem(string key, T value) public virtual Task RemoveItem(string key) { IsInitialized(); - lock (entriesLock) - { - Entries.Remove(key); - } + Entries.Remove(key, out _); return Task.CompletedTask; } @@ -75,19 +60,13 @@ public virtual Task RemoveItem(string key) public virtual Task HasItem(string key) { IsInitialized(); - lock (entriesLock) - { - return Task.FromResult(Entries.ContainsKey(key)); - } + return Task.FromResult(Entries.ContainsKey(key)); } public virtual Task Clear() { IsInitialized(); - lock (entriesLock) - { - Entries.Clear(); - } + Entries.Clear(); return Task.CompletedTask; } diff --git a/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs b/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs index bf4862f..386c236 100644 --- a/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs +++ b/Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs @@ -63,7 +63,7 @@ public AuthClientTests(CryptoWalletFixture cryptoFixture, ITestOutputHelper test } [Fact, Trait("Category", "unit")] - public async void TestInit() + public async Task TestInit() { Assert.NotNull(PeerA); Assert.NotNull(PeerB); @@ -80,7 +80,7 @@ public async void TestInit() } [Fact, Trait("Category", "unit")] - public async void TestPairs() + public async Task TestPairs() { var ogPairSize = PeerA.Core.Pairing.Pairings.Length; @@ -112,7 +112,7 @@ public async void TestPairs() } [Fact, Trait("Category", "unit")] - public async void TestKnownPairings() + public async Task TestKnownPairings() { var ogSizeA = PeerA.Core.Pairing.Pairings.Length; var history = await PeerA.AuthHistory(); @@ -192,7 +192,7 @@ void OnPeerAOnAuthError(object sender, AuthErrorResponse args) } [Fact, Trait("Category", "unit")] - public async void HandlesAuthRequests() + public async Task HandlesAuthRequests() { var ogSize = PeerB.Requests.Length; @@ -215,7 +215,7 @@ public async void HandlesAuthRequests() } [Fact, Trait("Category", "unit")] - public async void TestErrorResponses() + public async Task TestErrorResponses() { var ogPSize = PeerA.Core.Pairing.Pairings.Length; @@ -254,7 +254,7 @@ void OnPeerAOnAuthResponded(object sender, AuthResponse response) } [Fact, Trait("Category", "unit")] - public async void HandlesSuccessfulResponse() + public async Task HandlesSuccessfulResponse() { var ogPSize = PeerA.Core.Pairing.Pairings.Length; @@ -298,7 +298,7 @@ async void OnPeerBOnAuthRequested(object sender, AuthRequest request) } [Fact, Trait("Category", "unit")] - public async void TestCustomRequestExpiry() + public async Task TestCustomRequestExpiry() { var uri = ""; var expiry = 1000; @@ -336,7 +336,7 @@ async void OnPeerBOnAuthRequested(object sender, AuthRequest request) } [Fact, Trait("Category", "unit")] - public async void TestGetPendingPairings() + public async Task TestGetPendingPairings() { var ogCount = PeerB.PendingRequests.Count; @@ -362,7 +362,7 @@ public async void TestGetPendingPairings() } [Fact, Trait("Category", "unit")] - public async void TestGetPairings() + public async Task TestGetPairings() { var peerAOgSize = PeerA.Core.Pairing.Pairings.Length; var peerBOgSize = PeerB.Core.Pairing.Pairings.Length; @@ -390,7 +390,7 @@ public async void TestGetPairings() } [Fact, Trait("Category", "unit")] - public async void TestPing() + public async Task TestPing() { TaskCompletionSource receivedAuthRequest = new TaskCompletionSource(); TaskCompletionSource receivedClientPing = new TaskCompletionSource(); @@ -429,7 +429,7 @@ public async void TestPing() } [Fact, Trait("Category", "unit")] - public async void TestDisconnectedPairing() + public async Task TestDisconnectedPairing() { var peerAOgSize = PeerA.Core.Pairing.Pairings.Length; var peerBOgSize = PeerB.Core.Pairing.Pairings.Length; @@ -469,7 +469,7 @@ public async void TestDisconnectedPairing() } [Fact, Trait("Category", "unit")] - public async void TestReceivesMetadata() + public async Task TestReceivesMetadata() { var receivedMetadataName = ""; var ogPairingSize = PeerA.Core.Pairing.Pairings.Length; diff --git a/Tests/WalletConnectSharp.Auth.Tests/SignatureTest.cs b/Tests/WalletConnectSharp.Auth.Tests/SignatureTest.cs index 4ba407d..f322668 100644 --- a/Tests/WalletConnectSharp.Auth.Tests/SignatureTest.cs +++ b/Tests/WalletConnectSharp.Auth.Tests/SignatureTest.cs @@ -22,7 +22,7 @@ public class SignatureTest Expiration Time: 2022-10-11T23:03:35.700Z".Replace("\r", ""); [Fact, Trait("Category", "unit")] - public async void TestValidEip1271Signature() + public async Task TestValidEip1271Signature() { var signature = new Cacao.CacaoSignature.EIP1271CacaoSignature( "0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c"); @@ -34,7 +34,7 @@ public async void TestValidEip1271Signature() } [Fact, Trait("Category", "unit")] - public async void TestBadEip1271Signature() + public async Task TestBadEip1271Signature() { var signature = new Cacao.CacaoSignature.EIP1271CacaoSignature( "0xdead5719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c"); diff --git a/Tests/WalletConnectSharp.Crypto.Tests/CryptoTests.cs b/Tests/WalletConnectSharp.Crypto.Tests/CryptoTests.cs index 051ef54..ecee01d 100644 --- a/Tests/WalletConnectSharp.Crypto.Tests/CryptoTests.cs +++ b/Tests/WalletConnectSharp.Crypto.Tests/CryptoTests.cs @@ -32,7 +32,7 @@ public CryptoTests(CryptoFixture cryptoFixture) } [Fact, Trait("Category", "unit")] - public async void TestEncodeDecode() + public async Task TestEncodeDecode() { await _cryptoFixture.WaitForModulesReady(); diff --git a/Tests/WalletConnectSharp.Network.Tests/RelayTests.cs b/Tests/WalletConnectSharp.Network.Tests/RelayTests.cs index 2d47e0d..806cb63 100644 --- a/Tests/WalletConnectSharp.Network.Tests/RelayTests.cs +++ b/Tests/WalletConnectSharp.Network.Tests/RelayTests.cs @@ -47,7 +47,7 @@ public async Task BuildGoodURL() } [Fact, Trait("Category", "integration")] - public async void ConnectAndRequest() + public async Task ConnectAndRequest() { var url = await BuildGoodURL(); var connection = new WebsocketConnection(url); @@ -60,7 +60,7 @@ public async void ConnectAndRequest() } [Fact, Trait("Category", "integration")] - public async void RequestWithoutConnect() + public async Task RequestWithoutConnect() { var url = await BuildGoodURL(); var connection = new WebsocketConnection(url); @@ -72,7 +72,7 @@ public async void RequestWithoutConnect() } [Fact, Trait("Category", "integration")] - public async void ThrowOnJsonRpcError() + public async Task ThrowOnJsonRpcError() { var url = await BuildGoodURL(); var connection = new WebsocketConnection(url); @@ -83,7 +83,7 @@ await Assert.ThrowsAsync(() => } [Fact, Trait("Category", "integration")] - public async void ThrowsOnUnavailableHost() + public async Task ThrowsOnUnavailableHost() { var connection = new WebsocketConnection(BAD_WS_URL); var provider = new JsonRpcProvider(connection); @@ -92,7 +92,7 @@ public async void ThrowsOnUnavailableHost() } [Fact, Trait("Category", "integration")] - public async void ReconnectsWithNewProvidedHost() + public async Task ReconnectsWithNewProvidedHost() { var url = await BuildGoodURL(); var connection = new WebsocketConnection(BAD_WS_URL); @@ -107,7 +107,7 @@ public async void ReconnectsWithNewProvidedHost() } [Fact, Trait("Category", "integration")] - public async void DoesNotDoubleRegisterListeners() + public async Task DoesNotDoubleRegisterListeners() { var url = await BuildGoodURL(); var connection = new WebsocketConnection(url); diff --git a/Tests/WalletConnectSharp.Sign.Test/SignClientConcurrency.cs b/Tests/WalletConnectSharp.Sign.Test/SignClientConcurrency.cs index cba4c0a..4797f42 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignClientConcurrency.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignClientConcurrency.cs @@ -46,7 +46,7 @@ public SignClientConcurrency(ITestOutputHelper output) } [Fact, Trait("Category", "concurrency")] - public async void TestConcurrentClients() => await _TestConcurrentClients().WithTimeout(TimeSpan.FromMinutes(20)); + public async Task TestConcurrentClients() => await _TestConcurrentClients().WithTimeout(TimeSpan.FromMinutes(20)); private int[][] BatchArray(int[] array, int size) { diff --git a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs index 8d6bcaa..c9c5f98 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignTests.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignTests.cs @@ -119,7 +119,7 @@ public static async Task TestConnectMethod(ISignClient clientA, I } [Fact, Trait("Category", "integration")] - public async void TestApproveSession() + public async Task TestApproveSession() { await _cryptoFixture.WaitForClientsReady(); @@ -127,7 +127,7 @@ public async void TestApproveSession() } [Fact, Trait("Category", "integration")] - public async void TestRejectSession() + public async Task TestRejectSession() { await _cryptoFixture.WaitForClientsReady(); @@ -172,7 +172,7 @@ public async void TestRejectSession() } [Fact, Trait("Category", "integration")] - public async void TestSessionRequestResponse() + public async Task TestSessionRequestResponse() { await _cryptoFixture.WaitForClientsReady(); @@ -272,7 +272,7 @@ public async void TestSessionRequestResponse() } [Fact, Trait("Category", "integration")] - public async void TestTwoUniqueSessionRequestResponse() + public async Task TestTwoUniqueSessionRequestResponse() { await _cryptoFixture.WaitForClientsReady(); @@ -508,7 +508,7 @@ public async Task TestTwoUniqueSessionRequestResponseUsingAddressProviderDefault } [Fact, Trait("Category", "integration")] - public async void TestAddressProviderDefaults() + public async Task TestAddressProviderDefaults() { await _cryptoFixture.WaitForClientsReady(); @@ -566,7 +566,7 @@ public async void TestAddressProviderDefaults() } [Fact, Trait("Category", "integration")] - public async void TestAddressProviderDefaultsSaving() + public async Task TestAddressProviderDefaultsSaving() { await _cryptoFixture.WaitForClientsReady(); diff --git a/Tests/WalletConnectSharp.Storage.Test/FileSystemStorageTest.cs b/Tests/WalletConnectSharp.Storage.Test/FileSystemStorageTest.cs index 7713112..3656db1 100644 --- a/Tests/WalletConnectSharp.Storage.Test/FileSystemStorageTest.cs +++ b/Tests/WalletConnectSharp.Storage.Test/FileSystemStorageTest.cs @@ -9,7 +9,7 @@ namespace WalletConnectSharp.Storage.Test public class FileSystemStorageTest { [Fact, Trait("Category", "unit")] - public async void GetSetRemoveTest() + public async Task GetSetRemoveTest() { using (var tempFolder = new TempFolder()) { @@ -23,7 +23,7 @@ public async void GetSetRemoveTest() } [Fact, Trait("Category", "unit")] - public async void GetKeysTest() + public async Task GetKeysTest() { using (var tempFolder = new TempFolder()) { @@ -36,7 +36,7 @@ public async void GetKeysTest() } [Fact, Trait("Category", "unit")] - public async void GetEntriesTests() + public async Task GetEntriesTests() { using (var tempFolder = new TempFolder()) { @@ -51,7 +51,7 @@ public async void GetEntriesTests() } [Fact, Trait("Category", "unit")] - public async void HasItemTest() + public async Task HasItemTest() { using (var tempFolder = new TempFolder()) { diff --git a/Tests/WalletConnectSharp.Web3Wallet.Tests/AuthTests.cs b/Tests/WalletConnectSharp.Web3Wallet.Tests/AuthTests.cs index fb70c84..bc3799c 100644 --- a/Tests/WalletConnectSharp.Web3Wallet.Tests/AuthTests.cs +++ b/Tests/WalletConnectSharp.Web3Wallet.Tests/AuthTests.cs @@ -93,7 +93,7 @@ public async Task DisposeAsync() } [Fact, Trait("Category", "unit")] - public async void TestRespondToAuthRequest() + public async Task TestRespondToAuthRequest() { var request = await _dapp.Request(DefaultRequestParams); uriString = request.Uri; @@ -134,7 +134,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestShouldRejectAuthRequest() + public async Task TestShouldRejectAuthRequest() { var request = await _dapp.Request(DefaultRequestParams); uriString = request.Uri; @@ -181,7 +181,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestGetPendingAuthRequest() + public async Task TestGetPendingAuthRequest() { var request = await _dapp.Request(DefaultRequestParams); uriString = request.Uri; diff --git a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs index 208ea19..1f30aad 100644 --- a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs +++ b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs @@ -183,7 +183,7 @@ public async Task DisposeAsync() } [Fact, Trait("Category", "unit")] - public async void TestShouldApproveSessionProposal() + public async Task TestShouldApproveSessionProposal() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -207,7 +207,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestShouldRejectSessionProposal() + public async Task TestShouldRejectSessionProposal() { var rejectionError = Error.FromErrorType(ErrorType.USER_DISCONNECTED); @@ -246,7 +246,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestUpdateSession() + public async Task TestUpdateSession() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -285,7 +285,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestExtendSession() + public async Task TestExtendSession() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -321,7 +321,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestRespondToSessionRequest() + public async Task TestRespondToSessionRequest() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -399,7 +399,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestWalletDisconnectFromSession() + public async Task TestWalletDisconnectFromSession() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -446,7 +446,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestDappDisconnectFromSession() + public async Task TestDappDisconnectFromSession() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -493,7 +493,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestEmitSessionEvent() + public async Task TestEmitSessionEvent() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -551,7 +551,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestGetActiveSessions() + public async Task TestGetActiveSessions() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => @@ -591,7 +591,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestGetPendingSessionProposals() + public async Task TestGetPendingSessionProposals() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += (sender, @event) => @@ -610,7 +610,7 @@ await Task.WhenAll( } [Fact, Trait("Category", "unit")] - public async void TestGetPendingSessionRequests() + public async Task TestGetPendingSessionRequests() { TaskCompletionSource task1 = new TaskCompletionSource(); _wallet.SessionProposed += async (sender, @event) => diff --git a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs index 17c3526..dbc8dd5 100644 --- a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs +++ b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs @@ -141,6 +141,8 @@ public event ResponseMethod OnResponse } } + public bool Disposed { get; protected set; } + protected TypedEventHandler(ICore engine) { _ref = engine; @@ -259,10 +261,23 @@ async Task VerifyContext(string hash, Metadata metadata) return context; } - public virtual void Dispose() + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) { - var context = _ref.Context; - _instances.Remove(context); + if (Disposed) return; + + if (disposing) + { + var context = _ref.Context; + _instances.Remove(context); + } + + Disposed = true; } } } diff --git a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs index eed978e..8a346a0 100644 --- a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs +++ b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs @@ -15,7 +15,7 @@ public struct DefaultData public string ChainId; } - public event EventHandler DefaultsLoading; + public event EventHandler DefaultsLoaded; public bool HasDefaultSession { @@ -104,14 +104,14 @@ public virtual async Task LoadDefaults() var key = $"{Context}-default-session"; if (await _client.Core.Storage.HasItem(key)) { - _state = await _client.Core.Storage.GetItem($"{Context}-default-session"); + _state = await _client.Core.Storage.GetItem(key); } else { _state = new DefaultData(); } - DefaultsLoading?.Invoke(this, new DefaultsLoadingEventArgs(ref _state)); + DefaultsLoaded?.Invoke(this, new DefaultsLoadingEventArgs(_state)); } private void ClientOnSessionUpdated(object sender, SessionEvent e) @@ -155,10 +155,12 @@ private async void UpdateDefaultChainAndNamespace() DefaultSession.RequiredNamespaces[DefaultNamespace].Chains.Contains(currentChain)) { // DefaultChain is still valid + await SaveDefaults(); return; } DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + await SaveDefaults(); return; } @@ -177,15 +179,19 @@ private async void UpdateDefaultChainAndNamespace() DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; } } + } else { DefaultNamespace = null; } + + await SaveDefaults(); } - finally + catch (Exception e) { - await SaveDefaults(); + WCLogger.LogError(e); + throw; } } diff --git a/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs b/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs index c16bbf2..2ce881a 100644 --- a/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs +++ b/WalletConnectSharp.Sign/Interfaces/IAddressProvider.cs @@ -5,7 +5,7 @@ namespace WalletConnectSharp.Sign.Interfaces; public interface IAddressProvider : IModule { - event EventHandler DefaultsLoading; + event EventHandler DefaultsLoaded; bool HasDefaultSession { get; } diff --git a/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs b/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs index 365449e..c93c114 100644 --- a/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs +++ b/WalletConnectSharp.Sign/Models/DefaultsLoadingEventArgs.cs @@ -4,7 +4,7 @@ namespace WalletConnectSharp.Sign.Models; public class DefaultsLoadingEventArgs : EventArgs { - public DefaultsLoadingEventArgs(ref AddressProvider.DefaultData defaults) + public DefaultsLoadingEventArgs(in AddressProvider.DefaultData defaults) { Defaults = defaults; } diff --git a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs index 01c365a..cd45029 100644 --- a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs +++ b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs @@ -51,10 +51,7 @@ protected override TypedEventHandler BuildNew(ICore _ref, Func - { - instance.Dispose(); - }); + _disposeActions.Add(instance.Dispose); return instance; } @@ -66,10 +63,7 @@ protected override void Setup() wrappedRef.OnRequest += WrappedRefOnOnRequest; wrappedRef.OnResponse += WrappedRefOnOnResponse; - _disposeActions.Add(() => - { - wrappedRef.Dispose(); - }); + _disposeActions.Add(wrappedRef.Dispose); } private Task WrappedRefOnOnResponse(ResponseEventArgs e) @@ -108,13 +102,16 @@ await _enginePrivate.SetPendingSessionRequest(new PendingRequestStruct() await base.RequestCallback(e.Topic, sessionRequest); } - public override void Dispose() + protected override void Dispose(bool disposing) { - foreach (var action in _disposeActions) + if (disposing) { - action(); + foreach (var action in _disposeActions) + { + action(); + } } - + base.Dispose(); } } From 799ed20ddef53a8317ce094d07825c796a2eae78 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sat, 25 Nov 2023 21:56:28 -0500 Subject: [PATCH 05/15] fix: use default namespace and chain in Engine --- WalletConnectSharp.Sign/Engine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index 846aa3e..dd1c773 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -684,8 +684,8 @@ public async Task Request(string topic, T data, string chainId = null if (string.IsNullOrWhiteSpace(chainId)) { var sessionData = Client.Session.Get(topic); - var firstRequiredNamespace = sessionData.RequiredNamespaces.OrderedKeys[0]; - defaultChainId = sessionData.RequiredNamespaces[firstRequiredNamespace].Chains[0]; + var defaultNamespace = Client.AddressProvider.DefaultNamespace ?? sessionData.RequiredNamespaces.OrderedKeys[0]; + defaultChainId = Client.AddressProvider.DefaultChain ?? sessionData.RequiredNamespaces[defaultNamespace].Chains[0]; } else { From d322f90f18049d7f39a84e5c73772113300815cf Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Wed, 29 Nov 2023 12:02:25 -0500 Subject: [PATCH 06/15] fix: compile errors --- .../Models/MessageHandler/TypedEventHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs index d89a26b..35c818f 100644 --- a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs +++ b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs @@ -277,8 +277,8 @@ protected virtual void Dispose(bool disposing) if (disposing) { - var context = _ref.Context; - _instances.Remove(context); + var context = Ref.Context; + Instances.Remove(context); } Disposed = true; From c56634d3814117aa8c38259bb041ad38e7b230c6 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Wed, 29 Nov 2023 12:02:33 -0500 Subject: [PATCH 07/15] chore: rename listener functions to match naming --- .../Controllers/Relayer.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 829d15b..3085407 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -199,20 +199,20 @@ protected virtual Task BuildConnection(string url) protected virtual void RegisterProviderEventListeners() { - Provider.RawMessageReceived += ProviderOnRawMessageReceived; - Provider.Connected += ProviderOnConnected; - Provider.Disconnected += ProviderOnDisconnected; - Provider.ErrorReceived += ProviderOnErrorReceived; + Provider.RawMessageReceived += OnProviderRawMessageReceived; + Provider.Connected += OnProviderConnected; + Provider.Disconnected += OnProviderDisconnected; + Provider.ErrorReceived += OnProviderErrorReceived; } - private void ProviderOnErrorReceived(object sender, Exception e) + private void OnProviderErrorReceived(object sender, Exception e) { if (Disposed) return; this.OnErrored?.Invoke(this, e); } - private async void ProviderOnDisconnected(object sender, EventArgs e) + private async void OnProviderDisconnected(object sender, EventArgs e) { if (Disposed) return; @@ -227,14 +227,14 @@ private async void ProviderOnDisconnected(object sender, EventArgs e) await RestartTransport(); } - private void ProviderOnConnected(object sender, IJsonRpcConnection e) + private void OnProviderConnected(object sender, IJsonRpcConnection e) { if (Disposed) return; this.OnConnected?.Invoke(sender, EventArgs.Empty); } - private void ProviderOnRawMessageReceived(object sender, string e) + private void OnProviderRawMessageReceived(object sender, string e) { if (Disposed) return; @@ -526,10 +526,10 @@ protected virtual void Dispose(bool disposing) Messages?.Dispose(); // Un-listen to events - Provider.Connected -= ProviderOnConnected; - Provider.Disconnected -= ProviderOnDisconnected; - Provider.RawMessageReceived -= ProviderOnRawMessageReceived; - Provider.ErrorReceived -= ProviderOnErrorReceived; + Provider.Connected -= OnProviderConnected; + Provider.Disconnected -= OnProviderDisconnected; + Provider.RawMessageReceived -= OnProviderRawMessageReceived; + Provider.ErrorReceived -= OnProviderErrorReceived; Provider?.Dispose(); } From 27cd591edd1bce59a49be8dc3c25f9ecea6793d4 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Sat, 9 Dec 2023 18:13:29 -0500 Subject: [PATCH 08/15] fix: dispose loop --- WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs index 830db5d..df86a33 100644 --- a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs +++ b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs @@ -115,7 +115,7 @@ protected override void Dispose(bool disposing) } } - base.Dispose(); + base.Dispose(disposing); } } } From 55b210c804c899d708ab6e654b2b3ccdc5e9d4f1 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Wed, 17 Jan 2024 15:10:08 -0500 Subject: [PATCH 09/15] feat: AddressProvider uses Namespaces instead of RequiredNamespaces also introduce SortedDictionary --- .../Controllers/AddressProvider.cs | 18 ++-- WalletConnectSharp.Sign/Engine.cs | 4 +- WalletConnectSharp.Sign/Models/Namespaces.cs | 32 +------ .../Models/RequiredNamespaces.cs | 61 +----------- .../Models/SortedDictionary.cs | 92 +++++++++++++++++++ 5 files changed, 108 insertions(+), 99 deletions(-) create mode 100644 WalletConnectSharp.Sign/Models/SortedDictionary.cs diff --git a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs index 8a346a0..6f24d16 100644 --- a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs +++ b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs @@ -21,7 +21,7 @@ public bool HasDefaultSession { get { - return !string.IsNullOrWhiteSpace(DefaultSession.Topic) && DefaultSession.RequiredNamespaces != null; + return !string.IsNullOrWhiteSpace(DefaultSession.Topic) && DefaultSession.Namespaces != null; } } @@ -147,36 +147,36 @@ private async void UpdateDefaultChainAndNamespace() if (HasDefaultSession) { var currentDefault = DefaultNamespace; - if (currentDefault != null && DefaultSession.RequiredNamespaces.ContainsKey(currentDefault)) + if (currentDefault != null && DefaultSession.Namespaces.ContainsKey(currentDefault)) { // DefaultNamespace is still valid var currentChain = DefaultChain; if (currentChain == null || - DefaultSession.RequiredNamespaces[DefaultNamespace].Chains.Contains(currentChain)) + DefaultSession.Namespaces[DefaultNamespace].Chains.Contains(currentChain)) { // DefaultChain is still valid await SaveDefaults(); return; } - DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; await SaveDefaults(); return; } - // DefaultNamespace is null or not found in RequiredNamespaces, update it - DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault(); + // DefaultNamespace is null or not found in current available spaces, update it + DefaultNamespace = DefaultSession.Namespaces.OrderedKeys.FirstOrDefault(); if (DefaultNamespace != null) { - DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; } else { // TODO The Keys property is unordered! Maybe this needs to be updated - DefaultNamespace = DefaultSession.RequiredNamespaces.Keys.FirstOrDefault(); + DefaultNamespace = DefaultSession.Namespaces.Keys.FirstOrDefault(); if (DefaultNamespace != null) { - DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; } } diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index fa297ef..c49ca07 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -688,8 +688,8 @@ public async Task Request(string topic, T data, string chainId = null if (string.IsNullOrWhiteSpace(chainId)) { var sessionData = Client.Session.Get(topic); - var defaultNamespace = Client.AddressProvider.DefaultNamespace ?? sessionData.RequiredNamespaces.OrderedKeys[0]; - defaultChainId = Client.AddressProvider.DefaultChain ?? sessionData.RequiredNamespaces[defaultNamespace].Chains[0]; + var defaultNamespace = Client.AddressProvider.DefaultNamespace ?? sessionData.Namespaces.OrderedKeys[0]; + defaultChainId = Client.AddressProvider.DefaultChain ?? sessionData.Namespaces[defaultNamespace].Chains[0]; } else { diff --git a/WalletConnectSharp.Sign/Models/Namespaces.cs b/WalletConnectSharp.Sign/Models/Namespaces.cs index a154d64..ba9e348 100644 --- a/WalletConnectSharp.Sign/Models/Namespaces.cs +++ b/WalletConnectSharp.Sign/Models/Namespaces.cs @@ -10,7 +10,7 @@ namespace WalletConnectSharp.Sign.Models /// namespace: [-a-z0-9]{3,8} /// reference: [-_a-zA-Z0-9]{1,32} /// - public class Namespaces : Dictionary, IEquatable + public class Namespaces : SortedDictionary { public Namespaces() : base() { } @@ -29,35 +29,7 @@ public Namespaces(Dictionary proposedNamespaces) WithProposedNamespaces(proposedNamespaces); } - public bool Equals(Namespaces other) - { - return new DictionaryComparer(Namespace.NamespaceComparer).Equals(this, other); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return Equals((Namespaces)obj); - } - - public override int GetHashCode() - { - throw new NotImplementedException(); - } + public override IEqualityComparer Comparer => Namespace.NamespaceComparer; public Namespaces WithNamespace(string chainNamespace, Namespace nm) { diff --git a/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs b/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs index bbd78d4..ffac548 100644 --- a/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs +++ b/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs @@ -10,69 +10,14 @@ namespace WalletConnectSharp.Sign.Models /// namespace: [-a-z0-9]{3,8} /// reference: [-_a-zA-Z0-9]{1,32} /// - public class RequiredNamespaces : Dictionary, IEquatable + public class RequiredNamespaces : SortedDictionary { - private List _orderedKeys = new(); - - public List OrderedKeys => _orderedKeys; - - public new void Add(string key, ProposedNamespace value) - { - base.Add(key, value); - _orderedKeys.Add(key); - } - - public new void Remove(string key) - { - base.Remove(key); - _orderedKeys.Remove(key); - } - - public bool Equals(RequiredNamespaces other) - { - return new DictionaryComparer(ProposedNamespace.RequiredNamespaceComparer).Equals(this, other); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return Equals((RequiredNamespaces)obj); - } - - public override int GetHashCode() - { - throw new NotImplementedException(); - } - - - public bool Equals(RequiredNamespaces x, RequiredNamespaces y) - { - return new DictionaryComparer(ProposedNamespace.RequiredNamespaceComparer).Equals(x, y); - } - - public int GetHashCode(RequiredNamespaces obj) - { - throw new NotImplementedException(); - } - public RequiredNamespaces WithProposedNamespace(string chainNamespace, ProposedNamespace proposedNamespace) { Add(chainNamespace, proposedNamespace); return this; } + + public override IEqualityComparer Comparer => ProposedNamespace.RequiredNamespaceComparer; } } diff --git a/WalletConnectSharp.Sign/Models/SortedDictionary.cs b/WalletConnectSharp.Sign/Models/SortedDictionary.cs new file mode 100644 index 0000000..dc99150 --- /dev/null +++ b/WalletConnectSharp.Sign/Models/SortedDictionary.cs @@ -0,0 +1,92 @@ +using WalletConnectSharp.Common.Utils; + +namespace WalletConnectSharp.Sign.Models; + +public abstract class SortedDictionary : Dictionary, IEquatable> +{ + public abstract IEqualityComparer Comparer { get; } + + protected SortedDictionary() : base() + { + } + + protected SortedDictionary(IDictionary dictionary) : base(dictionary) + { + } + + private List _orderedKeys = new(); + + public List OrderedKeys => _orderedKeys; + + public TValue this[TKey key] + { + get + { + return base[key]; + } + set + { + if (base[key] == null && value != null) + { + _orderedKeys.Add(key); + } + else if (base[key] != null && value == null) + { + _orderedKeys.Remove(key); + } + } + } + + public new void Add(TKey key, TValue value) + { + base.Add(key, value); + _orderedKeys.Add(key); + } + + public new void Remove(TKey key) + { + base.Remove(key); + _orderedKeys.Remove(key); + } + + public bool Equals(SortedDictionary other) + { + return new DictionaryComparer(Comparer).Equals(this, other); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + + return Equals((SortedDictionary)obj); + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + + public bool Equals(SortedDictionary x, SortedDictionary y) + { + return new DictionaryComparer(Comparer).Equals(x, y); + } + + public int GetHashCode(SortedDictionary obj) + { + throw new NotImplementedException(); + } +} From 60d5fa915f19ac5c4b3c513273c3feaeb6ecfd48 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Thu, 18 Jan 2024 19:35:33 -0500 Subject: [PATCH 10/15] feat: added relay batch message fetching and fixed several lifecycle issues --- .../JsonRpcProvider.cs | 14 +++++ .../SignClientFixture.cs | 20 ++++++- .../TwoClientsFixture.cs | 2 +- .../SignTests.cs | 4 +- .../Controllers/AuthEngine.cs | 26 +++++++-- .../Interfaces/IAuthEngine.cs | 2 +- .../WalletConnectAuthClient.cs | 2 +- .../Controllers/Pairing.cs | 13 +++-- .../Controllers/Relayer.cs | 55 +++++++++++++++++++ .../Controllers/TypedMessageHandler.cs | 13 ++++- .../Interfaces/ITypedMessageHandler.cs | 3 +- .../Models/BatchFetchMessageRequest.cs | 10 ++++ .../Models/BatchFetchMessagesResponse.cs | 27 +++++++++ .../Models/DisposeHandlerToken.cs | 33 +++++++++++ .../MessageHandler/TypedEventHandler.cs | 51 +++++++++++++++-- WalletConnectSharp.Core/WalletConnectCore.cs | 2 +- .../Controllers/AddressProvider.cs | 29 ++++++++-- WalletConnectSharp.Sign/Engine.cs | 55 +++++++++++-------- .../Interfaces/IEngineAPI.cs | 3 +- WalletConnectSharp.Sign/Models/Namespace.cs | 7 ++- .../Models/ProposalStruct.cs | 6 +- .../Models/ProposedNamespace.cs | 7 ++- .../Models/SessionRequestEventHandler.cs | 28 ++++------ .../WalletConnectSignClient.cs | 5 +- 24 files changed, 342 insertions(+), 75 deletions(-) create mode 100644 WalletConnectSharp.Core/Models/BatchFetchMessageRequest.cs create mode 100644 WalletConnectSharp.Core/Models/BatchFetchMessagesResponse.cs create mode 100644 WalletConnectSharp.Core/Models/DisposeHandlerToken.cs diff --git a/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs b/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs index eeeda1b..cec4514 100644 --- a/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs +++ b/Core Modules/WalletConnectSharp.Network/JsonRpcProvider.cs @@ -253,6 +253,19 @@ protected void RegisterEventListeners() _hasRegisteredEventListeners = true; } + protected void UnregisterEventListeners() + { + if (!_hasRegisteredEventListeners) return; + + WCLogger.Log( + $"[JsonRpcProvider] Unregistering event listeners on connection object with context {_connection.ToString()} inside {Context}"); + _connection.PayloadReceived -= OnPayload; + _connection.Closed -= OnConnectionDisconnected; + _connection.ErrorReceived -= OnConnectionError; + + _hasRegisteredEventListeners = false; + } + private void OnConnectionError(object sender, Exception e) { this.ErrorReceived?.Invoke(this, e); @@ -313,6 +326,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { + UnregisterEventListeners(); _connection?.Dispose(); } diff --git a/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs b/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs index 4460965..5ef576c 100644 --- a/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs +++ b/Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs @@ -1,4 +1,5 @@ -using WalletConnectSharp.Core; +using WalletConnectSharp.Common.Logging; +using WalletConnectSharp.Core; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Storage; using WalletConnectSharp.Storage.Interfaces; @@ -55,6 +56,21 @@ public override async Task Init() ClientA = await WalletConnectSignClient.Init(OptionsA); ClientB = await WalletConnectSignClient.Init(OptionsB); } + + public override async Task DisposeAndReset() + { + await WaitForNoPendingRequests(ClientA); + await WaitForNoPendingRequests(ClientB); + + await base.DisposeAndReset(); + } - + protected async Task WaitForNoPendingRequests(WalletConnectSignClient client) + { + while (client.PendingSessionRequests.Length > 0) + { + WCLogger.Log($"Waiting for {client.PendingSessionRequests.Length} requests to finish sending"); + await Task.Delay(100); + } + } } diff --git a/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs b/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs index 7cec984..78962be 100644 --- a/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs +++ b/Tests/WalletConnectSharp.Tests.Common/TwoClientsFixture.cs @@ -20,7 +20,7 @@ public async Task WaitForClientsReady() await Task.Delay(10); } - public async Task DisposeAndReset() + public virtual async Task DisposeAndReset() { if (ClientA != null) { diff --git a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs index 1f30aad..e7f4bd8 100644 --- a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs +++ b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs @@ -534,7 +534,7 @@ await Task.WhenAll( }; TaskCompletionSource task2 = new TaskCompletionSource(); - _dapp.HandleEventMessageType(async (s, request) => + var handler = await _dapp.HandleEventMessageType(async (s, request) => { var eventData = request.Params.Event; var topic = request.Params.Topic; @@ -548,6 +548,8 @@ await Task.WhenAll( task2.Task, _wallet.EmitSessionEvent(session.Topic, sentData, TestRequiredNamespaces["eip155"].Chains[0]) ); + + handler.Dispose(); } [Fact, Trait("Category", "unit")] diff --git a/WalletConnectSharp.Auth/Controllers/AuthEngine.cs b/WalletConnectSharp.Auth/Controllers/AuthEngine.cs index e7ac368..1652a35 100644 --- a/WalletConnectSharp.Auth/Controllers/AuthEngine.cs +++ b/WalletConnectSharp.Auth/Controllers/AuthEngine.cs @@ -5,6 +5,7 @@ using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Verify; using WalletConnectSharp.Crypto.Models; using WalletConnectSharp.Network.Models; @@ -25,6 +26,9 @@ public partial class AuthEngine : IAuthEngine $"{AUTH_CLIENT_PROTOCOL}@{AUTH_CLIENT_VERSION}:{AUTH_CLIENT_CONTEXT}"; public static readonly string AUTH_CLIENT_PUBLIC_KEY_NAME = $"{AUTH_CLIENT_STORAGE_PREFIX}:PUB_KEY"; + + private DisposeHandlerToken messageHandler; + protected bool Disposed; public string Name { @@ -57,11 +61,11 @@ public AuthEngine(IAuthClient client) Client = client; } - public void Init() + public async Task Init() { if (!initialized) { - RegisterRelayerEvents(); + await RegisterRelayerEvents(); this.initialized = true; } } @@ -234,10 +238,10 @@ protected async Task SetExpiry(string topic, long expiry) this.Client.Core.Expirer.Set(topic, expiry); } - private void RegisterRelayerEvents() + private async Task RegisterRelayerEvents() { // MessageHandler will handle all topic tracking - this.Client.Core.MessageHandler.HandleMessageType(OnAuthRequest, OnAuthResponse); + this.messageHandler = await this.Client.Core.MessageHandler.HandleMessageType(OnAuthRequest, OnAuthResponse); } private async Task OnAuthResponse(string topic, JsonRpcResponse response) @@ -392,5 +396,19 @@ private void IsInitialized() public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (Disposed) return; + + if (disposing) + { + this.messageHandler.Dispose(); + } + + Disposed = true; } } diff --git a/WalletConnectSharp.Auth/Interfaces/IAuthEngine.cs b/WalletConnectSharp.Auth/Interfaces/IAuthEngine.cs index 01df8d2..968fa54 100644 --- a/WalletConnectSharp.Auth/Interfaces/IAuthEngine.cs +++ b/WalletConnectSharp.Auth/Interfaces/IAuthEngine.cs @@ -9,7 +9,7 @@ public interface IAuthEngine : IModule IDictionary PendingRequests { get; } - void Init(); + Task Init(); Task Request(RequestParams @params, string topic = null); diff --git a/WalletConnectSharp.Auth/WalletConnectAuthClient.cs b/WalletConnectSharp.Auth/WalletConnectAuthClient.cs index eda24b9..f76d0b0 100644 --- a/WalletConnectSharp.Auth/WalletConnectAuthClient.cs +++ b/WalletConnectSharp.Auth/WalletConnectAuthClient.cs @@ -85,7 +85,7 @@ private async Task Initialize() await this.AuthKeys.Init(); await this.Requests.Init(); await this.PairingTopics.Init(); - this.Engine.Init(); + await this.Engine.Init(); } private WalletConnectAuthClient(AuthOptions options) diff --git a/WalletConnectSharp.Core/Controllers/Pairing.cs b/WalletConnectSharp.Core/Controllers/Pairing.cs index 33cbe9f..f5017cf 100644 --- a/WalletConnectSharp.Core/Controllers/Pairing.cs +++ b/WalletConnectSharp.Core/Controllers/Pairing.cs @@ -6,6 +6,7 @@ using WalletConnectSharp.Common.Model.Relay; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Expirer; using WalletConnectSharp.Core.Models.Pairing; using WalletConnectSharp.Core.Models.Pairing.Methods; @@ -52,6 +53,8 @@ public string Context public event EventHandler PairingDeleted; private EventHandlerMap> PairingPingResponseEvents = new(); + private DisposeHandlerToken pairingDeleteMessageHandler; + private DisposeHandlerToken pairingPingMessageHandler; /// /// Get the module that is handling the storage of @@ -95,7 +98,7 @@ public async Task Init() { await this.Store.Init(); await Cleanup(); - RegisterTypedMessages(); + await RegisterTypedMessages(); RegisterExpirerEvents(); this._initialized = true; } @@ -106,10 +109,10 @@ private void RegisterExpirerEvents() this.Core.Expirer.Expired += ExpiredCallback; } - private void RegisterTypedMessages() + private async Task RegisterTypedMessages() { - Core.MessageHandler.HandleMessageType(OnPairingDeleteRequest, null); - Core.MessageHandler.HandleMessageType(OnPairingPingRequest, OnPairingPingResponse); + this.pairingDeleteMessageHandler = await Core.MessageHandler.HandleMessageType(OnPairingDeleteRequest, null); + this.pairingPingMessageHandler = await Core.MessageHandler.HandleMessageType(OnPairingPingRequest, OnPairingPingResponse); } /// @@ -488,6 +491,8 @@ protected virtual void Dispose(bool disposing) if (disposing) { Store?.Dispose(); + this.pairingDeleteMessageHandler.Dispose(); + this.pairingPingMessageHandler.Dispose(); } Disposed = true; diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 3085407..aa24fd1 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -3,6 +3,7 @@ using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Core.Models.Subscriber; using WalletConnectSharp.Network; @@ -123,6 +124,8 @@ public bool TransportExplicitlyClosed private bool initialized; private bool reconnecting = false; protected bool Disposed; + private DateTime lastSyncTime; + private bool isSyncing; /// /// Create a new Relayer with the given RelayerOptions. @@ -203,6 +206,56 @@ protected virtual void RegisterProviderEventListeners() Provider.Connected += OnProviderConnected; Provider.Disconnected += OnProviderDisconnected; Provider.ErrorReceived += OnProviderErrorReceived; + + Core.HeartBeat.OnPulse += HeartBeatOnOnPulse; + } + + private async void HeartBeatOnOnPulse(object sender, EventArgs e) + { + var topics = Subscriber.Topics; + if (topics.Length > 0 && !isSyncing && DateTime.Now - lastSyncTime >= TimeSpan.FromSeconds(15)) + { + isSyncing = true; + bool hasMore = false; + do + { + WCLogger.Log($"Asking Relay server for messages inside {Context}"); + var request = new BatchFetchMessageRequest() + { + Topics = topics + }; + + var response = + await Request(new RequestArguments() + { + Method = "irn_batchFetchMessages", + Params = request + }); + + if (response?.Messages == null) + { + WCLogger.Log("Got no messages from Relayer"); + break; + } + + WCLogger.Log($"Got {response.Messages.Length} messages from Relay server"); + foreach (var message in response.Messages) + { + var messageEvent = new MessageEvent() + { + Message = message.Message, Topic = message.Topic + }; + + await OnMessageEvent(messageEvent); + } + + hasMore = response.HasMore; + WCLogger.Log($"Are there more messages? {hasMore}"); + } while (hasMore); + + isSyncing = false; + lastSyncTime = DateTime.Now; + } } private void OnProviderErrorReceived(object sender, Exception e) @@ -530,6 +583,8 @@ protected virtual void Dispose(bool disposing) Provider.Disconnected -= OnProviderDisconnected; Provider.RawMessageReceived -= OnProviderRawMessageReceived; Provider.ErrorReceived -= OnProviderErrorReceived; + + Core.HeartBeat.OnPulse -= HeartBeatOnOnPulse; Provider?.Dispose(); } diff --git a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs index 57cfaf2..94af826 100644 --- a/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Controllers/TypedMessageHandler.cs @@ -4,6 +4,7 @@ using WalletConnectSharp.Common.Model.Errors; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Crypto.Models; using WalletConnectSharp.Network.Models; @@ -88,7 +89,7 @@ async void RelayerMessageCallback(object sender, MessageEvent e) /// The callback function to invoke when a response is received with the given response type /// The request type to trigger the requestCallback for /// The response type to trigger the responseCallback for - public async void HandleMessageType(Func, Task> requestCallback, + public async Task HandleMessageType(Func, Task> requestCallback, Func, Task> responseCallback) { var method = RpcMethodAttribute.MethodForType(); @@ -178,6 +179,14 @@ async void InspectResponseRaw(object sender, DecodedMessageEvent e) // Handle response_raw in this context // This will allow us to examine response_raw in every typed context registered this.RawMessage += InspectResponseRaw; + + return new DisposeHandlerToken(() => + { + this.RawMessage -= InspectResponseRaw; + + messageEventHandlerMap[$"request_{method}"] -= RequestCallback; + messageEventHandlerMap[$"response_{method}"] -= ResponseCallback; + }); } /// @@ -302,8 +311,6 @@ public async Task SendRequest(string topic, T parameters, long? exp var payload = new JsonRpcRequest(method, parameters); - WCLogger.Log(JsonConvert.SerializeObject(payload)); - var message = await this.Core.Crypto.Encode(topic, payload, options); var opts = RpcRequestOptionsFromType(); diff --git a/WalletConnectSharp.Core/Interfaces/ITypedMessageHandler.cs b/WalletConnectSharp.Core/Interfaces/ITypedMessageHandler.cs index b73400a..4b4e51c 100644 --- a/WalletConnectSharp.Core/Interfaces/ITypedMessageHandler.cs +++ b/WalletConnectSharp.Core/Interfaces/ITypedMessageHandler.cs @@ -1,4 +1,5 @@ using WalletConnectSharp.Common; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Crypto.Models; using WalletConnectSharp.Network.Models; @@ -31,7 +32,7 @@ public interface ITypedMessageHandler : IModule /// The callback function to invoke when a response is received with the given response type /// The request type to trigger the requestCallback for /// The response type to trigger the responseCallback for - void HandleMessageType(Func, Task> requestCallback, + Task HandleMessageType(Func, Task> requestCallback, Func, Task> responseCallback); /// diff --git a/WalletConnectSharp.Core/Models/BatchFetchMessageRequest.cs b/WalletConnectSharp.Core/Models/BatchFetchMessageRequest.cs new file mode 100644 index 0000000..e404b8f --- /dev/null +++ b/WalletConnectSharp.Core/Models/BatchFetchMessageRequest.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; +using WalletConnectSharp.Network.Models; + +namespace WalletConnectSharp.Core.Models; + +public class BatchFetchMessageRequest +{ + [JsonProperty("topics")] + public string[] Topics; +} diff --git a/WalletConnectSharp.Core/Models/BatchFetchMessagesResponse.cs b/WalletConnectSharp.Core/Models/BatchFetchMessagesResponse.cs new file mode 100644 index 0000000..e64bc19 --- /dev/null +++ b/WalletConnectSharp.Core/Models/BatchFetchMessagesResponse.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; + +namespace WalletConnectSharp.Core.Models; + +public class BatchFetchMessagesResponse +{ + public class ReceivedMessage + { + [JsonProperty("topic")] + public string Topic; + + [JsonProperty("message")] + public string Message; + + [JsonProperty("publishedAt")] + public long PublishedAt; + + [JsonProperty("tag")] + public long Tag; + } + + [JsonProperty("messages")] + public ReceivedMessage[] Messages; + + [JsonProperty("hasMore")] + public bool HasMore; +} diff --git a/WalletConnectSharp.Core/Models/DisposeHandlerToken.cs b/WalletConnectSharp.Core/Models/DisposeHandlerToken.cs new file mode 100644 index 0000000..5671cec --- /dev/null +++ b/WalletConnectSharp.Core/Models/DisposeHandlerToken.cs @@ -0,0 +1,33 @@ +namespace WalletConnectSharp.Core.Models; + +public class DisposeHandlerToken : IDisposable +{ + private readonly Action _onDispose; + + public DisposeHandlerToken(Action onDispose) + { + if (onDispose == null) + throw new ArgumentException("onDispose must be non-null"); + this._onDispose = onDispose; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (Disposed) return; + + if (disposing) + { + this._onDispose(); + } + + Disposed = true; + } + + protected bool Disposed; +} diff --git a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs index 35c818f..c9da69c 100644 --- a/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs +++ b/WalletConnectSharp.Core/Models/MessageHandler/TypedEventHandler.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json; +using WalletConnectSharp.Common.Logging; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Verify; using WalletConnectSharp.Network.Models; @@ -19,6 +21,7 @@ public class TypedEventHandler : IDisposable { protected static readonly Dictionary> Instances = new(); protected readonly ICore Ref; + protected List _disposeActions = new List(); protected Func, bool> RequestPredicate; protected Func, bool> ResponsePredicate; @@ -66,6 +69,7 @@ public delegate Task private event ResponseMethod _onResponse; private object _eventLock = new object(); private int _activeCount; + protected DisposeHandlerToken messageHandler; /// /// The event handler that triggers when a new request of type @@ -188,24 +192,33 @@ protected virtual TypedEventHandler BuildNew(ICore _ref, Func, bool> requestPredicate, Func, bool> responsePredicate) { - return new TypedEventHandler(_ref) + var wrappedRef = new TypedEventHandler(_ref) { RequestPredicate = requestPredicate, ResponsePredicate = responsePredicate }; + + _disposeActions.Add(wrappedRef.Dispose); + + return wrappedRef; } - protected virtual void Setup() + protected virtual async void Setup() { - Ref.MessageHandler.HandleMessageType(RequestCallback, ResponseCallback); + this.messageHandler = await Ref.MessageHandler.HandleMessageType(RequestCallback, ResponseCallback); } - protected virtual void Teardown() + protected virtual async void Teardown() { - // TODO Unsubscribe from HandleMessageType from above + if (this.messageHandler != null) + { + this.messageHandler.Dispose(); + this.messageHandler = null; + } } protected virtual Task ResponseCallback(string arg1, JsonRpcResponse arg2) { + WCLogger.Log($"Got generic response for type {typeof(TR)}"); var rea = new ResponseEventArgs(arg2, arg1); return ResponsePredicate != null && !ResponsePredicate(rea) ? Task.CompletedTask : _onResponse != null ? _onResponse(rea) : Task.CompletedTask; @@ -229,7 +242,23 @@ protected virtual async Task RequestCallback(string arg1, JsonRpcRequest arg2 if (RequestPredicate != null && !RequestPredicate(rea)) return; if (_onRequest == null) return; + var isDisposed = ((WalletConnectCore)Ref).Disposed; + + if (isDisposed) + { + WCLogger.Log($"Too late to process request {typeof(T)} in topic {arg1}, the WalletConnect instance {Ref.Context} was disposed before we could"); + return; + } + await _onRequest(rea); + + var nextIsDisposed = ((WalletConnectCore)Ref).Disposed; + + if (nextIsDisposed) + { + WCLogger.Log($"Too late to send a result for request {typeof(T)} in topic {arg1}, the WalletConnect instance {Ref.Context} was disposed before we could"); + return; + } if (rea.Error != null) { @@ -278,7 +307,17 @@ protected virtual void Dispose(bool disposing) if (disposing) { var context = Ref.Context; - Instances.Remove(context); + foreach (var action in _disposeActions) + { + action(); + } + + _disposeActions.Clear(); + + if (Instances.ContainsKey(context)) + Instances.Remove(context); + + Teardown(); } Disposed = true; diff --git a/WalletConnectSharp.Core/WalletConnectCore.cs b/WalletConnectSharp.Core/WalletConnectCore.cs index e3d06af..4e3a11a 100644 --- a/WalletConnectSharp.Core/WalletConnectCore.cs +++ b/WalletConnectSharp.Core/WalletConnectCore.cs @@ -112,7 +112,7 @@ public string Context public CoreOptions Options { get; } - protected bool Disposed; + public bool Disposed; /// /// Create a new Core with the given options. diff --git a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs index 6f24d16..20cce5b 100644 --- a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs +++ b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs @@ -168,16 +168,37 @@ private async void UpdateDefaultChainAndNamespace() DefaultNamespace = DefaultSession.Namespaces.OrderedKeys.FirstOrDefault(); if (DefaultNamespace != null) { - DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; + if (DefaultSession.Namespaces.ContainsKey(DefaultNamespace) && DefaultSession.Namespaces[DefaultNamespace].Chains != null) + { + DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; + } + else if (DefaultSession.RequiredNamespaces.ContainsKey(DefaultNamespace) && DefaultSession.RequiredNamespaces[DefaultNamespace].Chains != null) + { + // We don't know what chain to use? Let's use the required one as a fallback + DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + } } else { - // TODO The Keys property is unordered! Maybe this needs to be updated - DefaultNamespace = DefaultSession.Namespaces.Keys.FirstOrDefault(); - if (DefaultNamespace != null) + DefaultNamespace = DefaultSession.Namespaces.OrderedKeys.FirstOrDefault(); + if (DefaultNamespace != null && DefaultSession.Namespaces[DefaultNamespace].Chains != null) { DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; } + else + { + // We don't know what chain to use? Let's use the required one as a fallback + DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault(); + if (DefaultNamespace != null && + DefaultSession.RequiredNamespaces[DefaultNamespace].Chains != null) + { + DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; + } + else + { + WCLogger.LogError("Could not figure out default chain to use"); + } + } } } diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index c49ca07..bf27b4e 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -7,6 +7,7 @@ using WalletConnectSharp.Common.Model.Relay; using WalletConnectSharp.Common.Utils; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Pairing; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Core.Models.Verify; @@ -43,6 +44,7 @@ public partial class Engine : IEnginePrivate, IEngine, IModule private ITypedMessageHandler MessageHandler => Client.Core.MessageHandler; private EventHandlerMap> sessionEventsHandlerMap = new(); + private List messageDisposeHandlers; /// /// The name of this Engine module @@ -85,7 +87,7 @@ public async Task Init() SetupEvents(); await PrivateThis.Cleanup(); - this.RegisterRelayerEvents(); + await this.RegisterRelayerEvents(); this.RegisterExpirerEvents(); this._initialized = true; } @@ -108,20 +110,33 @@ private void RegisterExpirerEvents() this.Client.Core.Expirer.Expired += ExpiredCallback; } - private void RegisterRelayerEvents() + private async Task RegisterRelayerEvents() { + this.messageDisposeHandlers = new List(); + // Register all Request Types - MessageHandler.HandleMessageType( - PrivateThis.OnSessionProposeRequest, PrivateThis.OnSessionProposeResponse); - MessageHandler.HandleMessageType(PrivateThis.OnSessionSettleRequest, - PrivateThis.OnSessionSettleResponse); - MessageHandler.HandleMessageType(PrivateThis.OnSessionUpdateRequest, - PrivateThis.OnSessionUpdateResponse); - MessageHandler.HandleMessageType(PrivateThis.OnSessionExtendRequest, - PrivateThis.OnSessionExtendResponse); - MessageHandler.HandleMessageType(PrivateThis.OnSessionDeleteRequest, null); - MessageHandler.HandleMessageType(PrivateThis.OnSessionPingRequest, - PrivateThis.OnSessionPingResponse); + this.messageDisposeHandlers.Add( + await MessageHandler.HandleMessageType( + PrivateThis.OnSessionProposeRequest, PrivateThis.OnSessionProposeResponse)); + + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( + PrivateThis.OnSessionSettleRequest, + PrivateThis.OnSessionSettleResponse)); + + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( + PrivateThis.OnSessionUpdateRequest, + PrivateThis.OnSessionUpdateResponse)); + + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( + PrivateThis.OnSessionExtendRequest, + PrivateThis.OnSessionExtendResponse)); + + this.messageDisposeHandlers.Add( + await MessageHandler.HandleMessageType(PrivateThis.OnSessionDeleteRequest, null)); + + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( + PrivateThis.OnSessionPingRequest, + PrivateThis.OnSessionPingResponse)); } /// @@ -240,11 +255,11 @@ public TypedEventHandler SessionRequestEvents() /// The callback function to invoke when a response is received with the given response type /// The request type to trigger the requestCallback for. Will be wrapped in /// The response type to trigger the responseCallback for - public void HandleSessionRequestMessageType( + public Task HandleSessionRequestMessageType( Func>, Task> requestCallback, Func, Task> responseCallback) { - Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback); + return Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback); } /// @@ -254,10 +269,10 @@ public void HandleSessionRequestMessageType( /// The callback function to invoke when a request is received with the given request type /// The callback function to invoke when a response is received with the given response type /// The request type to trigger the requestCallback for. Will be wrapped in - public void HandleEventMessageType(Func>, Task> requestCallback, + public Task HandleEventMessageType(Func>, Task> requestCallback, Func, Task> responseCallback) { - Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback); + return Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback); } public Task UpdateSession(Namespaces namespaces) @@ -372,8 +387,6 @@ public async Task Connect(ConnectOptions options) var pairing = this.Client.Core.Pairing.Store.Get(topic); if (pairing.Active != null) active = pairing.Active.Value; - - WCLogger.Log($"Loaded pairing for {topic}"); } if (string.IsNullOrEmpty(topic) || !active) @@ -381,8 +394,6 @@ public async Task Connect(ConnectOptions options) var CreatePairing = await this.Client.Core.Pairing.Create(); topic = CreatePairing.Topic; uri = CreatePairing.Uri; - - WCLogger.Log($"Created pairing for new topic: {topic}"); } var publicKey = await this.Client.Core.Crypto.GenerateKeyPair(); @@ -397,8 +408,6 @@ public async Task Connect(ConnectOptions options) SessionProperties = sessionProperties, }; - WCLogger.Log($"Created public key pair"); - TaskCompletionSource approvalTask = new TaskCompletionSource(); this.SessionConnected += async (sender, session) => { diff --git a/WalletConnectSharp.Sign/Interfaces/IEngineAPI.cs b/WalletConnectSharp.Sign/Interfaces/IEngineAPI.cs index fdb63f0..9fbd1c2 100644 --- a/WalletConnectSharp.Sign/Interfaces/IEngineAPI.cs +++ b/WalletConnectSharp.Sign/Interfaces/IEngineAPI.cs @@ -1,3 +1,4 @@ +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Pairing; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Network.Models; @@ -258,7 +259,7 @@ public interface IEngineAPI /// All sessions that have a namespace that match the given SessionStruct[] Find(RequiredNamespaces requiredNamespaces); - void HandleEventMessageType(Func>, Task> requestCallback, + Task HandleEventMessageType(Func>, Task> requestCallback, Func, Task> responseCallback); /// diff --git a/WalletConnectSharp.Sign/Models/Namespace.cs b/WalletConnectSharp.Sign/Models/Namespace.cs index 1e1a345..1dbd6e0 100644 --- a/WalletConnectSharp.Sign/Models/Namespace.cs +++ b/WalletConnectSharp.Sign/Models/Namespace.cs @@ -63,10 +63,15 @@ public Namespace WithAccount(string account) Accounts = Accounts.Append(account).ToArray(); return this; } + + protected bool ArrayEquals(string[] a, string[] b) + { + return a.Length == b.Length && a.All(b.Contains) && b.All(a.Contains); + } protected bool Equals(Namespace other) { - return Equals(Accounts, other.Accounts) && Equals(Methods, other.Methods) && Equals(Events, other.Events); + return ArrayEquals(Accounts, other.Accounts) && ArrayEquals(Methods, other.Methods) && ArrayEquals(Events, other.Events); } public override bool Equals(object obj) diff --git a/WalletConnectSharp.Sign/Models/ProposalStruct.cs b/WalletConnectSharp.Sign/Models/ProposalStruct.cs index 43d129e..714228c 100644 --- a/WalletConnectSharp.Sign/Models/ProposalStruct.cs +++ b/WalletConnectSharp.Sign/Models/ProposalStruct.cs @@ -121,7 +121,8 @@ public ApproveParams ApproveProposal(string[] approvedAccounts, ProtocolOptions { Accounts = allAccounts, Events = rn.Events, - Methods = rn.Methods + Methods = rn.Methods, + Chains = rn.Chains, }); } if (OptionalNamespaces != null) @@ -135,7 +136,8 @@ public ApproveParams ApproveProposal(string[] approvedAccounts, ProtocolOptions { Accounts = allAccounts, Events = rn.Events, - Methods = rn.Methods + Methods = rn.Methods, + Chains = rn.Chains, }); } diff --git a/WalletConnectSharp.Sign/Models/ProposedNamespace.cs b/WalletConnectSharp.Sign/Models/ProposedNamespace.cs index dd1a0f4..f2611db 100644 --- a/WalletConnectSharp.Sign/Models/ProposedNamespace.cs +++ b/WalletConnectSharp.Sign/Models/ProposedNamespace.cs @@ -75,9 +75,14 @@ public Namespace WithAccount(string account) return new Namespace(this).WithAccount(account); } + protected bool ArrayEquals(string[] a, string[] b) + { + return a.Length == b.Length && a.All(b.Contains) && b.All(a.Contains); + } + protected bool Equals(ProposedNamespace other) { - return Equals(Chains, other.Chains) && Equals(Methods, other.Methods) && Equals(Events, other.Events); + return ArrayEquals(Chains, other.Chains) && ArrayEquals(Methods, other.Methods) && ArrayEquals(Events, other.Events); } public override bool Equals(object obj) diff --git a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs index df86a33..91e1ec3 100644 --- a/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs +++ b/WalletConnectSharp.Sign/Models/SessionRequestEventHandler.cs @@ -1,4 +1,6 @@ -using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Common.Logging; +using WalletConnectSharp.Common.Model.Errors; +using WalletConnectSharp.Core.Interfaces; using WalletConnectSharp.Network.Models; using WalletConnectSharp.Sign.Interfaces; using WalletConnectSharp.Sign.Models.Engine.Methods; @@ -14,7 +16,6 @@ namespace WalletConnectSharp.Sign.Models public class SessionRequestEventHandler : TypedEventHandler { private readonly IEnginePrivate _enginePrivate; - private List _disposeActions = new List(); /// /// Get a singleton instance of this class for the given context. The context @@ -64,11 +65,17 @@ protected override void Setup() wrappedRef.OnRequest += WrappedRefOnOnRequest; wrappedRef.OnResponse += WrappedRefOnOnResponse; - _disposeActions.Add(wrappedRef.Dispose); - } + _disposeActions.Add(() => + { + wrappedRef.OnRequest -= WrappedRefOnOnRequest; + wrappedRef.OnResponse -= WrappedRefOnOnResponse; + wrappedRef.Dispose(); + }); + } private Task WrappedRefOnOnResponse(ResponseEventArgs e) { + WCLogger.Log($"Got response for type {typeof(TR)}"); return base.ResponseCallback(e.Topic, e.Response); } @@ -103,19 +110,8 @@ await _enginePrivate.SetPendingSessionRequest(new PendingRequestStruct() }); await base.RequestCallback(e.Topic, sessionRequest); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - foreach (var action in _disposeActions) - { - action(); - } - } - base.Dispose(disposing); + await _enginePrivate.DeletePendingSessionRequest(e.Request.Id, Error.FromErrorType(ErrorType.GENERIC)); } } } diff --git a/WalletConnectSharp.Sign/WalletConnectSignClient.cs b/WalletConnectSharp.Sign/WalletConnectSignClient.cs index 659bc9f..8665fd2 100644 --- a/WalletConnectSharp.Sign/WalletConnectSignClient.cs +++ b/WalletConnectSharp.Sign/WalletConnectSignClient.cs @@ -2,6 +2,7 @@ using WalletConnectSharp.Core; using WalletConnectSharp.Core.Controllers; using WalletConnectSharp.Core.Interfaces; +using WalletConnectSharp.Core.Models; using WalletConnectSharp.Core.Models.Pairing; using WalletConnectSharp.Core.Models.Relay; using WalletConnectSharp.Crypto; @@ -427,10 +428,10 @@ public SessionStruct[] Find(RequiredNamespaces requiredNamespaces) return Engine.Find(requiredNamespaces); } - public void HandleEventMessageType(Func>, Task> requestCallback, + public Task HandleEventMessageType(Func>, Task> requestCallback, Func, Task> responseCallback) { - this.Engine.HandleEventMessageType(requestCallback, responseCallback); + return this.Engine.HandleEventMessageType(requestCallback, responseCallback); } public Task UpdateSession(Namespaces namespaces) From 77c03d3703cfd445fb31257fecfcd0949b04a8f4 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Thu, 18 Jan 2024 20:15:37 -0500 Subject: [PATCH 11/15] test: Ensure namespaces include Chains array --- .../SignTests.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs index e7f4bd8..92a09f4 100644 --- a/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs +++ b/Tests/WalletConnectSharp.Web3Wallet.Tests/SignTests.cs @@ -82,7 +82,8 @@ public class ChainChangedEvent "eth_signTypedData" }, Accounts = TestAccounts, - Events = TestEvents + Events = TestEvents, + Chains = new[] { TestEthereumChain }, } } }; @@ -91,7 +92,8 @@ public class ChainChangedEvent { Methods = new[] { "eth_signTransaction", }, Accounts = new[] { TestAccounts[0] }, - Events = new[] { TestEvents[0] } + Events = new[] { TestEvents[0] }, + Chains = new[] { TestEthereumChain }, }; private static readonly Namespaces TestNamespaces = new Namespaces() @@ -337,7 +339,8 @@ public async Task TestRespondToSessionRequest() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new[] { TestEthereumChain }, } } }); @@ -415,7 +418,8 @@ public async Task TestWalletDisconnectFromSession() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new [] { TestEthereumChain } } } }); @@ -462,7 +466,8 @@ public async Task TestDappDisconnectFromSession() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new [] { TestEthereumChain } } } }); @@ -509,7 +514,8 @@ public async Task TestEmitSessionEvent() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new []{ $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new [] { TestEthereumChain } } } }); @@ -571,7 +577,8 @@ public async Task TestGetActiveSessions() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new[] { $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new[] { $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new [] { TestEthereumChain } } } }); @@ -630,7 +637,8 @@ public async Task TestGetPendingSessionRequests() { Methods = TestNamespace.Methods, Events = TestNamespace.Events, - Accounts = new[] { $"{TestEthereumChain}:{WalletAddress}" } + Accounts = new[] { $"{TestEthereumChain}:{WalletAddress}" }, + Chains = new [] { TestEthereumChain } } } }); From dac3bfb988575c08ba6f39a5b9751f77fd94903b Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Thu, 18 Jan 2024 23:55:13 -0500 Subject: [PATCH 12/15] chore: remove debug logging --- .../Controllers/Relayer.cs | 67 +++++++++---------- .../Models/Relay/RelayerOptions.cs | 7 ++ 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index aa24fd1..59ec6bc 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -149,8 +149,11 @@ public Relayer(RelayerOptions opts) ConnectionTimeout = opts.ConnectionTimeout; RelayUrlBuilder = opts.RelayUrlBuilder; + MessageFetchInterval = opts.MessageFetchInterval; } + public TimeSpan? MessageFetchInterval { get; set; } + /// /// Initialize this Relayer module. This will initialize all sub-modules /// and connect the backing IJsonRpcProvider. @@ -212,50 +215,46 @@ protected virtual void RegisterProviderEventListeners() private async void HeartBeatOnOnPulse(object sender, EventArgs e) { + var interval = this.MessageFetchInterval; + if (interval == null) return; + var topics = Subscriber.Topics; - if (topics.Length > 0 && !isSyncing && DateTime.Now - lastSyncTime >= TimeSpan.FromSeconds(15)) + if (topics.Length <= 0 || isSyncing || !(DateTime.Now - lastSyncTime >= interval)) return; + + isSyncing = true; + bool hasMore; + do { - isSyncing = true; - bool hasMore = false; - do + var request = new BatchFetchMessageRequest() { - WCLogger.Log($"Asking Relay server for messages inside {Context}"); - var request = new BatchFetchMessageRequest() - { - Topics = topics - }; - - var response = - await Request(new RequestArguments() - { - Method = "irn_batchFetchMessages", - Params = request - }); + Topics = topics + }; - if (response?.Messages == null) + var response = + await Request(new RequestArguments() { - WCLogger.Log("Got no messages from Relayer"); - break; - } + Method = "irn_batchFetchMessages", + Params = request + }); + + if (response?.Messages == null) + break; - WCLogger.Log($"Got {response.Messages.Length} messages from Relay server"); - foreach (var message in response.Messages) + foreach (var message in response.Messages) + { + var messageEvent = new MessageEvent { - var messageEvent = new MessageEvent() - { - Message = message.Message, Topic = message.Topic - }; + Message = message.Message, Topic = message.Topic + }; - await OnMessageEvent(messageEvent); - } + await OnMessageEvent(messageEvent); + } - hasMore = response.HasMore; - WCLogger.Log($"Are there more messages? {hasMore}"); - } while (hasMore); + hasMore = response.HasMore; + } while (hasMore); - isSyncing = false; - lastSyncTime = DateTime.Now; - } + isSyncing = false; + lastSyncTime = DateTime.Now; } private void OnProviderErrorReceived(object sender, Exception e) diff --git a/WalletConnectSharp.Core/Models/Relay/RelayerOptions.cs b/WalletConnectSharp.Core/Models/Relay/RelayerOptions.cs index 4fbfbba..023ac1d 100644 --- a/WalletConnectSharp.Core/Models/Relay/RelayerOptions.cs +++ b/WalletConnectSharp.Core/Models/Relay/RelayerOptions.cs @@ -33,6 +33,13 @@ public class RelayerOptions /// public TimeSpan? ConnectionTimeout = TimeSpan.FromSeconds(30); + /// + /// The interval at which the Relayer should request new (unsent) messages from the Relay server. If + /// this field is null, then the Relayer will never ask for new messages, so all new messages will only + /// come directly from Relay server + /// + public TimeSpan? MessageFetchInterval = TimeSpan.FromSeconds(15); + /// /// The module to use for building the Relay RPC URL. /// From 666aac4f01fe36a5c8e12d68a2bd9e546761ab61 Mon Sep 17 00:00:00 2001 From: Julia Seward Date: Thu, 18 Jan 2024 23:57:21 -0500 Subject: [PATCH 13/15] chore: replace foreach await with task.whenall --- WalletConnectSharp.Core/Controllers/Relayer.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/WalletConnectSharp.Core/Controllers/Relayer.cs b/WalletConnectSharp.Core/Controllers/Relayer.cs index 59ec6bc..f61681a 100644 --- a/WalletConnectSharp.Core/Controllers/Relayer.cs +++ b/WalletConnectSharp.Core/Controllers/Relayer.cs @@ -239,16 +239,9 @@ await Request(new RequestA if (response?.Messages == null) break; - - foreach (var message in response.Messages) - { - var messageEvent = new MessageEvent - { - Message = message.Message, Topic = message.Topic - }; - await OnMessageEvent(messageEvent); - } + await Task.WhenAll(response.Messages.Select(message => new MessageEvent() { Message = message.Message, Topic = message.Topic }) + .Select(OnMessageEvent)); hasMore = response.HasMore; } while (hasMore); From 878b89389a80d1b711369255646ad1f944aa0733 Mon Sep 17 00:00:00 2001 From: skibitsky Date: Fri, 19 Jan 2024 23:13:26 +0900 Subject: [PATCH 14/15] Make udpated FileSystemStorage backwards compatibile --- .../FileSystemStorage.cs | 20 +++++++++++++++---- .../InMemoryStorage.cs | 9 ++++++--- WalletConnectSharp.Core/WalletConnectCore.cs | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs index 2e44f8b..975e22b 100644 --- a/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs @@ -42,6 +42,9 @@ public FileSystemStorage(string filePath = null) /// public override Task Init() { + if (Initialized) + return Task.CompletedTask; + return Task.WhenAll( Load(), base.Init() ); @@ -139,10 +142,19 @@ private async Task Load() { _semaphoreSlim.Release(); } - - // Hard fail here if the storage file is bad - Entries = JsonConvert.DeserializeObject>(json, - new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }); + + // Hard fail here if the storage file is bad, unless it's serialized as a Dictionary (for backwards compatibility) + var jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }; + try + { + Entries = JsonConvert.DeserializeObject>(json, + jsonSerializerSettings); + } + catch (JsonSerializationException) + { + var dict = JsonConvert.DeserializeObject>(json, jsonSerializerSettings); + Entries = new ConcurrentDictionary(dict); + } } protected override void Dispose(bool disposing) diff --git a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs index 77243b8..46b0f2b 100644 --- a/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs +++ b/Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs @@ -7,12 +7,15 @@ namespace WalletConnectSharp.Storage public class InMemoryStorage : IKeyValueStorage { protected ConcurrentDictionary Entries = new ConcurrentDictionary(); - private bool _initialized = false; + protected bool Initialized = false; protected bool Disposed; public virtual Task Init() { - _initialized = true; + if (Initialized) + return Task.CompletedTask; + + Initialized = true; return Task.CompletedTask; } @@ -73,7 +76,7 @@ public virtual Task Clear() protected void IsInitialized() { - if (!_initialized) + if (!Initialized) { throw WalletConnectException.FromType(ErrorType.NOT_INITIALIZED, "Storage"); } diff --git a/WalletConnectSharp.Core/WalletConnectCore.cs b/WalletConnectSharp.Core/WalletConnectCore.cs index 4e3a11a..5b5a78b 100644 --- a/WalletConnectSharp.Core/WalletConnectCore.cs +++ b/WalletConnectSharp.Core/WalletConnectCore.cs @@ -112,7 +112,7 @@ public string Context public CoreOptions Options { get; } - public bool Disposed; + public bool Disposed { get; protected set; } /// /// Create a new Core with the given options. From 2ea8d21d5363f6ab13437f5a3a6e74229c2c1dfa Mon Sep 17 00:00:00 2001 From: skibitsky Date: Sat, 20 Jan 2024 00:05:43 +0900 Subject: [PATCH 15/15] Fix namespaces not being deserialized correctly `OrderedKeys` has always been 0 when deserializing the Namespaces JSON. The issue was solved by replacing `SortedDictionary` with an implementation from `System.Collections.Generic`. --- .../Controllers/AddressProvider.cs | 22 +++-- WalletConnectSharp.Sign/Engine.cs | 22 +++-- WalletConnectSharp.Sign/Models/Namespaces.cs | 17 +--- .../Models/RequiredNamespaces.cs | 2 - .../Models/SortedDictionary.cs | 92 ------------------- 5 files changed, 29 insertions(+), 126 deletions(-) delete mode 100644 WalletConnectSharp.Sign/Models/SortedDictionary.cs diff --git a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs index 20cce5b..dc546f9 100644 --- a/WalletConnectSharp.Sign/Controllers/AddressProvider.cs +++ b/WalletConnectSharp.Sign/Controllers/AddressProvider.cs @@ -78,6 +78,7 @@ public string DefaultChain _state.ChainId = value; } } + public ISession Sessions { get; private set; } private ISignClient _client; @@ -86,7 +87,7 @@ public AddressProvider(ISignClient client) { this._client = client; this.Sessions = client.Session; - + // set the first connected session to the default one client.SessionConnected += ClientOnSessionConnected; client.SessionDeleted += ClientOnSessionDeleted; @@ -110,10 +111,10 @@ public virtual async Task LoadDefaults() { _state = new DefaultData(); } - + DefaultsLoaded?.Invoke(this, new DefaultsLoadingEventArgs(_state)); } - + private void ClientOnSessionUpdated(object sender, SessionEvent e) { if (DefaultSession.Topic == e.Topic) @@ -165,14 +166,16 @@ private async void UpdateDefaultChainAndNamespace() } // DefaultNamespace is null or not found in current available spaces, update it - DefaultNamespace = DefaultSession.Namespaces.OrderedKeys.FirstOrDefault(); + DefaultNamespace = DefaultSession.Namespaces.Keys.FirstOrDefault(); if (DefaultNamespace != null) { - if (DefaultSession.Namespaces.ContainsKey(DefaultNamespace) && DefaultSession.Namespaces[DefaultNamespace].Chains != null) + if (DefaultSession.Namespaces.ContainsKey(DefaultNamespace) && + DefaultSession.Namespaces[DefaultNamespace].Chains != null) { DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; } - else if (DefaultSession.RequiredNamespaces.ContainsKey(DefaultNamespace) && DefaultSession.RequiredNamespaces[DefaultNamespace].Chains != null) + else if (DefaultSession.RequiredNamespaces.ContainsKey(DefaultNamespace) && + DefaultSession.RequiredNamespaces[DefaultNamespace].Chains != null) { // We don't know what chain to use? Let's use the required one as a fallback DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0]; @@ -180,7 +183,7 @@ private async void UpdateDefaultChainAndNamespace() } else { - DefaultNamespace = DefaultSession.Namespaces.OrderedKeys.FirstOrDefault(); + DefaultNamespace = DefaultSession.Namespaces.Keys.FirstOrDefault(); if (DefaultNamespace != null && DefaultSession.Namespaces[DefaultNamespace].Chains != null) { DefaultChain = DefaultSession.Namespaces[DefaultNamespace].Chains[0]; @@ -188,7 +191,7 @@ private async void UpdateDefaultChainAndNamespace() else { // We don't know what chain to use? Let's use the required one as a fallback - DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault(); + DefaultNamespace = DefaultSession.RequiredNamespaces.Keys.FirstOrDefault(); if (DefaultNamespace != null && DefaultSession.RequiredNamespaces[DefaultNamespace].Chains != null) { @@ -200,7 +203,6 @@ private async void UpdateDefaultChainAndNamespace() } } } - } else { @@ -244,7 +246,7 @@ public void Dispose() _client.SessionConnected -= ClientOnSessionConnected; _client.SessionDeleted -= ClientOnSessionDeleted; _client.SessionUpdated -= ClientOnSessionUpdated; - _client.SessionApproved -= ClientOnSessionConnected; + _client.SessionApproved -= ClientOnSessionConnected; _client = null; Sessions = null; diff --git a/WalletConnectSharp.Sign/Engine.cs b/WalletConnectSharp.Sign/Engine.cs index bf27b4e..8b8d328 100644 --- a/WalletConnectSharp.Sign/Engine.cs +++ b/WalletConnectSharp.Sign/Engine.cs @@ -113,27 +113,27 @@ private void RegisterExpirerEvents() private async Task RegisterRelayerEvents() { this.messageDisposeHandlers = new List(); - + // Register all Request Types this.messageDisposeHandlers.Add( await MessageHandler.HandleMessageType( PrivateThis.OnSessionProposeRequest, PrivateThis.OnSessionProposeResponse)); - + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( PrivateThis.OnSessionSettleRequest, PrivateThis.OnSessionSettleResponse)); - + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( PrivateThis.OnSessionUpdateRequest, PrivateThis.OnSessionUpdateResponse)); - + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( PrivateThis.OnSessionExtendRequest, PrivateThis.OnSessionExtendResponse)); - + this.messageDisposeHandlers.Add( await MessageHandler.HandleMessageType(PrivateThis.OnSessionDeleteRequest, null)); - + this.messageDisposeHandlers.Add(await MessageHandler.HandleMessageType( PrivateThis.OnSessionPingRequest, PrivateThis.OnSessionPingResponse)); @@ -269,7 +269,8 @@ public Task HandleSessionRequestMessageType( /// The callback function to invoke when a request is received with the given request type /// The callback function to invoke when a response is received with the given response type /// The request type to trigger the requestCallback for. Will be wrapped in - public Task HandleEventMessageType(Func>, Task> requestCallback, + public Task HandleEventMessageType( + Func>, Task> requestCallback, Func, Task> responseCallback) { return Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback); @@ -697,8 +698,10 @@ public async Task Request(string topic, T data, string chainId = null if (string.IsNullOrWhiteSpace(chainId)) { var sessionData = Client.Session.Get(topic); - var defaultNamespace = Client.AddressProvider.DefaultNamespace ?? sessionData.Namespaces.OrderedKeys[0]; - defaultChainId = Client.AddressProvider.DefaultChain ?? sessionData.Namespaces[defaultNamespace].Chains[0]; + var defaultNamespace = Client.AddressProvider.DefaultNamespace ?? + sessionData.Namespaces.Keys.FirstOrDefault(); + defaultChainId = Client.AddressProvider.DefaultChain ?? + sessionData.Namespaces[defaultNamespace].Chains[0]; } else { @@ -886,6 +889,7 @@ protected virtual void Dispose(bool disposing) { action(); } + _disposeActions.Clear(); } diff --git a/WalletConnectSharp.Sign/Models/Namespaces.cs b/WalletConnectSharp.Sign/Models/Namespaces.cs index ba9e348..b2c6d31 100644 --- a/WalletConnectSharp.Sign/Models/Namespaces.cs +++ b/WalletConnectSharp.Sign/Models/Namespaces.cs @@ -1,6 +1,3 @@ -using System.Collections.Generic; -using WalletConnectSharp.Common.Utils; - namespace WalletConnectSharp.Sign.Models { /// @@ -16,21 +13,18 @@ public Namespaces() : base() { } public Namespaces(Namespaces namespaces) : base(namespaces) { - } public Namespaces(RequiredNamespaces requiredNamespaces) { WithProposedNamespaces(requiredNamespaces); } - + public Namespaces(Dictionary proposedNamespaces) { WithProposedNamespaces(proposedNamespaces); } - public override IEqualityComparer Comparer => Namespace.NamespaceComparer; - public Namespaces WithNamespace(string chainNamespace, Namespace nm) { Add(chainNamespace, nm); @@ -41,14 +35,11 @@ public Namespace At(string chainNamespace) { return this[chainNamespace]; } - - public Namespaces WithProposedNamespaces(Dictionary proposedNamespaces) + + public Namespaces WithProposedNamespaces(IDictionary proposedNamespaces) { - foreach (var pair in proposedNamespaces) + foreach (var (chainNamespace, requiredNamespace) in proposedNamespaces) { - var chainNamespace = pair.Key; - var requiredNamespace = pair.Value; - Add(chainNamespace, new Namespace(requiredNamespace)); } diff --git a/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs b/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs index ffac548..0b53ed6 100644 --- a/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs +++ b/WalletConnectSharp.Sign/Models/RequiredNamespaces.cs @@ -17,7 +17,5 @@ public RequiredNamespaces WithProposedNamespace(string chainNamespace, ProposedN Add(chainNamespace, proposedNamespace); return this; } - - public override IEqualityComparer Comparer => ProposedNamespace.RequiredNamespaceComparer; } } diff --git a/WalletConnectSharp.Sign/Models/SortedDictionary.cs b/WalletConnectSharp.Sign/Models/SortedDictionary.cs deleted file mode 100644 index dc99150..0000000 --- a/WalletConnectSharp.Sign/Models/SortedDictionary.cs +++ /dev/null @@ -1,92 +0,0 @@ -using WalletConnectSharp.Common.Utils; - -namespace WalletConnectSharp.Sign.Models; - -public abstract class SortedDictionary : Dictionary, IEquatable> -{ - public abstract IEqualityComparer Comparer { get; } - - protected SortedDictionary() : base() - { - } - - protected SortedDictionary(IDictionary dictionary) : base(dictionary) - { - } - - private List _orderedKeys = new(); - - public List OrderedKeys => _orderedKeys; - - public TValue this[TKey key] - { - get - { - return base[key]; - } - set - { - if (base[key] == null && value != null) - { - _orderedKeys.Add(key); - } - else if (base[key] != null && value == null) - { - _orderedKeys.Remove(key); - } - } - } - - public new void Add(TKey key, TValue value) - { - base.Add(key, value); - _orderedKeys.Add(key); - } - - public new void Remove(TKey key) - { - base.Remove(key); - _orderedKeys.Remove(key); - } - - public bool Equals(SortedDictionary other) - { - return new DictionaryComparer(Comparer).Equals(this, other); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - { - return false; - } - - if (ReferenceEquals(this, obj)) - { - return true; - } - - if (obj.GetType() != this.GetType()) - { - return false; - } - - return Equals((SortedDictionary)obj); - } - - public override int GetHashCode() - { - throw new NotImplementedException(); - } - - - public bool Equals(SortedDictionary x, SortedDictionary y) - { - return new DictionaryComparer(Comparer).Equals(x, y); - } - - public int GetHashCode(SortedDictionary obj) - { - throw new NotImplementedException(); - } -}