Skip to content

Commit

Permalink
fix(net): query reply code parse (#699)
Browse files Browse the repository at this point in the history
  • Loading branch information
vobradovich authored Dec 2, 2024
1 parent 6589f74 commit 6295488
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 17 deletions.
23 changes: 11 additions & 12 deletions net/src/Sails.Remoting/Core/RemotingViaNodeClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EnsureThat;
Expand Down Expand Up @@ -90,11 +91,9 @@ public RemotingViaNodeClient(
cancellationToken),
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()
);
var payload = replyMessage.Payload.Value.Value.Select(@byte => @byte.Value).ToArray();
EnsureSuccessOrThrowReplyException(replyMessage.Details.Value.Code, payload);
return ((ActorId)queuedMessageData.Value[2], payload);
},
cancellationToken)
.ConfigureAwait(false);
Expand Down Expand Up @@ -141,8 +140,9 @@ public async Task<RemotingReply<byte[]>> MessageAsync(
cancellationToken),
extractResult: static (_, replyMessage) =>
{
EnsureSuccessOrThrowReplyException(replyMessage.Details.Value.Code, replyMessage.Payload.Bytes);
return replyMessage.Payload.Value.Value.Select(@byte => @byte.Value).ToArray();
var payload = replyMessage.Payload.Value.Value.Select(@byte => @byte.Value).ToArray();
EnsureSuccessOrThrowReplyException(replyMessage.Details.Value.Code, payload);
return payload;
},
cancellationToken)
.ConfigureAwait(false);
Expand Down Expand Up @@ -208,17 +208,16 @@ private static void EnsureSuccessOrThrowReplyException(EnumReplyCode replyCode,

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

private static void ThrowReplyException(EnumReplyCode replyCode, string message)
Expand Down
74 changes: 69 additions & 5 deletions net/src/Substrate.Gear.Client/SubstrateClientExtExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,75 @@ public ReplyInfo ToReplyInfo()
{
EncodedPayload = Utils.HexToByteArray(this.EncodedPayload),
Value = (ValueUnit)this.Value,
// TODO: It is broken. Need to deserialize rust enum (see serde).
Code = new EnumReplyCode()
{
Value = ReplyCode.Success
}
Code = this.Code.DeserializeEnumReplyCode(),
};
}

/// <summary>
/// Convert JToken (JObject) wtih single property to EnumReplyCode
/// </summary>
/// <param name="token">JObject with single property</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="NotImplementedException"></exception>
internal static EnumReplyCode DeserializeEnumReplyCode(this JToken? token)
{
if (token?.First is not JProperty prop || !Enum.TryParse<ReplyCode>(prop.Name, out var replyCode))
{
throw new InvalidOperationException("Failed to convert JToken to EnumReplyCode");
}
IType value = replyCode switch
{
ReplyCode.Success => DeserializeBaseEnum<EnumSuccessReplyReason, SuccessReplyReason>(prop.Value),
ReplyCode.Error => DeserializeEnumErrorReplyReason(prop.Value),
ReplyCode.Unsupported => new BaseVoid(),
_ => throw new NotImplementedException(),
};
var enumValue = new EnumReplyCode();
enumValue.Create(replyCode, value);
return enumValue;
}

/// <summary>
/// Convert JToken (JObject) wtih single property to EnumErrorReplyReason
/// </summary>
/// <param name="token">JObject with single property</param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="NotImplementedException"></exception>
internal static EnumErrorReplyReason DeserializeEnumErrorReplyReason(this JToken? token)
{
if (token?.First is not JProperty prop || !Enum.TryParse<ErrorReplyReason>(prop.Name, out var replyReason))
{
throw new InvalidOperationException("Failed to convert JToken to EnumErrorReplyReason");
}
IType value = replyReason switch
{
ErrorReplyReason.Execution
=> DeserializeBaseEnum<EnumSimpleExecutionError, SimpleExecutionError>(prop.Value),
ErrorReplyReason.FailedToCreateProgram
=> DeserializeBaseEnum<EnumSimpleProgramCreationError, SimpleProgramCreationError>(prop.Value),
ErrorReplyReason.InactiveActor => new BaseVoid(),
ErrorReplyReason.RemovedFromWaitlist => new BaseVoid(),
ErrorReplyReason.ReinstrumentationFailure => new BaseVoid(),
ErrorReplyReason.Unsupported => new BaseVoid(),
_ => throw new NotImplementedException(),
};
var enumValue = new EnumErrorReplyReason();
enumValue.Create(replyReason, value);
return enumValue;
}

internal static T DeserializeBaseEnum<T, TEnum>(this JToken? token)
where T : BaseEnum<TEnum>, new()
where TEnum : struct, Enum
{
if (token is not JValue val || !Enum.TryParse<TEnum>(val.ToString(), out var enumValue))
{
throw new InvalidOperationException($"Failed to convert JToken to {typeof(T).FullName}");
}
var value = new T();
value.Create(enumValue);
return value;
}
}
42 changes: 42 additions & 0 deletions net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,46 @@ public async Task Program_Activation_Throws_NotEnoughGas()
ExecutionError = SimpleExecutionError.RanOutOfGas,
});
}

[Fact]
public async Task Program_Query_Throws_NotEnoughGas()
{
// Arrange
var codeId = await this.sailsFixture.GetDemoContractCodeIdAsync();
var activationReply = await this.remoting.ActivateAsync(
codeId,
salt: BitConverter.GetBytes(Random.NextInt64()),
new Str("Default").Encode(),
CancellationToken.None);
var (programId, _) = await activationReply.ReadAsync(CancellationToken.None);
var messageReply = await this.remoting.MessageAsync(
programId,
encodedPayload: new Str("Counter").Encode()
.Concat(new Str("Add").Encode())
.Concat(new U32(42).Encode())
.ToArray(),
CancellationToken.None);
await messageReply.ReadAsync(CancellationToken.None);

// Act
var encodedPayload = new Str("Counter").Encode()
.Concat(new Str("Value").Encode())
.ToArray();

// throws on QueryAsync
var ex = await Assert.ThrowsAsync<ExecutionReplyException>(() => this.remoting.QueryAsync(
programId,
encodedPayload,
new(0),
new(0),
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 6295488

Please sign in to comment.