Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default session saving completed & tested #156

Merged
merged 17 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Core Modules/WalletConnectSharp.Crypto/KeyChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ private async Task<Dictionary<string, string>> 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<string, string>(this._keyChain));
}

public void Dispose()
Expand Down
67 changes: 47 additions & 20 deletions Core Modules/WalletConnectSharp.Storage/FileSystemStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,38 +88,65 @@ 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()
{
if (!File.Exists(FilePath))
return;

await _semaphoreSlim.WaitAsync();
var json = await File.ReadAllTextAsync(FilePath, Encoding.UTF8);
_semaphoreSlim.Release();

string json;
try
{
Entries = JsonConvert.DeserializeObject<Dictionary<string, object>>(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<string, object>();
_semaphoreSlim.Release();
}

// Hard fail here if the storage file is bad
lock (entriesLock)
{
Entries = JsonConvert.DeserializeObject<Dictionary<string, object>>(json,
new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });
}
}

Expand Down
40 changes: 33 additions & 7 deletions Core Modules/WalletConnectSharp.Storage/InMemoryStorage.cs
skibitsky marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace WalletConnectSharp.Storage
{
public class InMemoryStorage : IKeyValueStorage
{
protected readonly object entriesLock = new object();
protected Dictionary<string, object> Entries = new Dictionary<string, object>();
private bool _initialized = false;
protected bool Disposed;
Expand All @@ -18,51 +19,76 @@ public virtual Task Init()
public virtual Task<string[]> GetKeys()
{
IsInitialized();
return Task.FromResult(Entries.Keys.ToArray());
lock (entriesLock)
{
return Task.FromResult(Entries.Keys.ToArray());
}
}

public virtual async Task<T[]> GetEntriesOfType<T>()
{
IsInitialized();
// GetEntries is thread-safe
return (await GetEntries()).OfType<T>().ToArray();
}

public virtual Task<object[]> GetEntries()
{
IsInitialized();
return Task.FromResult(Entries.Values.ToArray());
lock (entriesLock)
{
return Task.FromResult(Entries.Values.ToArray());
}
}

public virtual Task<T> GetItem<T>(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<T>(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<bool> 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;
}

Expand Down
21 changes: 12 additions & 9 deletions Tests/WalletConnectSharp.Auth.Tests/AuthClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,6 +27,7 @@ public class AuthClientTests : IClassFixture<CryptoWalletFixture>, IAsyncLifetim
};

private readonly CryptoWalletFixture _cryptoWalletFixture;
private readonly ITestOutputHelper _testOutputHelper;

private IAuthClient PeerA;
public IAuthClient PeerB;
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -120,7 +123,7 @@ public async void TestKnownPairings()
var ogHistorySizeB = historyB.Keys.Length;

List<TopicMessage> responses = new List<TopicMessage>();
TaskCompletionSource<TopicMessage> responseTask = new TaskCompletionSource<TopicMessage>();
TaskCompletionSource<TopicMessage> knownPairingTask = new TaskCompletionSource<TopicMessage>();

async void OnPeerBOnAuthRequested(object sender, AuthRequest request)
{
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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<TopicMessage>();
knownPairingTask = new TaskCompletionSource<TopicMessage>();

// 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);

Expand Down
14 changes: 10 additions & 4 deletions Tests/WalletConnectSharp.Sign.Test/SignClientFixture.cs
Original file line number Diff line number Diff line change
@@ -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<WalletConnectSignClient>
{
public IKeyValueStorage StorageOverrideA;
public IKeyValueStorage StorageOverrideB;

public SignClientOptions OptionsA { get; protected set; }
public SignClientOptions OptionsB { get; protected set; }

Expand All @@ -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()
Expand All @@ -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);
}


}
Loading
Loading