From 78a7853b75fd86c2cb6bc68fba74ab21d37928ae Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 07:58:27 -0800 Subject: [PATCH 1/6] Adding nonce to httpagent --- src/Agent/Agents/HttpAgent.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Agent/Agents/HttpAgent.cs b/src/Agent/Agents/HttpAgent.cs index 2d0dc55..c68a2e2 100644 --- a/src/Agent/Agents/HttpAgent.cs +++ b/src/Agent/Agents/HttpAgent.cs @@ -18,6 +18,7 @@ using System.Linq; using EdjCase.ICP.BLS; using System.Threading; +using System.Security.Cryptography; namespace EdjCase.ICP.Agent.Agents { @@ -128,7 +129,9 @@ public async Task CallAsync( CallRequest BuildRequest(Principal sender, ICTimestamp now) { - return new CallRequest(canisterId, method, arg, sender, now); + byte[] nonce = new byte[16]; + RandomNumberGenerator.Fill(nonce); + return new CallRequest(canisterId, method, arg, sender, now, nonce); } } @@ -172,7 +175,9 @@ public async Task CallAsynchronousAsync( CallRequest BuildRequest(Principal sender, ICTimestamp now) { - return new CallRequest(canisterId, method, arg, sender, now); + byte[] nonce = new byte[16]; + RandomNumberGenerator.Fill(nonce); + return new CallRequest(canisterId, method, arg, sender, now, nonce); } } From cb41f874b5582a8ab8099d2db19ca6919d63afb8 Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 07:59:14 -0800 Subject: [PATCH 2/6] tests --- samples/Sample.PocketIC/PocketIc.Tests.cs | 213 ++++++++++++++++++---- 1 file changed, 178 insertions(+), 35 deletions(-) diff --git a/samples/Sample.PocketIC/PocketIc.Tests.cs b/samples/Sample.PocketIC/PocketIc.Tests.cs index 63e1967..f1a8c69 100644 --- a/samples/Sample.PocketIC/PocketIc.Tests.cs +++ b/samples/Sample.PocketIC/PocketIc.Tests.cs @@ -35,14 +35,14 @@ public void Dispose() public class PocketIcTests : IClassFixture { - private readonly PocketIcServerFixture fixture; - private string url => this.fixture.Server.GetUrl(); - - public PocketIcTests(PocketIcServerFixture fixture) - { - this.fixture = fixture; - } - + // private readonly PocketIcServerFixture fixture; + // private string url => this.fixture.Server.GetUrl(); + + // public PocketIcTests(PocketIcServerFixture fixture) + // { + // this.fixture = fixture; + // } + private string url = "http://localhost:42915"; [Fact] public async Task UpdateCallAsync_CounterWasm__Basic__Valid() { @@ -121,40 +121,20 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() Assert.Equal((UnboundedUInt)0, getResponseValue); - var processInfo = new ProcessStartInfo - { - FileName = "dfx", - Arguments = $"canister call {canisterId} inc () --network {httpGateway.Url} --identity anonymous --verbose", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = "/home/gekctek/git/ICP.NET", - }; - Debug.WriteLine("dfx " + processInfo.Arguments); - // RequestId requestId; - using (var process = Process.Start(processInfo)) - { - string output = process!.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - // Optionally log or handle the output/error - Debug.WriteLine($"Output: {output}"); - Debug.WriteLine($"Error: {error}"); - // requestId = new RequestId(Convert.FromHexString(output.Trim().Substring(2))); - } Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); + // CandidArg incResponseArg = await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); // Assert.Equal(CandidArg.Empty(), incResponseArg); // This alternative also doesnt work + var status = await agent.GetReplicaStatusAsync(); + var state = await agent.ReadStateAsync(canisterId, new List()); RequestId requestId = await agent.CallAsynchronousAsync(canisterId, "inc", CandidArg.Empty()); // ICTimestamp currentTime = await pocketIc.GetTimeAsync(); - // await pocketIc.SetTimeAsync(currentTime + TimeSpan.FromSeconds(5)); - await pocketIc.TickAsync(1); - // await Task.Delay(5000); + // await pocketIc.SetTimeAsync(currentTime + TimeSpan.FromSeconds(5)); // Doesnt work + // await pocketIc.TickAsync(1); // Works + await Task.Delay(5000); // Doesnt work IngressStatus ingressStatus = await pocketIc.GetIngressStatusAsync(requestId, EffectivePrincipal.Canister(canisterId)); switch (ingressStatus.Type) @@ -167,6 +147,9 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() throw new Exception("Ingress message ????"); } + throw new Exception("Success"); + + CancellationTokenSource cts = new(TimeSpan.FromSeconds(5)); CandidArg incResponseArg = await agent.WaitForRequestAsync(canisterId, requestId, cts.Token); // Waits indefinitely here Assert.Equal(CandidArg.Empty(), incResponseArg); @@ -174,7 +157,7 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); getResponseArg = getResponse.ThrowOrGetReply(); getResponseValue = getResponseArg.ToObjects(); - Assert.Equal((UnboundedUInt)2, getResponseValue); + Assert.Equal((UnboundedUInt)1, getResponseValue); // CandidArg setRequestArg = CandidArg.FromObjects((UnboundedUInt)10); // cts = new(TimeSpan.FromSeconds(5)); @@ -192,5 +175,165 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() } } } + + private async Task Common(Func> callFunc) + { + byte[] wasmModule = File.ReadAllBytes("CanisterWasmModules/counter.wasm"); + CandidArg arg = CandidArg.FromCandid(); + + + SubnetConfig nnsSubnet = SubnetConfig.New(); // NNS subnet required for HttpGateway + + await using (PocketIc pocketIc = await PocketIc.CreateAsync(this.url, nnsSubnet: nnsSubnet)) + { + Principal canisterId = await pocketIc.CreateAndInstallCanisterAsync(wasmModule, arg); + + await pocketIc.StartCanisterAsync(canisterId); + + await pocketIc.SetTimeAsync(ICTimestamp.Now()); + // Let time progress so that update calls get processed + await using (await pocketIc.AutoProgressTimeAsync()) + { + await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) + { + HttpAgent agent = httpGateway.BuildHttpAgent(); + + Debug.Indent(); + try + { + Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); + List badRequestIds = new(); + List goodRequestIds = new(); + int retries = 0; + while (true) + { + RequestId requestId = await callFunc(agent, canisterId, httpGateway); + // await pocketIc.TickAsync(1); // Works + await Task.Delay(100); + IngressStatus ingressStatus = await pocketIc.GetIngressStatusAsync(requestId, EffectivePrincipal.Canister(canisterId)); + + switch (ingressStatus.Type) + { + case IngressStatusType.Ok: + goodRequestIds.Add(requestId); + Debug.WriteLine("Success"); + foreach (var badRequestId in badRequestIds) + { + Debug.WriteLine(badRequestId + " - Bad"); + } + foreach (var goodRequestId in goodRequestIds) + { + Debug.WriteLine(goodRequestId + " - Good"); + } + return; + case IngressStatusType.NotFound: + badRequestIds.Add(requestId); + retries++; + continue; + default: + throw new Exception("Ingress message ????"); + } + } + } + finally + { + Debug.Unindent(); + } + } + } + } + } + + + [Fact] + public async Task HttpGateway_Ingress_Code() + { + await this.Common(async (agent, canisterId, httpGateway) => + { + return await agent.CallAsynchronousAsync(canisterId, "inc", CandidArg.Empty()); + }); + } + + [Fact] + public async Task HttpGateway_Ingress_Dfx() + { + await this.Common(async (agent, canisterId, httpGateway) => + { + var processInfo = new ProcessStartInfo + { + FileName = "dfx", + Arguments = $"canister call {canisterId} inc () --network {httpGateway.Url} --identity anonymous --verbose --async", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = "/home/gekctek/git/ICP.NET", + }; + Debug.WriteLine("dfx " + processInfo.Arguments); + using (var process = Process.Start(processInfo)) + { + string output = process!.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + // Optionally log or handle the output/error + Debug.WriteLine($"Output: {output}"); + Debug.WriteLine($"Error: {error}"); + return new RequestId(Convert.FromHexString(output.Trim().Substring(2))); + } + }); + } + + [Fact] + public async Task HttpGateway_Ingress_Code_V3() + { + byte[] wasmModule = File.ReadAllBytes("CanisterWasmModules/counter.wasm"); + CandidArg arg = CandidArg.FromCandid(); + + + SubnetConfig nnsSubnet = SubnetConfig.New(); // NNS subnet required for HttpGateway + + await using (PocketIc pocketIc = await PocketIc.CreateAsync(this.url, nnsSubnet: nnsSubnet)) + { + Principal canisterId = await pocketIc.CreateAndInstallCanisterAsync(wasmModule, arg); + + await pocketIc.StartCanisterAsync(canisterId); + + await pocketIc.SetTimeAsync(ICTimestamp.Now()); + // Let time progress so that update calls get processed + await using (await pocketIc.AutoProgressTimeAsync()) + { + await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) + { + HttpAgent agent = httpGateway.BuildHttpAgent(); + + Debug.Indent(); + try + { + Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); + int retries = 0; + while (true) + { + CancellationTokenSource cts = new(TimeSpan.FromSeconds(3)); + try + { + await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); + Debug.WriteLine("Success. Retries: " + retries); + return; + } + catch (OperationCanceledException e) + { + retries++; + } + } + } + finally + { + Debug.Unindent(); + } + } + } + } + } } } From cf186a7d3dad2f443cb0773203dc30939fa52c0a Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 09:09:46 -0800 Subject: [PATCH 3/6] Removing test code --- samples/Sample.PocketIC/PocketIc.Tests.cs | 223 ++-------------------- src/Agent/Agents/HttpAgent.cs | 2 +- src/Candid/Models/ICTimestamp.cs | 5 +- src/PocketIC/IPocketIcHttpClient.cs | 15 +- src/PocketIC/PocketIcHttpClient.cs | 2 + 5 files changed, 33 insertions(+), 214 deletions(-) diff --git a/samples/Sample.PocketIC/PocketIc.Tests.cs b/samples/Sample.PocketIC/PocketIc.Tests.cs index f1a8c69..dfa426f 100644 --- a/samples/Sample.PocketIC/PocketIc.Tests.cs +++ b/samples/Sample.PocketIC/PocketIc.Tests.cs @@ -35,14 +35,14 @@ public void Dispose() public class PocketIcTests : IClassFixture { - // private readonly PocketIcServerFixture fixture; - // private string url => this.fixture.Server.GetUrl(); + private readonly PocketIcServerFixture fixture; + private string url => this.fixture.Server.GetUrl(); + + public PocketIcTests(PocketIcServerFixture fixture) + { + this.fixture = fixture; + } - // public PocketIcTests(PocketIcServerFixture fixture) - // { - // this.fixture = fixture; - // } - private string url = "http://localhost:42915"; [Fact] public async Task UpdateCallAsync_CounterWasm__Basic__Valid() { @@ -121,37 +121,8 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() Assert.Equal((UnboundedUInt)0, getResponseValue); - Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); - - - // CandidArg incResponseArg = await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); - // Assert.Equal(CandidArg.Empty(), incResponseArg); - - // This alternative also doesnt work - var status = await agent.GetReplicaStatusAsync(); - var state = await agent.ReadStateAsync(canisterId, new List()); - RequestId requestId = await agent.CallAsynchronousAsync(canisterId, "inc", CandidArg.Empty()); - // ICTimestamp currentTime = await pocketIc.GetTimeAsync(); - // await pocketIc.SetTimeAsync(currentTime + TimeSpan.FromSeconds(5)); // Doesnt work - // await pocketIc.TickAsync(1); // Works - await Task.Delay(5000); // Doesnt work - IngressStatus ingressStatus = await pocketIc.GetIngressStatusAsync(requestId, EffectivePrincipal.Canister(canisterId)); - - switch (ingressStatus.Type) - { - case IngressStatusType.Ok: - break; - case IngressStatusType.NotFound: - throw new Exception("Ingress message not found"); - default: - throw new Exception("Ingress message ????"); - } - - throw new Exception("Success"); - - CancellationTokenSource cts = new(TimeSpan.FromSeconds(5)); - CandidArg incResponseArg = await agent.WaitForRequestAsync(canisterId, requestId, cts.Token); // Waits indefinitely here + CandidArg incResponseArg = await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); Assert.Equal(CandidArg.Empty(), incResponseArg); getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); @@ -159,178 +130,16 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() getResponseValue = getResponseArg.ToObjects(); Assert.Equal((UnboundedUInt)1, getResponseValue); - // CandidArg setRequestArg = CandidArg.FromObjects((UnboundedUInt)10); - // cts = new(TimeSpan.FromSeconds(5)); - // CandidArg setResponseArg = await agent.CallAsync(canisterId, "set", setRequestArg, cancellationToken: cts.Token); - // Assert.Equal(CandidArg.Empty(), setResponseArg); - - // await pocketIc.TickAsync(); - - // getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); - // getResponseArg = getResponse.ThrowOrGetReply(); - // getResponseValue = getResponseArg.ToObjects(); - // Assert.Equal((UnboundedUInt)10, getResponseValue); - - } - } - } - } - - private async Task Common(Func> callFunc) - { - byte[] wasmModule = File.ReadAllBytes("CanisterWasmModules/counter.wasm"); - CandidArg arg = CandidArg.FromCandid(); - - - SubnetConfig nnsSubnet = SubnetConfig.New(); // NNS subnet required for HttpGateway + CandidArg setRequestArg = CandidArg.FromObjects((UnboundedUInt)10); + cts = new(TimeSpan.FromSeconds(5)); + CandidArg setResponseArg = await agent.CallAsync(canisterId, "set", setRequestArg, cancellationToken: cts.Token); + Assert.Equal(CandidArg.Empty(), setResponseArg); - await using (PocketIc pocketIc = await PocketIc.CreateAsync(this.url, nnsSubnet: nnsSubnet)) - { - Principal canisterId = await pocketIc.CreateAndInstallCanisterAsync(wasmModule, arg); - - await pocketIc.StartCanisterAsync(canisterId); - - await pocketIc.SetTimeAsync(ICTimestamp.Now()); - // Let time progress so that update calls get processed - await using (await pocketIc.AutoProgressTimeAsync()) - { - await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) - { - HttpAgent agent = httpGateway.BuildHttpAgent(); - - Debug.Indent(); - try - { - Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); - List badRequestIds = new(); - List goodRequestIds = new(); - int retries = 0; - while (true) - { - RequestId requestId = await callFunc(agent, canisterId, httpGateway); - // await pocketIc.TickAsync(1); // Works - await Task.Delay(100); - IngressStatus ingressStatus = await pocketIc.GetIngressStatusAsync(requestId, EffectivePrincipal.Canister(canisterId)); - - switch (ingressStatus.Type) - { - case IngressStatusType.Ok: - goodRequestIds.Add(requestId); - Debug.WriteLine("Success"); - foreach (var badRequestId in badRequestIds) - { - Debug.WriteLine(badRequestId + " - Bad"); - } - foreach (var goodRequestId in goodRequestIds) - { - Debug.WriteLine(goodRequestId + " - Good"); - } - return; - case IngressStatusType.NotFound: - badRequestIds.Add(requestId); - retries++; - continue; - default: - throw new Exception("Ingress message ????"); - } - } - } - finally - { - Debug.Unindent(); - } - } - } - } - } - - - [Fact] - public async Task HttpGateway_Ingress_Code() - { - await this.Common(async (agent, canisterId, httpGateway) => - { - return await agent.CallAsynchronousAsync(canisterId, "inc", CandidArg.Empty()); - }); - } - - [Fact] - public async Task HttpGateway_Ingress_Dfx() - { - await this.Common(async (agent, canisterId, httpGateway) => - { - var processInfo = new ProcessStartInfo - { - FileName = "dfx", - Arguments = $"canister call {canisterId} inc () --network {httpGateway.Url} --identity anonymous --verbose --async", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = "/home/gekctek/git/ICP.NET", - }; - Debug.WriteLine("dfx " + processInfo.Arguments); - using (var process = Process.Start(processInfo)) - { - string output = process!.StandardOutput.ReadToEnd(); - string error = process.StandardError.ReadToEnd(); - process.WaitForExit(); - - // Optionally log or handle the output/error - Debug.WriteLine($"Output: {output}"); - Debug.WriteLine($"Error: {error}"); - return new RequestId(Convert.FromHexString(output.Trim().Substring(2))); - } - }); - } - - [Fact] - public async Task HttpGateway_Ingress_Code_V3() - { - byte[] wasmModule = File.ReadAllBytes("CanisterWasmModules/counter.wasm"); - CandidArg arg = CandidArg.FromCandid(); - - - SubnetConfig nnsSubnet = SubnetConfig.New(); // NNS subnet required for HttpGateway - - await using (PocketIc pocketIc = await PocketIc.CreateAsync(this.url, nnsSubnet: nnsSubnet)) - { - Principal canisterId = await pocketIc.CreateAndInstallCanisterAsync(wasmModule, arg); - - await pocketIc.StartCanisterAsync(canisterId); - - await pocketIc.SetTimeAsync(ICTimestamp.Now()); - // Let time progress so that update calls get processed - await using (await pocketIc.AutoProgressTimeAsync()) - { - await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) - { - HttpAgent agent = httpGateway.BuildHttpAgent(); + getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); + getResponseArg = getResponse.ThrowOrGetReply(); + getResponseValue = getResponseArg.ToObjects(); + Assert.Equal((UnboundedUInt)10, getResponseValue); - Debug.Indent(); - try - { - Debug.WriteLine("---Start-----\n\n\n\n\n\n\n\n\n\n\n"); - int retries = 0; - while (true) - { - CancellationTokenSource cts = new(TimeSpan.FromSeconds(3)); - try - { - await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); - Debug.WriteLine("Success. Retries: " + retries); - return; - } - catch (OperationCanceledException e) - { - retries++; - } - } - } - finally - { - Debug.Unindent(); - } } } } diff --git a/src/Agent/Agents/HttpAgent.cs b/src/Agent/Agents/HttpAgent.cs index c68a2e2..dc05781 100644 --- a/src/Agent/Agents/HttpAgent.cs +++ b/src/Agent/Agents/HttpAgent.cs @@ -318,7 +318,7 @@ public async Task GetReplicaStatusAsync( SubjectPublicKeyInfo publicKey = this.Identity.GetPublicKey(); principal = publicKey.ToPrincipal(); } - TRequest request = getRequest(principal, ICTimestamp.Future(TimeSpan.FromSeconds(10))); + TRequest request = getRequest(principal, ICTimestamp.Future(TimeSpan.FromMinutes(3))); Dictionary content = request.BuildHashableItem(); SignedContent signedContent; diff --git a/src/Candid/Models/ICTimestamp.cs b/src/Candid/Models/ICTimestamp.cs index 653c720..e698f15 100644 --- a/src/Candid/Models/ICTimestamp.cs +++ b/src/Candid/Models/ICTimestamp.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using EdjCase.ICP.Candid.Crypto; using EdjCase.ICP.Candid.Encodings; using EdjCase.ICP.Candid.Models.Values; @@ -136,13 +137,13 @@ public override string ToString() private static UnboundedUInt EpochNowInNanoseconds() { - ulong nanoseconds = (ulong)(((DateTime.UtcNow - epoch).TotalMilliseconds + REPLICA_PERMITTED_DRIFT_MILLISECONDS) * 1_000_000); + ulong nanoseconds = (ulong)((DateTime.UtcNow - epoch).TotalMilliseconds * 1_000_000); return UnboundedUInt.FromUInt64(nanoseconds); } private static UnboundedUInt GetNanosecondsFromTimeSpan(TimeSpan timespanFromNow) { - return (ulong)(timespanFromNow.Ticks / 100L); + return (ulong)(timespanFromNow.TotalMilliseconds * 1_000_000); } } } \ No newline at end of file diff --git a/src/PocketIC/IPocketIcHttpClient.cs b/src/PocketIC/IPocketIcHttpClient.cs index a0de04f..635a620 100644 --- a/src/PocketIC/IPocketIcHttpClient.cs +++ b/src/PocketIC/IPocketIcHttpClient.cs @@ -95,7 +95,7 @@ Task QueryCallAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal? effectivePrincipal = null); + EffectivePrincipal effectivePrincipal); /// /// Gets the topology information for a PocketIC instance @@ -150,7 +150,14 @@ Task QueryCallAsync( /// The public key principal of the subnet Task GetPublicKeyForSubnetAsync(int instanceId, Principal subnetId); - Task GetIngressStatusAsync(int instanceId, RequestId messageId, EffectivePrincipal effectivePrincipal); + /// + /// Gets the status of an ingress message + /// + /// The id of the PocketIC instance + /// The id of the request to check the status for + /// Optional effective principal for the call, defaults to canister id + /// + Task GetIngressStatusAsync(int instanceId, RequestId requestId, EffectivePrincipal effectivePrincipal); /// /// Submits an ingress message to a canister without waiting for execution @@ -168,7 +175,7 @@ Task SubmitIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal? effectivePrincipal = null); + EffectivePrincipal effectivePrincipal); /// /// Executes an ingress message on a canister and waits for the response @@ -186,7 +193,7 @@ Task ExecuteIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal? effectivePrincipal = null); + EffectivePrincipal effectivePrincipal); /// /// Waits for an ingress message to complete execution diff --git a/src/PocketIC/PocketIcHttpClient.cs b/src/PocketIC/PocketIcHttpClient.cs index 6c69cac..c07aade 100644 --- a/src/PocketIC/PocketIcHttpClient.cs +++ b/src/PocketIC/PocketIcHttpClient.cs @@ -205,6 +205,7 @@ public async Task DeleteInstanceAsync(int id) { await this.DeleteAsync($"/instances/{id}"); } + /// public async Task QueryCallAsync( int instanceId, @@ -326,6 +327,7 @@ public async Task GetPublicKeyForSubnetAsync(int instanceId, Principa return Principal.FromBytes(publicKey); } + /// public async Task GetIngressStatusAsync(int instanceId, RequestId messageId, EffectivePrincipal effectivePrincipal) { var data = new JsonObject From 269f44bc7bc9ecfb52d232dcbc28c7a3732cf4ef Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 09:28:04 -0800 Subject: [PATCH 4/6] Adding xml docs --- src/PocketIC/API.xml | 83 +++++++++++++++++++++++++++++ src/PocketIC/IPocketIcHttpClient.cs | 55 ++++++++++++++++--- src/PocketIC/PocketIc.cs | 10 +++- src/PocketIC/PocketIcHttpClient.cs | 15 +++--- 4 files changed, 149 insertions(+), 14 deletions(-) diff --git a/src/PocketIC/API.xml b/src/PocketIC/API.xml index c6d146c..4f16651 100644 --- a/src/PocketIC/API.xml +++ b/src/PocketIC/API.xml @@ -130,6 +130,15 @@ The subnet id The public key principal of the subnet + + + Gets the status of an ingress message + + The id of the PocketIC instance + The id of the request to check the status for + Optional effective principal for the call + + Submits an ingress message to a canister without waiting for execution @@ -261,6 +270,49 @@ The instance has been deleted + + + A variant model representing the status of an ingress message + + + + + The type of ingress status + + + + + Gets the Ok variant data for the status + + + + + Creates a new Ok status with the variant data + + The request status data + An Ok ingress variant model + + + + Creates a new NotFound status + + A NotFound ingress variant model + + + + The variant types of ingress status + + + + + Not Found + + + + + Ok + + Configuration for HTTPS support @@ -376,6 +428,26 @@ The principal id + + + Creates an empty effective principal + + An empty effective principal + + + + Creates an effective principal for a subnet + + The subnet id + A subnet effective principal + + + + Creates an effective principal for a canister + + The canister id + A canister effective principal + Types of effective principals @@ -644,6 +716,9 @@ + + + @@ -935,6 +1010,14 @@ Whether to use cached topology info. Defaults to true List of subnet topology information + + + Gets the ingress status of the specified request + + The request ID to check + The effective principal for the request + The ingress status of the request + Gets the cycles balance of a canister diff --git a/src/PocketIC/IPocketIcHttpClient.cs b/src/PocketIC/IPocketIcHttpClient.cs index 635a620..410ca37 100644 --- a/src/PocketIC/IPocketIcHttpClient.cs +++ b/src/PocketIC/IPocketIcHttpClient.cs @@ -95,7 +95,7 @@ Task QueryCallAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal); + EffectivePrincipal? effectivePrincipal = null); /// /// Gets the topology information for a PocketIC instance @@ -155,9 +155,12 @@ Task QueryCallAsync( /// /// The id of the PocketIC instance /// The id of the request to check the status for - /// Optional effective principal for the call, defaults to canister id + /// Optional effective principal for the call /// - Task GetIngressStatusAsync(int instanceId, RequestId requestId, EffectivePrincipal effectivePrincipal); + Task GetIngressStatusAsync( + int instanceId, + RequestId requestId, + EffectivePrincipal effectivePrincipal); /// /// Submits an ingress message to a canister without waiting for execution @@ -175,7 +178,7 @@ Task SubmitIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal); + EffectivePrincipal? effectivePrincipal = null); /// /// Executes an ingress message on a canister and waits for the response @@ -193,7 +196,7 @@ Task ExecuteIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal); + EffectivePrincipal? effectivePrincipal = null); /// /// Waits for an ingress message to complete execution @@ -309,9 +312,14 @@ public enum InstanceStatus /// Deleted } - +/// +/// A variant model representing the status of an ingress message +/// public class IngressStatus { + /// + /// The type of ingress status + /// public IngressStatusType Type { get; } private object? value; @@ -322,25 +330,46 @@ private IngressStatus(IngressStatusType type, object? value) this.value = value; } + /// + /// Gets the Ok variant data for the status + /// public RequestStatus AsOk() { return (RequestStatus)this.value!; } + /// + /// Creates a new Ok status with the variant data + /// + /// The request status data + /// An Ok ingress variant model public static IngressStatus Ok(RequestStatus status) { return new IngressStatus(IngressStatusType.Ok, status); } + /// + /// Creates a new NotFound status + /// + /// A NotFound ingress variant model public static IngressStatus NotFound() { return new IngressStatus(IngressStatusType.NotFound, null); } } +/// +/// The variant types of ingress status +/// public enum IngressStatusType { + /// + /// Not Found + /// NotFound, + /// + /// Ok + /// Ok } @@ -463,16 +492,30 @@ private EffectivePrincipal(EffectivePrincipalType type, Principal id) this.Id = id; } + /// + /// Creates an empty effective principal + /// + /// An empty effective principal public static EffectivePrincipal None() { return new EffectivePrincipal(EffectivePrincipalType.None, Principal.Anonymous()); } + /// + /// Creates an effective principal for a subnet + /// + /// The subnet id + /// A subnet effective principal public static EffectivePrincipal Subnet(Principal id) { return new EffectivePrincipal(EffectivePrincipalType.Subnet, id); } + /// + /// Creates an effective principal for a canister + /// + /// The canister id + /// A canister effective principal public static EffectivePrincipal Canister(Principal id) { return new EffectivePrincipal(EffectivePrincipalType.Canister, id); diff --git a/src/PocketIC/PocketIc.cs b/src/PocketIC/PocketIc.cs index 4e98d21..f3bbffe 100644 --- a/src/PocketIC/PocketIc.cs +++ b/src/PocketIC/PocketIc.cs @@ -262,9 +262,15 @@ public async ValueTask> GetTopologyAsync(bool useCache = tr return topologies; } - public async Task GetIngressStatusAsync(RequestId messageId, EffectivePrincipal effectivePrincipal) + /// + /// Gets the ingress status of the specified request + /// + /// The request ID to check + /// The effective principal for the request + /// The ingress status of the request + public async Task GetIngressStatusAsync(RequestId requestId, EffectivePrincipal effectivePrincipal) { - return await this.HttpClient.GetIngressStatusAsync(this.InstanceId, messageId, effectivePrincipal); + return await this.HttpClient.GetIngressStatusAsync(this.InstanceId, requestId, effectivePrincipal); } /// diff --git a/src/PocketIC/PocketIcHttpClient.cs b/src/PocketIC/PocketIcHttpClient.cs index c07aade..001664e 100644 --- a/src/PocketIC/PocketIcHttpClient.cs +++ b/src/PocketIC/PocketIcHttpClient.cs @@ -213,7 +213,7 @@ public async Task QueryCallAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal) + EffectivePrincipal? effectivePrincipal = null) { return await this.ProcessIngressMessageInternalAsync( $"/instances/{instanceId}/read/query", @@ -328,7 +328,10 @@ public async Task GetPublicKeyForSubnetAsync(int instanceId, Principa } /// - public async Task GetIngressStatusAsync(int instanceId, RequestId messageId, EffectivePrincipal effectivePrincipal) + public async Task GetIngressStatusAsync( + int instanceId, + RequestId messageId, + EffectivePrincipal effectivePrincipal) { var data = new JsonObject { @@ -383,7 +386,7 @@ public async Task SubmitIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal) + EffectivePrincipal? effectivePrincipal = null) { return await this.ProcessIngressMessageInternalAsync( $"/instances/{instanceId}/update/submit_ingress_message", @@ -401,7 +404,7 @@ public async Task ExecuteIngressMessageAsync( Principal canisterId, string method, CandidArg request, - EffectivePrincipal effectivePrincipal) + EffectivePrincipal? effectivePrincipal = null) { return await this.ProcessIngressMessageInternalAsync( $"/instances/{instanceId}/update/execute_ingress_message", @@ -440,11 +443,11 @@ private async Task ProcessIngressMessageInternalAsync( Principal canisterId, string method, CandidArg arg, - EffectivePrincipal effectivePrincipal) + EffectivePrincipal? effectivePrincipal = null) { byte[] payload = arg.Encode(); - JsonNode effectivePrincipalJson = EffectivePrincipalToJson(effectivePrincipal); + JsonNode effectivePrincipalJson = EffectivePrincipalToJson(effectivePrincipal ?? EffectivePrincipal.Canister(canisterId)); var options = new JsonObject { From 3044f0d4be422ac9302837d6b77c9d557235de1a Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 10:06:37 -0800 Subject: [PATCH 5/6] Fixing tests --- samples/Sample.PocketIC/PocketIc.Tests.cs | 13 +++-- src/Agent/Agents/HttpAgent.cs | 3 ++ src/PocketIC/API.xml | 6 +-- src/PocketIC/PocketIc.cs | 4 +- src/PocketIC/PocketIcServer.cs | 6 ++- test/PocketIC.Tests/PocketIc.Tests.cs | 61 +++++++++++------------ 6 files changed, 52 insertions(+), 41 deletions(-) diff --git a/samples/Sample.PocketIC/PocketIc.Tests.cs b/samples/Sample.PocketIC/PocketIc.Tests.cs index dfa426f..5db485a 100644 --- a/samples/Sample.PocketIC/PocketIc.Tests.cs +++ b/samples/Sample.PocketIC/PocketIc.Tests.cs @@ -16,10 +16,17 @@ public class PocketIcServerFixture : IDisposable { public PocketIcServer Server { get; private set; } + private static readonly int uniqueId = 0; + private static object lockObj = new object(); + public PocketIcServerFixture() { - // Start the server for all tests - this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug, showErrorLogs: true).GetAwaiter().GetResult(); + lock (lockObj) + { + // Start the server for all tests + this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug, showErrorLogs: true, uniqueId: uniqueId).GetAwaiter().GetResult(); + uniqueId++; + } } public void Dispose() @@ -110,7 +117,7 @@ public async Task HttpGateway_CounterWasm__Basic__Valid() await pocketIc.SetTimeAsync(ICTimestamp.Now()); // Let time progress so that update calls get processed - await using (await pocketIc.AutoProgressTimeAsync()) + await using (await pocketIc.AutoProgressAsync()) { await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) { diff --git a/src/Agent/Agents/HttpAgent.cs b/src/Agent/Agents/HttpAgent.cs index dc05781..ff971af 100644 --- a/src/Agent/Agents/HttpAgent.cs +++ b/src/Agent/Agents/HttpAgent.cs @@ -19,6 +19,7 @@ using EdjCase.ICP.BLS; using System.Threading; using System.Security.Cryptography; +using System.Diagnostics; namespace EdjCase.ICP.Agent.Agents { @@ -318,6 +319,8 @@ public async Task GetReplicaStatusAsync( SubjectPublicKeyInfo publicKey = this.Identity.GetPublicKey(); principal = publicKey.ToPrincipal(); } + Debug.WriteLine("Now: " + ICTimestamp.Now()); + Debug.WriteLine("Expiry: " + ICTimestamp.Future(TimeSpan.FromMinutes(3))); TRequest request = getRequest(principal, ICTimestamp.Future(TimeSpan.FromMinutes(3))); Dictionary content = request.BuildHashableItem(); diff --git a/src/PocketIC/API.xml b/src/PocketIC/API.xml index 4f16651..c4be999 100644 --- a/src/PocketIC/API.xml +++ b/src/PocketIC/API.xml @@ -982,9 +982,9 @@ The timestamp to set - + - Enables automatic time progression for the IC instance until disposed + Enables automatic time/tick progression for the IC instance until disposed Optional delay between time updates A disposable object that will stop auto progression when disposed @@ -1290,7 +1290,7 @@ Disposes of the server process - + Starts the pocket-ic server process diff --git a/src/PocketIC/PocketIc.cs b/src/PocketIC/PocketIc.cs index f3bbffe..c95c358 100644 --- a/src/PocketIC/PocketIc.cs +++ b/src/PocketIC/PocketIc.cs @@ -211,11 +211,11 @@ public Task SetTimeAsync(ICTimestamp time) } /// - /// Enables automatic time progression for the IC instance until disposed + /// Enables automatic time/tick progression for the IC instance until disposed /// /// Optional delay between time updates /// A disposable object that will stop auto progression when disposed - public async Task AutoProgressTimeAsync(TimeSpan? artificalDelay = null) + public async Task AutoProgressAsync(TimeSpan? artificalDelay = null) { await this.HttpClient.AutoProgressTimeAsync(this.InstanceId, artificalDelay); diff --git a/src/PocketIC/PocketIcServer.cs b/src/PocketIC/PocketIcServer.cs index eea5543..2671635 100644 --- a/src/PocketIC/PocketIcServer.cs +++ b/src/PocketIC/PocketIcServer.cs @@ -53,16 +53,18 @@ public async ValueTask DisposeAsync() /// /// Outputs the runtime logs using Debug.WriteLine(...) with the specified log level. Null value disables the logging /// Outputs the error logs using Debug.WriteLine(...) + /// Unique identifier for the server process. If null, the current process ID is used /// The instance of the PocketIcServer with the running process public static async Task StartAsync( LogLevel? runtimeLogLevel = null, - bool showErrorLogs = true + bool showErrorLogs = true, + int? uniqueId = null ) { string binPath = GetBinPath(); EnsureExecutablePermission(binPath); - int pid = Process.GetCurrentProcess().Id; + int pid = uniqueId ?? Process.GetCurrentProcess().Id; string picFilePrefix = $"pocket_ic_{pid}"; string portFilePath = Path.Combine(Path.GetTempPath(), $"{picFilePrefix}.port"); diff --git a/test/PocketIC.Tests/PocketIc.Tests.cs b/test/PocketIC.Tests/PocketIc.Tests.cs index 3607d6f..0f0f75c 100644 --- a/test/PocketIC.Tests/PocketIc.Tests.cs +++ b/test/PocketIC.Tests/PocketIc.Tests.cs @@ -122,26 +122,13 @@ await pocketIc.UpdateCallNoResponseAsync( // Test time ICTimestamp initialTime = await pocketIc.GetTimeAsync(); - ICTimestamp newTime = new(initialTime.NanoSeconds + (UnboundedUInt)1_000); + ICTimestamp newTime = ICTimestamp.Now(); await pocketIc.SetTimeAsync(newTime); ICTimestamp resetTime = await pocketIc.GetTimeAsync(); Assert.Equal(newTime.NanoSeconds, resetTime.NanoSeconds); - // Test auto progress time - await using (await pocketIc.AutoProgressTimeAsync()) - { - await Task.Delay(100); - ICTimestamp autoProgressedTime = await pocketIc.GetTimeAsync(); - Assert.True(autoProgressedTime.NanoSeconds > resetTime.NanoSeconds); - } - - // Verify time is stopped - ICTimestamp stopProgressTime = await pocketIc.GetTimeAsync(); - await Task.Delay(100); - ICTimestamp stopProgressTime2 = await pocketIc.GetTimeAsync(); - Assert.Equal(stopProgressTime.NanoSeconds, stopProgressTime2.NanoSeconds); // Test subnet id Principal subnetId = await pocketIc.GetSubnetIdForCanisterAsync(canisterId); @@ -159,26 +146,38 @@ await pocketIc.UpdateCallNoResponseAsync( Assert.Equal(newStableMemory, stableMemory[..8]); - // Setup http gateway and test api call to canister - await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) + // Test auto progress time + await using (await pocketIc.AutoProgressAsync()) { - HttpAgent agent = httpGateway.BuildHttpAgent(); - QueryResponse getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); - CandidArg getResponseArg = getResponse.ThrowOrGetReply(); - UnboundedUInt getResponseValue = getResponseArg.ToObjects(); - Assert.Equal((UnboundedUInt)1, getResponseValue); - - CancellationTokenSource cts = new(TimeSpan.FromSeconds(5)); - CandidArg incResponseArg = await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); - Assert.Equal(CandidArg.Empty(), incResponseArg); - - getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); - getResponseArg = getResponse.ThrowOrGetReply(); - getResponseValue = getResponseArg.ToObjects(); - Assert.Equal((UnboundedUInt)2, getResponseValue); - + await Task.Delay(100); + ICTimestamp autoProgressedTime = await pocketIc.GetTimeAsync(); + Assert.True(autoProgressedTime.NanoSeconds > resetTime.NanoSeconds); + // Setup http gateway and test api call to canister (needs auto progress) + await using (HttpGateway httpGateway = await pocketIc.RunHttpGatewayAsync()) + { + HttpAgent agent = httpGateway.BuildHttpAgent(); + QueryResponse getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); + CandidArg getResponseArg = getResponse.ThrowOrGetReply(); + UnboundedUInt getResponseValue = getResponseArg.ToObjects(); + Assert.Equal((UnboundedUInt)1, getResponseValue); + + CancellationTokenSource cts = new(TimeSpan.FromSeconds(50)); + CandidArg incResponseArg = await agent.CallAsync(canisterId, "inc", CandidArg.Empty(), cancellationToken: cts.Token); + Assert.Equal(CandidArg.Empty(), incResponseArg); + + getResponse = await agent.QueryAsync(canisterId, "get", CandidArg.Empty()); + getResponseArg = getResponse.ThrowOrGetReply(); + getResponseValue = getResponseArg.ToObjects(); + Assert.Equal((UnboundedUInt)2, getResponseValue); + + } } + // Verify time is stopped + ICTimestamp stopProgressTime = await pocketIc.GetTimeAsync(); + await Task.Delay(100); + ICTimestamp stopProgressTime2 = await pocketIc.GetTimeAsync(); + Assert.Equal(stopProgressTime.NanoSeconds, stopProgressTime2.NanoSeconds); // Stop canister await pocketIc.StopCanisterAsync(canisterId); From 0fb72ae0bc91332bf853bb206d8364cdb8d9dd69 Mon Sep 17 00:00:00 2001 From: Gekctek Date: Fri, 20 Dec 2024 10:14:32 -0800 Subject: [PATCH 6/6] Fixing tests --- samples/Sample.PocketIC/PocketIc.Tests.cs | 11 +++-------- src/Agent/Agents/HttpAgent.cs | 2 -- src/PocketIC/API.xml | 2 +- src/PocketIC/PocketIcServer.cs | 6 ++---- test/PocketIC.Tests/PocketIcServerFixture.cs | 3 ++- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/samples/Sample.PocketIC/PocketIc.Tests.cs b/samples/Sample.PocketIC/PocketIc.Tests.cs index 5db485a..8e4f64c 100644 --- a/samples/Sample.PocketIC/PocketIc.Tests.cs +++ b/samples/Sample.PocketIC/PocketIc.Tests.cs @@ -16,17 +16,12 @@ public class PocketIcServerFixture : IDisposable { public PocketIcServer Server { get; private set; } - private static readonly int uniqueId = 0; - private static object lockObj = new object(); public PocketIcServerFixture() { - lock (lockObj) - { - // Start the server for all tests - this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug, showErrorLogs: true, uniqueId: uniqueId).GetAwaiter().GetResult(); - uniqueId++; - } + // Start the server for all tests + this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug, showErrorLogs: true).GetAwaiter().GetResult(); + } public void Dispose() diff --git a/src/Agent/Agents/HttpAgent.cs b/src/Agent/Agents/HttpAgent.cs index ff971af..cf79ec5 100644 --- a/src/Agent/Agents/HttpAgent.cs +++ b/src/Agent/Agents/HttpAgent.cs @@ -319,8 +319,6 @@ public async Task GetReplicaStatusAsync( SubjectPublicKeyInfo publicKey = this.Identity.GetPublicKey(); principal = publicKey.ToPrincipal(); } - Debug.WriteLine("Now: " + ICTimestamp.Now()); - Debug.WriteLine("Expiry: " + ICTimestamp.Future(TimeSpan.FromMinutes(3))); TRequest request = getRequest(principal, ICTimestamp.Future(TimeSpan.FromMinutes(3))); Dictionary content = request.BuildHashableItem(); diff --git a/src/PocketIC/API.xml b/src/PocketIC/API.xml index c4be999..7c0b851 100644 --- a/src/PocketIC/API.xml +++ b/src/PocketIC/API.xml @@ -1290,7 +1290,7 @@ Disposes of the server process - + Starts the pocket-ic server process diff --git a/src/PocketIC/PocketIcServer.cs b/src/PocketIC/PocketIcServer.cs index 2671635..2f2108d 100644 --- a/src/PocketIC/PocketIcServer.cs +++ b/src/PocketIC/PocketIcServer.cs @@ -53,18 +53,16 @@ public async ValueTask DisposeAsync() /// /// Outputs the runtime logs using Debug.WriteLine(...) with the specified log level. Null value disables the logging /// Outputs the error logs using Debug.WriteLine(...) - /// Unique identifier for the server process. If null, the current process ID is used /// The instance of the PocketIcServer with the running process public static async Task StartAsync( LogLevel? runtimeLogLevel = null, - bool showErrorLogs = true, - int? uniqueId = null + bool showErrorLogs = true ) { string binPath = GetBinPath(); EnsureExecutablePermission(binPath); - int pid = uniqueId ?? Process.GetCurrentProcess().Id; + string pid = Guid.NewGuid().ToString(); string picFilePrefix = $"pocket_ic_{pid}"; string portFilePath = Path.Combine(Path.GetTempPath(), $"{picFilePrefix}.port"); diff --git a/test/PocketIC.Tests/PocketIcServerFixture.cs b/test/PocketIC.Tests/PocketIcServerFixture.cs index 6442599..0da4e4c 100644 --- a/test/PocketIC.Tests/PocketIcServerFixture.cs +++ b/test/PocketIC.Tests/PocketIcServerFixture.cs @@ -11,7 +11,7 @@ public class PocketIcServerFixture : IDisposable public PocketIcServerFixture() { // Start the server for all tests - this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug).GetAwaiter().GetResult(); + this.Server = PocketIcServer.StartAsync(runtimeLogLevel: LogLevel.Debug, showErrorLogs: true).GetAwaiter().GetResult(); } public void Dispose() @@ -19,6 +19,7 @@ public void Dispose() // Stop the server after all tests if (this.Server != null) { + this.Server.StopAsync().GetAwaiter().GetResult(); this.Server.DisposeAsync().GetAwaiter().GetResult(); } }