From 9413cbef00fcfc1029bc9d38133dadfd929de9e3 Mon Sep 17 00:00:00 2001 From: Cedric Decoster Date: Tue, 24 Oct 2023 22:44:15 +0300 Subject: [PATCH] [WIP] Feat rust primitives (#65) * added implicit operators added explicit operators added tests removed unused dependencies * formatting * fixed test for extrinsic state * added a fail test with bob * added inistial work on unstable api https://paritytech.github.io/json-rpc-interface-spec/ --- .../TypeConverters/BaseTypesTest.cs | 106 +++++++++- .../TypeConverters/PrimitiveTypesTest.cs | 185 ++++++++++++++---- Substrate.NetApi.TestNode/ExtrinsicsTest.cs | 167 ++++++++-------- Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs | 1 + .../Model/Rpc/TransactionEventInfo.cs | 36 ++++ .../Model/Types/Base/BaseBitSeq.cs | 5 +- Substrate.NetApi/Model/Types/Base/BaseCom.cs | 8 +- Substrate.NetApi/Model/Types/Base/BaseEnum.cs | 8 +- Substrate.NetApi/Model/Types/Base/BaseOpt.cs | 10 +- Substrate.NetApi/Model/Types/Base/BasePrim.cs | 6 +- Substrate.NetApi/Model/Types/Base/BaseType.cs | 8 +- Substrate.NetApi/Model/Types/Base/BaseVec.cs | 9 +- .../Model/Types/Base/BlockNumber.cs | 4 + Substrate.NetApi/Model/Types/Base/Hash.cs | 9 +- .../Model/Types/Primitive/Bool.cs | 4 + .../Model/Types/Primitive/I128.cs | 4 + Substrate.NetApi/Model/Types/Primitive/I16.cs | 4 + .../Model/Types/Primitive/I256.cs | 4 + Substrate.NetApi/Model/Types/Primitive/I32.cs | 4 + Substrate.NetApi/Model/Types/Primitive/I64.cs | 4 + Substrate.NetApi/Model/Types/Primitive/I8.cs | 4 + .../Model/Types/Primitive/PrimChar.cs | 4 + Substrate.NetApi/Model/Types/Primitive/Str.cs | 4 + .../Model/Types/Primitive/U128.cs | 7 +- Substrate.NetApi/Model/Types/Primitive/U16.cs | 4 + .../Model/Types/Primitive/U256.cs | 4 + Substrate.NetApi/Model/Types/Primitive/U32.cs | 4 + Substrate.NetApi/Model/Types/Primitive/U64.cs | 4 + Substrate.NetApi/Model/Types/Primitive/U8.cs | 4 + .../Modules/Contracts/IUnstableCalls.cs | 68 +++++++ Substrate.NetApi/Modules/UnstableCalls.cs | 111 +++++++++++ Substrate.NetApi/SubscriptionListener.cs | 6 + Substrate.NetApi/Substrate.NetApi.csproj | 2 +- Substrate.NetApi/SubstrateClient.cs | 10 + .../ExtrinsicStatusJsonConverter.cs | 87 ++++++++ 35 files changed, 757 insertions(+), 152 deletions(-) create mode 100644 Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs create mode 100644 Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs create mode 100644 Substrate.NetApi/Modules/UnstableCalls.cs diff --git a/Substrate.NetApi.Test/TypeConverters/BaseTypesTest.cs b/Substrate.NetApi.Test/TypeConverters/BaseTypesTest.cs index 4635bbb..a908e96 100644 --- a/Substrate.NetApi.Test/TypeConverters/BaseTypesTest.cs +++ b/Substrate.NetApi.Test/TypeConverters/BaseTypesTest.cs @@ -3,6 +3,8 @@ using Substrate.NetApi.Model.Types.Base; using Substrate.NetApi.Model.Types.Primitive; using NUnit.Framework; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; namespace Substrate.NetApi.Test { @@ -90,6 +92,20 @@ public void BaseVecTest() var baseVecCtor_2 = new BaseVec( new uint[] { 4, 8, 15, 16, 23, 42, 100 }.Select(x => new U16((ushort)x)).ToArray()); Assert.IsFalse(baseVecCtor.Equals(baseVecCtor_2)); + + U16[] arrayFromBaseVec = baseVec; + for (int i = 0; i < vecUInt16.Length; i++) + { + Assert.AreEqual(vecUInt16[i], arrayFromBaseVec[i].Value); + } + + BaseVec baseVecFromU16Array = (BaseVec) arrayFromBaseVec; + for (int i = 0; i < vecUInt16.Length; i++) + { + Assert.AreEqual(vecUInt16[i], baseVecFromU16Array.Value[i].Value); + } + Assert.AreEqual(baseVec.Bytes, baseVecFromU16Array.Bytes); + Assert.AreEqual(baseVec.TypeSize, baseVecFromU16Array.TypeSize); } [Test] @@ -130,6 +146,45 @@ public void BaseOptTest() Assert.That(baseOptFilledError.OptionFlag, Is.EqualTo(false)); Assert.That(baseOptFilledError.Bytes, Is.Not.Null); Assert.That(baseOptFilledError.Bytes.Length, Is.EqualTo(1)); + + ulong value = 100; + + // Testing implicit operator + BaseOpt implicitOpt = (U64)value; + Assert.AreEqual(value, implicitOpt.Value.Value); + + // Testing explicit operator with a filled BaseOpt + BaseOpt explicitOptFilled = new BaseOpt((U64)value); + ulong explicitValueFilled = (U64)explicitOptFilled; + Assert.AreEqual(value, explicitValueFilled); + + // Testing explicit operator with an empty BaseOpt + BaseOpt explicitOptEmpty = new BaseOpt(null); + bool exceptionThrown = false; + try + { + _ = (U64)explicitOptEmpty; + } + catch (InvalidOperationException ex) + { + exceptionThrown = true; + Assert.AreEqual("Option is None", ex.Message); + } + Assert.IsTrue(exceptionThrown, "Exception not thrown for explicit cast of empty BaseOpt"); + + // Testing explicit operator with a filled BaseOpt but OptionFlag = false (this is an unusual case) + explicitOptFilled.OptionFlag = false; + exceptionThrown = false; + try + { + _ = (U64)explicitOptFilled; + } + catch (InvalidOperationException ex) + { + exceptionThrown = true; + Assert.AreEqual("Option is None", ex.Message); + } + Assert.IsTrue(exceptionThrown, "Exception not thrown for explicit cast with OptionFlag = false"); } [Test] @@ -206,6 +261,18 @@ public void BaseComTest() Assert.AreEqual(baseComFromValue.Bytes, new BaseCom(new CompactInteger(new U128(10))).Bytes); Assert.AreEqual(baseComFromValue.Value, new BaseCom(new CompactInteger(new U128(10))).Value); Assert.AreEqual(baseComFromValue.TypeSize, new BaseCom(new CompactInteger(new U128(10))).TypeSize); + + // Test explicit conversion from CompactInteger to BaseCom + var compactInt = new CompactInteger(new U64(10)); + var baseComFromExplicitConversion = (BaseCom)compactInt; + Assert.AreEqual(baseComFromValue.Bytes, baseComFromExplicitConversion.Bytes); + Assert.AreEqual(baseComFromValue.Value, baseComFromExplicitConversion.Value); + Assert.AreEqual(baseComFromValue.TypeSize, baseComFromExplicitConversion.TypeSize); + + // Test implicit conversion from BaseCom to CompactInteger + CompactInteger compactIntFromImplicitConversion = baseComFromValue; + Assert.AreEqual(compactInt.Value, compactIntFromImplicitConversion.Value); + Assert.AreEqual(compactInt.Encode(), compactIntFromImplicitConversion.Encode()); } public enum PartialBalanceEvents @@ -238,6 +305,16 @@ public void BaseEnumTest() Assert.AreNotEqual(baseEnumFromValue.Bytes, new BaseEnum(PartialBalanceEvents.BalanceSet).Bytes); Assert.AreNotEqual(baseEnumFromValue.Bytes, new BaseEnum(PartialBalanceEvents.BalanceSet).Value); + + // Test explicit conversion from Enum to BaseEnum + BaseEnum baseEnumFromImplicitConversion = (BaseEnum)PartialBalanceEvents.Transfer; + Assert.AreEqual(baseEnumFromValue.Bytes, baseEnumFromImplicitConversion.Bytes); + Assert.AreEqual(baseEnumFromValue.Value, baseEnumFromImplicitConversion.Value); + Assert.AreEqual(baseEnumFromValue.TypeSize, baseEnumFromImplicitConversion.TypeSize); + + // Test implicit conversion from BaseEnum to Enum + PartialBalanceEvents enumValueFromImplicitConversion = baseEnumFromValue; + Assert.AreEqual(PartialBalanceEvents.Transfer, enumValueFromImplicitConversion); } [Test] @@ -254,14 +331,29 @@ public void AccountIdTest() [Test] public void HashTest() { - var blockHash = new byte[] - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - var hash = new Hash(); - hash.Create(blockHash); - - var hashPrim = new Hash(blockHash); + // Use a more meaningful byte array as test data + var value = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; + var hexValue = Utils.Bytes2HexString(value); - Assert.AreEqual(hash.Bytes, hashPrim.Bytes); + // Create Hash object using Create method + var hash = new Hash(); + hash.Create(value); + Assert.AreEqual(value, hash.Bytes); + Assert.AreEqual(hexValue, hash.Value); + + // Create Hash object using constructor + var hashPrim = new Hash(value); + Assert.AreEqual(value, hashPrim.Bytes); + Assert.AreEqual(hexValue, hashPrim.Value); + + // Test explicit conversion from byte[] to Hash + Hash hashExplicit = (Hash)value; + Assert.AreEqual(value, hashExplicit.Bytes); + Assert.AreEqual(hexValue, hashExplicit.Value); + + // Test implicit conversion from Hash to byte[] + byte[] bytesImplicit = hash; + Assert.AreEqual(value, bytesImplicit); } [Test] diff --git a/Substrate.NetApi.Test/TypeConverters/PrimitiveTypesTest.cs b/Substrate.NetApi.Test/TypeConverters/PrimitiveTypesTest.cs index 82052df..e955045 100644 --- a/Substrate.NetApi.Test/TypeConverters/PrimitiveTypesTest.cs +++ b/Substrate.NetApi.Test/TypeConverters/PrimitiveTypesTest.cs @@ -2,6 +2,7 @@ using System.Numerics; using Substrate.NetApi.Model.Types.Primitive; using NUnit.Framework; +using Newtonsoft.Json.Linq; namespace Substrate.NetApi.Test { @@ -25,42 +26,70 @@ public void PrimBoolTest() var primTrueCtor = new Bool(true); Assert.AreEqual(true, primTrueCtor.Value); Assert.AreEqual(primTrueCtor.Bytes, primTrue.Bytes); + + Bool primExplicit = (Bool)false; + Assert.AreEqual(false, primExplicit.Value); + + bool primImplicit = new Bool(true); + Assert.AreEqual(true, primImplicit); } [Test] public void PrimCharTest() { - // str test - var oneChar = 'b'; + var value = 'b'; + var primChar = new PrimChar(); - primChar.Create(oneChar); - Assert.AreEqual(oneChar, primChar.Value); + primChar.Create(value); + Assert.AreEqual(value, primChar.Value); - var primCharCtor = new PrimChar(oneChar); - Assert.AreEqual(oneChar, primCharCtor.Value); + var primCharCtor = new PrimChar(value); + Assert.AreEqual(value, primCharCtor.Value); Assert.AreEqual(primCharCtor.Bytes, primChar.Bytes); + + PrimChar primExplicit = (PrimChar)value; + Assert.AreEqual(value, primExplicit.Value); + + char primImplicit = new PrimChar(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimU8Test() { + byte value = 69; + var prim = new U8(); prim.Create("0x45"); - Assert.AreEqual(69, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new U8(69); + var primCtor = new U8(value); Assert.AreEqual(prim.Value, primCtor.Value); + + U8 primExplicit = (U8)value; + Assert.AreEqual(value, primExplicit.Value); + + byte primImplicit = new U8(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimU16Test() { + ushort value = 42; + var prim = new U16(); prim.Create("0x2a00"); - Assert.AreEqual(42, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new U16(42); + var primCtor = new U16(value); Assert.AreEqual(prim.Value, primCtor.Value); + + U16 primExplicit = (U16)value; + Assert.AreEqual(value, primExplicit.Value); + + ushort primImplicit = new U16(value); + Assert.AreEqual(value, primImplicit); } [Test] @@ -77,12 +106,20 @@ public void PrimU16CreateTest() [Test] public void PrimU32Test() { + uint value = 16777215; + var prim = new U32(); prim.Create("0xffffff00"); - Assert.AreEqual(16777215, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new U32(16777215); + var primCtor = new U32(value); Assert.AreEqual(prim.Value, primCtor.Value); + + U32 primExplicit = (U32)value; + Assert.AreEqual(value, primExplicit.Value); + + uint primImplicit = new U32(value); + Assert.AreEqual(value, primImplicit); } [Test] @@ -99,12 +136,20 @@ public void PrimU32CreateTest() [Test] public void PrimU64Test() { + ulong value = 72057589759737855; + var prim = new U64(); prim.Create("0xffffff00ffffff00"); Assert.AreEqual(72057589759737855, prim.Value); var primCtor = new U64(72057589759737855); Assert.AreEqual(prim.Value, primCtor.Value); + + U64 primExplicit = (U64)value; + Assert.AreEqual(value, primExplicit.Value); + + ulong primImplicit = new U64(value); + Assert.AreEqual(value, primImplicit); } [Test] @@ -121,13 +166,20 @@ public void PrimU64CreateTest() [Test] public void PrimU128Test() { - var bigNum = BigInteger.Parse("1329227916866238350086128051511361535"); + var value = BigInteger.Parse("1329227916866238350086128051511361535"); + var prim = new U128(); prim.Create("0xffffff00ffffff00ffffff00ffffff00"); - Assert.AreEqual(bigNum, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new U128(bigNum); + var primCtor = new U128(value); Assert.AreEqual(prim.Value, primCtor.Value); + + U128 primExplicit = (U128)value; + Assert.AreEqual(value, primExplicit.Value); + + BigInteger primImplicit = new U128(value); + Assert.AreEqual(value, primImplicit); } [Test] @@ -162,17 +214,24 @@ public void PrimU128_WithNegativeNumber_ShouldFail() [Test] public void PrimU256Test() { - var bigNumber = BigInteger.Parse("452312821728632006638659744032470891714787547825123743022878680681856106495"); + var value = BigInteger.Parse("452312821728632006638659744032470891714787547825123743022878680681856106495"); + var prim = new U256(); prim.Create("0xffffff00ffffff00ffffff00ffffff00ffffff00ffffff00ffffff00ffffff00"); - Assert.AreEqual(bigNumber, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new U256(bigNumber); + var primCtor = new U256(value); Assert.AreEqual(prim.Value, primCtor.Value); // 0 is a valid input var primZero = new U256(0); Assert.That(primZero.Value, Is.EqualTo(BigInteger.Zero)); + + U256 primExplicit = (U256)value; + Assert.AreEqual(value, primExplicit.Value); + + BigInteger primImplicit = new U256(value); + Assert.AreEqual(value, primImplicit); } [Test] @@ -185,86 +244,138 @@ public void PrimU256_WithNegativeNumber_ShouldFail() [Test] public void PrimI8Test() { + sbyte value = -11; + var prim = new I8(); prim.Create("0xf5"); - Assert.AreEqual(-11, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I8(-11); + var primCtor = new I8(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I8 primExplicit = (I8)value; + Assert.AreEqual(value, primExplicit.Value); + + sbyte primImplicit = new I8(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimI16Test() { + short value = -2571; + var prim = new I16(); prim.Create("0xf5f5"); - Assert.AreEqual(-2571, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I16(-2571); + var primCtor = new I16(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I16 primExplicit = (I16)value; + Assert.AreEqual(value, primExplicit.Value); + + short primImplicit = new I16(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimI32Test() { + int value = -168430091; + var prim = new I32(); prim.Create("0xf5f5f5f5"); - Assert.AreEqual(-168430091, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I32(-168430091); + var primCtor = new I32(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I32 primExplicit = (I32)value; + Assert.AreEqual(value, primExplicit.Value); + + int primImplicit = new I32(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimI64Test() { + long value = -723401728380766731; + var prim = new I64(); prim.Create("0xf5f5f5f5f5f5f5f5"); - Assert.AreEqual(-723401728380766731, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I64(-723401728380766731); + var primCtor = new I64(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I64 primExplicit = (I64)value; + Assert.AreEqual(value, primExplicit.Value); + + long primImplicit = new I64(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimI128Test() { - var bigNumber = BigInteger.Parse("-13344406545919155429936259114971302411"); + var value = BigInteger.Parse("-13344406545919155429936259114971302411"); + var prim = new I128(); prim.Create("0xf5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5"); - Assert.AreEqual(bigNumber, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I128(bigNumber); + var primCtor = new I128(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I128 primExplicit = (I128)value; + Assert.AreEqual(value, primExplicit.Value); + + BigInteger primImplicit = new I128(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimI256Test() { - var veryveryveryveryBigNegativeNumber = BigInteger.Parse("-4540866244600635114649842549360310111892940575123159374096375843447573711371"); + var value = BigInteger.Parse("-4540866244600635114649842549360310111892940575123159374096375843447573711371"); + var prim = new I256(); prim.Create("0xf5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5"); - Assert.AreEqual(veryveryveryveryBigNegativeNumber, prim.Value); + Assert.AreEqual(value, prim.Value); - var primCtor = new I256(veryveryveryveryBigNegativeNumber); + var primCtor = new I256(value); Assert.AreEqual(prim.Value, primCtor.Value); + + I256 primExplicit = (I256)value; + Assert.AreEqual(value, primExplicit.Value); + + BigInteger primImplicit = new I256(value); + Assert.AreEqual(value, primImplicit); } [Test] public void PrimStrTest() { - // str test - var vecChar = new char[] { 'b', 'a', 'n', 'a', 'n', 'e' }; + var valueStr = "banane"; + var primVec = new Str(); primVec.Create(Utils.HexToByteArray("0x1862616e616e65")); - for (int i = 0; i < vecChar.Length; i++) + for (int i = 0; i < valueStr.Length; i++) { - Assert.AreEqual(vecChar[i], primVec.Value.ToCharArray()[i]); + Assert.AreEqual(valueStr[i], primVec.Value[i]); } - var primCtor = new Str(new string(vecChar)); + var primCtor = new Str(valueStr); Assert.AreEqual(primVec.Value, primCtor.Value); Assert.AreEqual(primVec.Bytes, primCtor.Bytes); + + Str primExplicit = (Str)valueStr; + Assert.AreEqual(valueStr, primExplicit.Value); + + string primImplicit = new Str(valueStr); + Assert.AreEqual(valueStr, primImplicit); } } } \ No newline at end of file diff --git a/Substrate.NetApi.TestNode/ExtrinsicsTest.cs b/Substrate.NetApi.TestNode/ExtrinsicsTest.cs index 9bba304..f3cd67a 100644 --- a/Substrate.NetApi.TestNode/ExtrinsicsTest.cs +++ b/Substrate.NetApi.TestNode/ExtrinsicsTest.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using NUnit.Framework; +using NUnit.Framework.Internal; +using Schnorrkel.Keys; using Substrate.NetApi.Model.Extrinsics; using Substrate.NetApi.Model.Rpc; using Substrate.NetApi.Model.Types; using Substrate.NetApi.Model.Types.Base; -using Substrate.NetApi.Model.Types.Primitive; -using NUnit.Framework; -using Schnorrkel.Keys; +using System; +using System.Threading; +using System.Threading.Tasks; namespace Substrate.NetApi.TestNode { @@ -17,6 +15,7 @@ public class ExtrinsicsTest { public MiniSecret MiniSecretAlice => new MiniSecret(Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"), ExpandMode.Ed25519); public Account Alice => Account.Build(KeyType.Sr25519, MiniSecretAlice.ExpandToSecret().ToBytes(), MiniSecretAlice.GetPair().Public.Key); + public MiniSecret MiniSecretBob => new MiniSecret(Utils.HexToByteArray("0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89"), ExpandMode.Ed25519); public Account Bob => Account.Build(KeyType.Sr25519, MiniSecretBob.ExpandToSecret().ToBytes(), MiniSecretBob.GetPair().Public.Key); @@ -39,110 +38,116 @@ public async Task CloseAsync() } [OneTimeSetUp] - public void CreateClient() + public async Task SetupAsync() { _chargeType = ChargeAssetTxPayment.Default(); _substrateClient = new SubstrateClient(new Uri(WebSocketUrl), _chargeType); + + try + { + await _substrateClient.ConnectAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to connect to Substrate node: {ex.Message}"); + Assert.Ignore("Skipped test because no active Substrate node was found on 127.0.0.1:9944"); + } } [OneTimeTearDown] - public void DisposeClient() + public async Task TearDownAsync() { - _substrateClient.Dispose(); + if (_substrateClient != null) + { + await _substrateClient.CloseAsync(); + _substrateClient.Dispose(); + } } /// - /// Extrinsic Remark test + /// Extrinsic Submit And Watch /// /// [Test] - public async Task Extrinsic_RemarkAsync() + public async Task Extrinsic_SubmitAndWatchExtrinsicAsync() { var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF }); - var taskCompletionSource = new TaskCompletionSource(); - await _substrateClient.Author.SubmitAndWatchExtrinsicAsync((string subscriptionId, ExtrinsicStatus extrinsicUpdate) => Callback(subscriptionId, extrinsicUpdate, taskCompletionSource), method, Alice, _chargeType, 64, CancellationToken.None); - - var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1))); // 5 minutes or any appropriate timeout + var taskCompletionSource = new TaskCompletionSource<(bool, Hash)>(); + await _substrateClient.Author.SubmitAndWatchExtrinsicAsync((string subscriptionId, ExtrinsicStatus extrinsicUpdate) => + { + if (extrinsicUpdate.ExtrinsicState == ExtrinsicState.Finalized || + extrinsicUpdate.ExtrinsicState == ExtrinsicState.Dropped || + extrinsicUpdate.ExtrinsicState == ExtrinsicState.Invalid) + { + taskCompletionSource.SetResult((true, extrinsicUpdate.Hash)); + } + }, method, Alice, _chargeType, 64, CancellationToken.None); + + var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1))); Assert.AreEqual(taskCompletionSource.Task, finished, "Test timed out waiting for final callback"); } /// - /// Extrinsic Transfer Callback test + /// Transaction Unstable Submit And Watch /// - /// - /// - /// - private static void Callback(string subscriptionId, ExtrinsicStatus extrinsicUpdate, TaskCompletionSource taskCompletionSource) + /// + [Test] + public async Task Extrinsic_TransactionUnstableSubmitAndWatchAsync() { - ActionExtrinsicUpdate(subscriptionId, extrinsicUpdate); - if (extrinsicUpdate.ExtrinsicState == ExtrinsicState.Finalized || - extrinsicUpdate.ExtrinsicState == ExtrinsicState.Dropped || - extrinsicUpdate.ExtrinsicState == ExtrinsicState.Invalid) + var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF }); + + var taskCompletionSource = new TaskCompletionSource<(bool, Hash)>(); + _ = await _substrateClient.Unstable.TransactionUnstableSubmitAndWatchAsync((string subscriptionId, TransactionEventInfo extrinsicUpdate) => { - taskCompletionSource.SetResult(true); - } + if (extrinsicUpdate.TransactionEvent == TransactionEvent.Finalized || + extrinsicUpdate.TransactionEvent == TransactionEvent.Dropped || + extrinsicUpdate.TransactionEvent == TransactionEvent.Invalid || + extrinsicUpdate.TransactionEvent == TransactionEvent.Error) + { + taskCompletionSource.SetResult((true, extrinsicUpdate.Hash)); + } + }, method, Alice, _chargeType, 64, CancellationToken.None); + + var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1))); + Assert.AreEqual(taskCompletionSource.Task, finished, "Test timed out waiting for final callback"); } /// - /// Simple extrinsic tester + /// Transaction Unstable Unwatch /// - /// - /// - private static void ActionExtrinsicUpdate(string subscriptionId, ExtrinsicStatus extrinsicUpdate) + /// + [Test, Timeout(10000)] // Timeout after 10 seconds + public async Task Extrinsic_TransactionUnstableUnwatchAsync() { - if (subscriptionId == null || subscriptionId.Length == 0) - { - Assert.IsTrue(false); - } + var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF }); + var cancellationTokenSource = new CancellationTokenSource(); - switch (extrinsicUpdate.ExtrinsicState) + var taskCompletionSource = new TaskCompletionSource(); + var subscriptionId = await _substrateClient.Unstable.TransactionUnstableSubmitAndWatchAsync( + (subscriptionId, extrinsicUpdate) => + { + { + if (extrinsicUpdate.TransactionEvent != TransactionEvent.Validated) + { + taskCompletionSource.SetResult(true); + } + } + }, + method, Alice, _chargeType, 64, cancellationTokenSource.Token); + + var unsubscribed = await _substrateClient.Unstable.TransactionUnstableUnwatchAsync(subscriptionId); + Assert.IsTrue(unsubscribed, "Unsubscribing from transaction should be successful."); + + // Optionally: wait for the callback to be called, which should not happen + var callbackCalled = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(500)); + if (callbackCalled == taskCompletionSource.Task) { - case ExtrinsicState.Future: - Assert.IsTrue(false); - break; - - case ExtrinsicState.Ready: - Assert.IsTrue(true); - break; - - case ExtrinsicState.Dropped: - Assert.IsTrue(false); - break; - - case ExtrinsicState.Invalid: - Assert.IsTrue(false); - break; - - case ExtrinsicState.Broadcast: - Assert.IsTrue(extrinsicUpdate.Broadcast != null); - break; - - case ExtrinsicState.InBlock: - Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0); - break; - - case ExtrinsicState.Retracted: - Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0); - break; - - case ExtrinsicState.FinalityTimeout: - Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0); - break; - - case ExtrinsicState.Finalized: - Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0); - break; - - case ExtrinsicState.Usurped: - Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0); - break; - - default: - Assert.IsTrue(false); - break; - + Assert.Fail("Callback should not be called after unsubscribing."); } + + // Cleanup if needed + cancellationTokenSource.Cancel(); } } } \ No newline at end of file diff --git a/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs b/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs index 8a6b590..4db6061 100644 --- a/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs +++ b/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using Substrate.NetApi.Model.Types.Base; +using System; namespace Substrate.NetApi.Model.Rpc { diff --git a/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs b/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs new file mode 100644 index 0000000..5e9c891 --- /dev/null +++ b/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs @@ -0,0 +1,36 @@ +using Substrate.NetApi.Model.Types.Base; + +namespace Substrate.NetApi.Model.Rpc +{ + public enum TransactionEvent + { + Validated, + + Broadcasted, + + BestChainBlockIncluded, + + Finalized, + + Error, + + Invalid, + + Dropped + } + + public sealed class TransactionEventInfo + { + public TransactionEvent TransactionEvent { get; set; } + + public uint? NumPeers { get; set; } + + public Hash Hash { get; set; } + + public uint? Index { get; set; } + + public bool? Broadcasted { get; set; } + + public string Error { get; set; } + } +} \ No newline at end of file diff --git a/Substrate.NetApi/Model/Types/Base/BaseBitSeq.cs b/Substrate.NetApi/Model/Types/Base/BaseBitSeq.cs index 24d2cbe..4246302 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseBitSeq.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseBitSeq.cs @@ -1,7 +1,6 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; namespace Substrate.NetApi.Model.Types.Base { diff --git a/Substrate.NetApi/Model/Types/Base/BaseCom.cs b/Substrate.NetApi/Model/Types/Base/BaseCom.cs index 242a2e4..84e3a27 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseCom.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseCom.cs @@ -1,9 +1,11 @@ -using System; - -namespace Substrate.NetApi.Model.Types.Base +namespace Substrate.NetApi.Model.Types.Base { public class BaseCom : BaseType where T : IType, new() { + public static explicit operator BaseCom(CompactInteger p) => new BaseCom(p); + + public static implicit operator CompactInteger(BaseCom p) => p.Value; + public BaseCom() { } diff --git a/Substrate.NetApi/Model/Types/Base/BaseEnum.cs b/Substrate.NetApi/Model/Types/Base/BaseEnum.cs index cb17692..357ce8b 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseEnum.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseEnum.cs @@ -1,11 +1,15 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using System; namespace Substrate.NetApi.Model.Types.Base { public class BaseEnum : IType where T : System.Enum { + public static explicit operator BaseEnum(T p) => new BaseEnum(p); + + public static implicit operator T(BaseEnum p) => p.Value; + public BaseEnum() { } diff --git a/Substrate.NetApi/Model/Types/Base/BaseOpt.cs b/Substrate.NetApi/Model/Types/Base/BaseOpt.cs index f6f8e21..83e10db 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseOpt.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseOpt.cs @@ -1,12 +1,16 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; +using Newtonsoft.Json; using Substrate.NetApi.Model.Types.Primitive; +using System; +using System.Collections.Generic; namespace Substrate.NetApi.Model.Types.Base { public class BaseOpt : IType where T : IType, new() { + public static implicit operator BaseOpt(T p) => new BaseOpt(p); + + public static explicit operator T(BaseOpt p) => p.OptionFlag ? p.Value : throw new InvalidOperationException("Option is None"); + public BaseOpt() { } diff --git a/Substrate.NetApi/Model/Types/Base/BasePrim.cs b/Substrate.NetApi/Model/Types/Base/BasePrim.cs index a56e802..966426d 100644 --- a/Substrate.NetApi/Model/Types/Base/BasePrim.cs +++ b/Substrate.NetApi/Model/Types/Base/BasePrim.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using Substrate.NetApi.Model.Types.Base; -using Substrate.NetApi.Model.Types.Primitive; +using Substrate.NetApi.Model.Types.Base; +using System; namespace Substrate.NetApi.Model.Types { diff --git a/Substrate.NetApi/Model/Types/Base/BaseType.cs b/Substrate.NetApi/Model/Types/Base/BaseType.cs index fb39af6..7e3212b 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseType.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseType.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Substrate.NetApi.Model.Types.Base { @@ -20,8 +18,6 @@ public abstract class BaseType : IType public virtual void Create(string str) => Create(Utils.HexToByteArray(str)); - public virtual void CreateFromJson(string str) => Create(Utils.HexToByteArray(str)); - public virtual void Create(byte[] byteArray) { var p = 0; @@ -29,6 +25,8 @@ public virtual void Create(byte[] byteArray) Decode(byteArray, ref p); } + public virtual void CreateFromJson(string str) => Create(Utils.HexToByteArray(str)); + public IType New() => this; public override string ToString() => JsonConvert.SerializeObject(this); diff --git a/Substrate.NetApi/Model/Types/Base/BaseVec.cs b/Substrate.NetApi/Model/Types/Base/BaseVec.cs index bd0a26c..897bdaf 100644 --- a/Substrate.NetApi/Model/Types/Base/BaseVec.cs +++ b/Substrate.NetApi/Model/Types/Base/BaseVec.cs @@ -1,12 +1,15 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; namespace Substrate.NetApi.Model.Types.Base { public class BaseVec : IType where T : IType, new() { + public static explicit operator BaseVec(T[] p) => new BaseVec(p); + + public static implicit operator T[](BaseVec p) => p.Value; + public BaseVec() { } diff --git a/Substrate.NetApi/Model/Types/Base/BlockNumber.cs b/Substrate.NetApi/Model/Types/Base/BlockNumber.cs index d448c27..f063dd2 100644 --- a/Substrate.NetApi/Model/Types/Base/BlockNumber.cs +++ b/Substrate.NetApi/Model/Types/Base/BlockNumber.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Base { public class BlockNumber : BasePrim { + public static explicit operator BlockNumber(uint p) => new BlockNumber(p); + + public static implicit operator uint(BlockNumber p) => p.Value; + public BlockNumber() { } diff --git a/Substrate.NetApi/Model/Types/Base/Hash.cs b/Substrate.NetApi/Model/Types/Base/Hash.cs index e397565..d15cd2b 100644 --- a/Substrate.NetApi/Model/Types/Base/Hash.cs +++ b/Substrate.NetApi/Model/Types/Base/Hash.cs @@ -1,7 +1,14 @@ -namespace Substrate.NetApi.Model.Types.Base +using System.Collections.Generic; +using System.Linq; + +namespace Substrate.NetApi.Model.Types.Base { public class Hash : BasePrim { + public static explicit operator Hash(byte[] p) => new Hash(p); + + public static implicit operator byte[](Hash p) => p.Bytes; + public Hash() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/Bool.cs b/Substrate.NetApi/Model/Types/Primitive/Bool.cs index b77a1d5..4d5a1d4 100644 --- a/Substrate.NetApi/Model/Types/Primitive/Bool.cs +++ b/Substrate.NetApi/Model/Types/Primitive/Bool.cs @@ -2,6 +2,10 @@ { public class Bool : BasePrim { + public static explicit operator Bool(bool p) => new Bool(p); + + public static implicit operator bool(Bool p) => p.Value; + public Bool() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I128.cs b/Substrate.NetApi/Model/Types/Primitive/I128.cs index 9a8d1e9..901f8ba 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I128.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I128.cs @@ -5,6 +5,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class I128 : BasePrim { + public static explicit operator I128(BigInteger p) => new I128(p); + + public static implicit operator BigInteger(I128 p) => p.Value; + public I128() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I16.cs b/Substrate.NetApi/Model/Types/Primitive/I16.cs index b3404e5..ca11842 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I16.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I16.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class I16 : BasePrim { + public static explicit operator I16(short p) => new I16(p); + + public static implicit operator short(I16 p) => p.Value; + public I16() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I256.cs b/Substrate.NetApi/Model/Types/Primitive/I256.cs index f5991c8..3bb64e2 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I256.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I256.cs @@ -5,6 +5,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class I256 : BasePrim { + public static explicit operator I256(BigInteger p) => new I256(p); + + public static implicit operator BigInteger(I256 p) => p.Value; + public I256() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I32.cs b/Substrate.NetApi/Model/Types/Primitive/I32.cs index df228cf..ae78792 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I32.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I32.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class I32 : BasePrim { + public static explicit operator I32(int p) => new I32(p); + + public static implicit operator int(I32 p) => p.Value; + public I32() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I64.cs b/Substrate.NetApi/Model/Types/Primitive/I64.cs index 475cd19..fe24e65 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I64.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I64.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class I64 : BasePrim { + public static explicit operator I64(long p) => new I64(p); + + public static implicit operator long(I64 p) => p.Value; + public I64() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/I8.cs b/Substrate.NetApi/Model/Types/Primitive/I8.cs index 8dca622..a5a5fcd 100644 --- a/Substrate.NetApi/Model/Types/Primitive/I8.cs +++ b/Substrate.NetApi/Model/Types/Primitive/I8.cs @@ -2,6 +2,10 @@ { public class I8 : BasePrim { + public static explicit operator I8(sbyte p) => new I8(p); + + public static implicit operator sbyte(I8 p) => p.Value; + public I8() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/PrimChar.cs b/Substrate.NetApi/Model/Types/Primitive/PrimChar.cs index 3849428..3411032 100644 --- a/Substrate.NetApi/Model/Types/Primitive/PrimChar.cs +++ b/Substrate.NetApi/Model/Types/Primitive/PrimChar.cs @@ -5,6 +5,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class PrimChar : BasePrim { + public static explicit operator PrimChar(char p) => new PrimChar(p); + + public static implicit operator char(PrimChar p) => p.Value; + public PrimChar() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/Str.cs b/Substrate.NetApi/Model/Types/Primitive/Str.cs index 34ca117..46d3251 100644 --- a/Substrate.NetApi/Model/Types/Primitive/Str.cs +++ b/Substrate.NetApi/Model/Types/Primitive/Str.cs @@ -6,6 +6,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class Str : BasePrim { + public static explicit operator Str(string p) => new Str(p); + + public static implicit operator string(Str p) => p.Value; + public Str() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/U128.cs b/Substrate.NetApi/Model/Types/Primitive/U128.cs index 8721b75..371ed95 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U128.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U128.cs @@ -5,9 +5,12 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class U128 : BasePrim { + public static explicit operator U128(BigInteger p) => new U128(p); + + public static implicit operator BigInteger(U128 p) => p.Value; + public U128() - { - } + { } public U128(BigInteger value) { diff --git a/Substrate.NetApi/Model/Types/Primitive/U16.cs b/Substrate.NetApi/Model/Types/Primitive/U16.cs index 1fc50ac..220b342 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U16.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U16.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class U16 : BasePrim { + public static explicit operator U16(ushort p) => new U16(p); + + public static implicit operator ushort(U16 p) => p.Value; + public U16() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/U256.cs b/Substrate.NetApi/Model/Types/Primitive/U256.cs index 39ccc7d..96ef845 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U256.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U256.cs @@ -5,6 +5,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class U256 : BasePrim { + public static explicit operator U256(BigInteger p) => new U256(p); + + public static implicit operator BigInteger(U256 p) => p.Value; + public U256() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/U32.cs b/Substrate.NetApi/Model/Types/Primitive/U32.cs index 9b0626e..074d4e6 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U32.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U32.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class U32 : BasePrim { + public static explicit operator U32(uint p) => new U32(p); + + public static implicit operator uint(U32 p) => p.Value; + public U32() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/U64.cs b/Substrate.NetApi/Model/Types/Primitive/U64.cs index 2532a6f..479100a 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U64.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U64.cs @@ -4,6 +4,10 @@ namespace Substrate.NetApi.Model.Types.Primitive { public class U64 : BasePrim { + public static explicit operator U64(ulong p) => new U64(p); + + public static implicit operator ulong(U64 p) => p.Value; + public U64() { } diff --git a/Substrate.NetApi/Model/Types/Primitive/U8.cs b/Substrate.NetApi/Model/Types/Primitive/U8.cs index 5eb4d9b..372ff69 100644 --- a/Substrate.NetApi/Model/Types/Primitive/U8.cs +++ b/Substrate.NetApi/Model/Types/Primitive/U8.cs @@ -2,6 +2,10 @@ { public class U8 : BasePrim { + public static explicit operator U8(byte p) => new U8(p); + + public static implicit operator byte(U8 p) => p.Value; + public U8() { } diff --git a/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs b/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs new file mode 100644 index 0000000..6e51f2d --- /dev/null +++ b/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Substrate.NetApi.Model.Extrinsics; +using Substrate.NetApi.Model.Rpc; +using Substrate.NetApi.Model.Types; +using Substrate.NetApi.Model.Types.Base; + +namespace Substrate.NetApi.Modules.Contracts +{ + public interface IUnstableCalls + { + /// + /// Submit and subscribe to watch an extrinsic until unsubscribed + /// + /// + /// + /// + /// + /// + /// + Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime); + + /// + /// Submit and subscribe to watch an extrinsic until unsubscribed + /// + /// + /// + /// + /// + /// + /// + /// + Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime, CancellationToken token); + + /// + /// Submit and subscribe to watch an extrinsic until unsubscribed + /// + /// + /// + /// + Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters); + + /// + /// Submit and subscribe to watch an extrinsic until unsubscribed + /// + /// + /// + /// + /// + Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters, CancellationToken token); + + /// + /// Unsuscribe to given subscription id + /// + /// + /// + Task TransactionUnstableUnwatchAsync(string subscriptionId); + + /// + /// Unsuscribe to given subscription id + /// + /// + /// + /// + Task TransactionUnstableUnwatchAsync(string subscriptionId, CancellationToken token); + } +} \ No newline at end of file diff --git a/Substrate.NetApi/Modules/UnstableCalls.cs b/Substrate.NetApi/Modules/UnstableCalls.cs new file mode 100644 index 0000000..421896c --- /dev/null +++ b/Substrate.NetApi/Modules/UnstableCalls.cs @@ -0,0 +1,111 @@ +using Substrate.NetApi.Model.Extrinsics; +using Substrate.NetApi.Model.Rpc; +using Substrate.NetApi.Model.Types; +using Substrate.NetApi.Modules.Contracts; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Substrate.NetApi.Modules +{ + /// + /// New Api 2 + /// + public class UnstableCalls : IUnstableCalls + { + /// The client + private readonly SubstrateClient _client; + + /// + /// New Api 2 + /// + /// + internal UnstableCalls(SubstrateClient client) + { + _client = client; + } + + /// + /// Transaction Unstable Submit And Watch Async + /// + /// + /// + /// + /// + /// + /// + public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime) + { + var extrinsic = await _client.GetExtrinsicParametersAsync(method, account, charge, lifeTime, signed: true, CancellationToken.None); + + return await TransactionUnstableSubmitAndWatchAsync(callback, Utils.Bytes2HexString(extrinsic.Encode())); + } + + /// + /// Transaction Unstable Submit And Watch Async + /// + /// + /// + /// + /// + /// + /// + /// + public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime, CancellationToken token) + { + var extrinsic = await _client.GetExtrinsicParametersAsync(method, account, charge, lifeTime, signed: true, token); + var extrinsicHex = Utils.Bytes2HexString(extrinsic.Encode()); + return await TransactionUnstableSubmitAndWatchAsync(callback, extrinsicHex); + } + + /// + /// Transaction Unstable Submit And Watch Async + /// + /// + /// + /// + public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters) + { + return await TransactionUnstableSubmitAndWatchAsync(callback, parameters, CancellationToken.None); + } + + /// + /// Transaction Unstable Submit And Watch Async + /// + /// + /// + /// + /// + public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters, CancellationToken token) + { + var subscriptionId = + await _client.InvokeAsync("transaction_unstable_submitAndWatch", new object[] { parameters }, token); + _client.Listener.RegisterCallBackHandler(subscriptionId, callback); + return subscriptionId; + } + + /// + /// Transaction Unstable Unwatch Async + /// + /// + /// + public async Task TransactionUnstableUnwatchAsync(string subscriptionId) + { + return await TransactionUnstableUnwatchAsync(subscriptionId, CancellationToken.None); + } + + /// + /// Transaction Unstable Unwatch Async + /// + /// + /// + /// + public async Task TransactionUnstableUnwatchAsync(string subscriptionId, CancellationToken token) + { + var result = + await _client.InvokeAsync("transaction_unstable_unwatch", new object[] { subscriptionId }, token); + if (result) _client.Listener.UnregisterHeaderHandler(subscriptionId); + return result; + } + } +} \ No newline at end of file diff --git a/Substrate.NetApi/SubscriptionListener.cs b/Substrate.NetApi/SubscriptionListener.cs index cbd998c..adeb198 100644 --- a/Substrate.NetApi/SubscriptionListener.cs +++ b/Substrate.NetApi/SubscriptionListener.cs @@ -142,5 +142,11 @@ public void AuthorSubmitAndWatchExtrinsic(string subscription, ExtrinsicStatus r { GenericCallBack(subscription, result); } + + [JsonRpcMethod("transaction_unstable_submitExtrinsic")] + public void TransactionUnstableSubmitExtrinsic(string subscription, TransactionEventInfo result) + { + GenericCallBack(subscription, result); + } } } \ No newline at end of file diff --git a/Substrate.NetApi/Substrate.NetApi.csproj b/Substrate.NetApi/Substrate.NetApi.csproj index a65d251..40fb86e 100644 --- a/Substrate.NetApi/Substrate.NetApi.csproj +++ b/Substrate.NetApi/Substrate.NetApi.csproj @@ -3,7 +3,7 @@ Substrate.NET.API netstandard2.0;netstandard2.1;net6.0 - 0.9.10 + 0.9.11 Substrate Gaming Substrate Gaming true diff --git a/Substrate.NetApi/SubstrateClient.cs b/Substrate.NetApi/SubstrateClient.cs index 71edbfa..bdb1fd0 100644 --- a/Substrate.NetApi/SubstrateClient.cs +++ b/Substrate.NetApi/SubstrateClient.cs @@ -36,6 +36,8 @@ public class SubstrateClient : IDisposable private readonly ExtrinsicStatusJsonConverter _extrinsicStatusJsonConverter; + private readonly TransactionEventJsonConverter _transactionEventJsonConverter; + /// The request token sources. private readonly ConcurrentDictionary _requestTokenSourceDict; @@ -66,12 +68,14 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific _extrinsicJsonConverter = new ExtrinsicJsonConverter(chargeType); _extrinsicStatusJsonConverter = new ExtrinsicStatusJsonConverter(); + _transactionEventJsonConverter = new TransactionEventJsonConverter(); System = new Modules.System(this); Chain = new Chain(this); Payment = new Payment(this); State = new State(this); Author = new Author(this); + Unstable = new UnstableCalls(this); _requestTokenSourceDict = new ConcurrentDictionary(); } @@ -108,6 +112,11 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific /// The author. public Author Author { get; } + /// + /// New Api 2 + /// + public UnstableCalls Unstable { get; } + public SubscriptionListener Listener { get; } = new SubscriptionListener(); /// Gets a value indicating whether this object is connected. @@ -195,6 +204,7 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell formatter.JsonSerializer.Converters.Add(new GenericTypeConverter()); formatter.JsonSerializer.Converters.Add(_extrinsicJsonConverter); formatter.JsonSerializer.Converters.Add(_extrinsicStatusJsonConverter); + formatter.JsonSerializer.Converters.Add(_transactionEventJsonConverter); _jsonRpc = new JsonRpc(new WebSocketMessageHandler(_socket, formatter)); _jsonRpc.TraceSource.Listeners.Add(new SerilogTraceListener.SerilogTraceListener()); diff --git a/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs b/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs index b365c03..71ec3c3 100644 --- a/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs +++ b/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Substrate.NetApi.Model.Rpc; using Substrate.NetApi.Model.Types.Base; using System; @@ -6,6 +7,92 @@ namespace Substrate.NetApi.TypeConverters { + public class TransactionEventJsonConverter : JsonConverter + { + /// Reads the JSON representation of the object. + /// The to read from. + /// Type of the object. + /// The existing value of object being read. If there is no existing value then null will be used. + /// The existing value has a value. + /// The calling serializer. + /// The object value. + /// + /// Unimplemented {reader.TokenType} of type '{reader.ValueType}' and value '{reader.Value}'. + /// or + /// Unimplemented {reader.TokenType} of type '{reader.ValueType}' and value '{reader.Value}'. + /// + public override TransactionEventInfo ReadJson(JsonReader reader, Type objectType, TransactionEventInfo existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var transactionEventStatus = hasExistingValue ? existingValue : new TransactionEventInfo(); + + var jObject = JObject.Load(reader); + + var eventName = jObject["event"]?.ToString(); + if (Enum.TryParse(eventName, true, out TransactionEvent transactionEvent)) + { + transactionEventStatus.TransactionEvent = transactionEvent; + + switch (transactionEvent) + { + case TransactionEvent.Validated: + break; + + case TransactionEvent.Broadcasted: + transactionEventStatus.NumPeers = uint.Parse(jObject["numPeers"].ToString()); + break; + + case TransactionEvent.BestChainBlockIncluded: + var bestChainBlock = jObject["block"]; + if (bestChainBlock != null) + { + transactionEventStatus.Hash = new Hash(jObject["block"]["hash"].ToString()); + transactionEventStatus.Index = uint.Parse(jObject["block"]["index"].ToString()); + } + break; + + case TransactionEvent.Finalized: + transactionEventStatus.Hash = new Hash(jObject["block"]["hash"].ToString()); + transactionEventStatus.Index = uint.Parse(jObject["block"]["index"].ToString()); + break; + + case TransactionEvent.Error: + transactionEventStatus.Error = jObject["error"].ToString(); + break; + + case TransactionEvent.Invalid: + transactionEventStatus.Error = jObject["error"].ToString(); + break; + + case TransactionEvent.Dropped: + // TODO, check if this works broadcassted boolean + //transactionEventStatus.Broadcasted = bool.Parse(jObject["broadcasted"].ToString()); + transactionEventStatus.Error = jObject["error"].ToString(); + break; + + default: + throw new NotImplementedException( + $"Unimplemented state {transactionEvent} with value '{reader.Value}'."); + + + } + } + + return transactionEventStatus; + } + + /// + /// Writes the JSON representation of the object. + /// + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, TransactionEventInfo value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } + public class ExtrinsicStatusJsonConverter : JsonConverter { /// Reads the JSON representation of the object.