Skip to content

Commit

Permalink
fix(net): parse reply code & error (#695)
Browse files Browse the repository at this point in the history
  • Loading branch information
vobradovich authored Nov 29, 2024
1 parent 1088d5f commit b567304
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 11 deletions.
76 changes: 65 additions & 11 deletions net/src/Sails.Remoting/Core/RemotingViaNodeClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using System.Threading.Tasks;
using EnsureThat;
using Sails.Remoting.Abstractions.Core;
using Sails.Remoting.Exceptions;
using Substrate.Gear.Api.Generated;
using Substrate.Gear.Api.Generated.Model.gear_core_errors.simple;
using Substrate.Gear.Api.Generated.Model.gprimitives;
using Substrate.Gear.Api.Generated.Model.vara_runtime;
using Substrate.Gear.Api.Generated.Storage;
Expand Down Expand Up @@ -83,14 +85,17 @@ public RemotingViaNodeClient(
createProgram,
DefaultExtrinsicTtlInBlocks,
selectResultOnSuccess: SelectMessageQueuedEventData,
selectResultOnError: (_) =>
selectResultOnError: static (_) =>
throw new Exception("TODO: Custom exception. Unable to create program."),
cancellationToken),
extractResult: (queuedMessageData, replyMessage) => (
(ActorId)queuedMessageData.Value[2],
replyMessage.Payload.Value.Value
.Select(@byte => @byte.Value)
.ToArray()),
extractResult: static (queuedMessageData, replyMessage) =>
{
EnsureSuccessOrThrowReplyException(replyMessage.Details.Value.Code, replyMessage.Payload.Bytes);
return (
(ActorId)queuedMessageData.Value[2],
replyMessage.Payload.Value.Value.Select(@byte => @byte.Value).ToArray()
);
},
cancellationToken)
.ConfigureAwait(false);
}
Expand Down Expand Up @@ -131,12 +136,14 @@ public async Task<RemotingReply<byte[]>> MessageAsync(
sendMessage,
DefaultExtrinsicTtlInBlocks,
selectResultOnSuccess: SelectMessageQueuedEventData,
selectResultOnError: (_) =>
selectResultOnError: static (_) =>
throw new Exception("TODO: Custom exception. Unable to send message."),
cancellationToken),
extractResult: (_, replyMessage) => replyMessage.Payload.Value.Value
.Select(@byte => @byte.Value)
.ToArray(),
extractResult: static (_, replyMessage) =>
{
EnsureSuccessOrThrowReplyException(replyMessage.Details.Value.Code, replyMessage.Payload.Bytes);
return replyMessage.Payload.Value.Value.Select(@byte => @byte.Value).ToArray();
},
cancellationToken)
.ConfigureAwait(false);
}
Expand Down Expand Up @@ -165,7 +172,7 @@ public async Task<byte[]> QueryAsync(
cancellationToken)
.ConfigureAwait(false);

// TODO: Check for reply code
EnsureSuccessOrThrowReplyException(replyInfo.Code, replyInfo.EncodedPayload);

return replyInfo.EncodedPayload;
}
Expand All @@ -188,4 +195,51 @@ private static MessageQueuedEventData SelectMessageQueuedEventData(IEnumerable<B
(MessageQueuedEventData data) => data)
.SingleOrDefault()
?? throw new Exception("TODO: Custom exception. Something terrible happened.");

private static void EnsureSuccessOrThrowReplyException(EnumReplyCode replyCode, byte[] payload)
{
if (replyCode.Value == ReplyCode.Success)
{
return;
}
var errorString = ParseErrorString(payload);
ThrowReplyException(replyCode, errorString);
}

private static string ParseErrorString(byte[] payload)
{
var p = 0;
var errorStr = new Str();
try
{
errorStr.Decode(payload, ref p);
}
catch
{
errorStr = new Str("Unexpected reply error");
}
return errorStr;
}

private static void ThrowReplyException(EnumReplyCode replyCode, string message)
{
var reason = ErrorReplyReason.Unsupported;
if (replyCode.Value == ReplyCode.Error)
{
var enumReason = (EnumErrorReplyReason)replyCode.Value2;
reason = enumReason.Value;

if (reason == ErrorReplyReason.Execution)
{
var error = (EnumSimpleExecutionError)enumReason.Value2;
throw new ExecutionReplyException(message, reason, error);
}
if (reason == ErrorReplyReason.FailedToCreateProgram)
{
var error = (EnumSimpleProgramCreationError)enumReason.Value2;
throw new ProgramCreationReplyException(message, reason, error);
}
}
throw new ReplyException(message, reason);
}
}
49 changes: 49 additions & 0 deletions net/src/Sails.Remoting/Exceptions/ExecutionReplyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using Substrate.Gear.Api.Generated.Model.gear_core_errors.simple;

