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

fix(net): query reply code parse #699

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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,
});
}
}