namespace Sails.Remoting.Exceptions;

public class ExecutionReplyException : ReplyException
{
public SimpleExecutionError ExecutionError { get; } = SimpleExecutionError.Unsupported;

protected ExecutionReplyException()
{
}

protected ExecutionReplyException(string message)
: base(message)
{
}

public ExecutionReplyException(string message, ErrorReplyReason reason, SimpleExecutionError executionError)
: base(message, reason)
{
this.ExecutionError = executionError;
}

public ExecutionReplyException(
string message,
ErrorReplyReason reason,
SimpleExecutionError executionError,
Exception innerException)
: base(message, reason, innerException)
{
this.ExecutionError = executionError;
}

protected ExecutionReplyException(string message, ErrorReplyReason reason)
: base(message, reason)
{
}

protected ExecutionReplyException(string message, ErrorReplyReason reason, Exception innerException)
: base(message, reason, innerException)
{
}

protected ExecutionReplyException(string message, Exception innerException)
: base(message, innerException)
{
}
}
48 changes: 48 additions & 0 deletions net/src/Sails.Remoting/Exceptions/ProgramCreationReplyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using Substrate.Gear.Api.Generated.Model.gear_core_errors.simple;

namespace Sails.Remoting.Exceptions;

public class ProgramCreationReplyException : ReplyException
{
public SimpleProgramCreationError ProgramCreationError { get; }

protected ProgramCreationReplyException()
{
}

protected ProgramCreationReplyException(string message) : base(message)
{
}

public ProgramCreationReplyException(string message, ErrorReplyReason reason, SimpleProgramCreationError programCreationError)
: base(message, reason)
{
this.ProgramCreationError = programCreationError;
}

public ProgramCreationReplyException(
string message,
ErrorReplyReason reason,
SimpleProgramCreationError programCreationError,
Exception innerException)
: base(message, reason, innerException)
{
this.ProgramCreationError = programCreationError;
}


protected ProgramCreationReplyException(string message, ErrorReplyReason reason) : base(message, reason)
{
}

protected ProgramCreationReplyException(string message, ErrorReplyReason reason, Exception innerException)
: base(message, reason, innerException)
{
}

protected ProgramCreationReplyException(string message, Exception innerException)
: base(message, innerException)
{
}
}
35 changes: 35 additions & 0 deletions net/src/Sails.Remoting/Exceptions/ReplyException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using Substrate.Gear.Api.Generated.Model.gear_core_errors.simple;

namespace Sails.Remoting.Exceptions;

public class ReplyException : Exception
{
public ErrorReplyReason Reason { get; } = ErrorReplyReason.Unsupported;

protected ReplyException()
{
}

protected ReplyException(string message)
: base(message)
{
}

public ReplyException(string message, ErrorReplyReason reason)
: base(message)
{
this.Reason = reason;
}

public ReplyException(string message, ErrorReplyReason reason, Exception innerException)
: base(message, innerException)
{
this.Reason = reason;
}

protected ReplyException(string message, Exception innerException)
: base(message, innerException)
{
}
}
30 changes: 30 additions & 0 deletions net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using Sails.Remoting.Exceptions;
using Sails.Remoting.Tests._Infra.XUnit.Fixtures;
using Substrate.Gear.Api.Generated.Model.gear_core_errors.simple;
using Substrate.Gear.Client.GearApi.Model.gprimitives;
using Substrate.NetApi.Model.Types.Primitive;

Expand Down Expand Up @@ -154,4 +156,32 @@ public async Task EventListener_Works()
source.Should().BeEquivalentTo(programId);
payload.Should().BeEquivalentTo(expectedEventPayload, options => options.WithStrictOrdering());
}

[Fact]
public async Task Program_Activation_Throws_NotEnoughGas()
{
// Arrange
var codeId = await this.sailsFixture.GetDemoContractCodeIdAsync();

// Act
var encodedPayload = new Str("Default").Encode();
var activationReply = await this.remoting.ActivateAsync(
codeId,
salt: BitConverter.GetBytes(Random.NextInt64()),
encodedPayload,
gasLimit: new(0),
value: new(0),
CancellationToken.None);

// throws on ReadAsync
var ex = await Assert.ThrowsAsync<ExecutionReplyException>(() => activationReply.ReadAsync(CancellationToken.None));

// Assert
ex.Should().BeEquivalentTo(new
{
Message = "Not enough gas to handle program data",
Reason = ErrorReplyReason.Execution,
ExecutionError = SimpleExecutionError.RanOutOfGas,
});
}
}

0 comments on commit b567304

Please sign in to comment.